JuhJuh File
Infrastructure

JuhJuh File

JuhJuh File: Define your infrastructure as code. One file for services, resources, secrets, VMs, and deployments. Full specification with progressive examples.

The JuhJuh File describes your entire application infrastructure in code. Services, databases, caches, secrets, VMs, deployments. Drop it in your repository root. JuhJuh reads it, provisions everything, and deploys your stack.

No clicking through wizards. No manual orchestration. One source of truth.

File discovery

JuhJuh searches for your config in this order:

  1. juhjuh (extensionless, recommended)
  2. juhjuh.yaml
  3. juhjuh.yml
  4. .juhjuh/ directory (multiple YAML files, merged alphabetically)

The extensionless juhjuh name is the recommended convention. It keeps your project root clean and signals a JuhJuh-native file.

For projects with larger configurations, the .juhjuh/ directory splits your config across multiple files. See Multi-file configuration for details.

Start simple

The smallest valid JuhJuh File:

name: my-app
version: "1"

resources:
  postgres:
    type: postgres
    version: "16"

services:
  web:
    type: web
    image: registry.example.com/my-app:latest
    port: 8000
    links: [postgres]

This defines a web service backed by a database. JuhJuh handles networking, health checks, volumes, and orchestration. You can deploy this immediately through the dashboard or the CLI.

Everything below builds on this foundation. Add what you need, skip what you do not.

Root fields

Field Required Description
name Yes Project name. Used for naming containers, networks, and volumes
version No Config version (default: "1")
variables No Reusable values with type safety. Referenced as ${var.name}
config No Cross-cutting platform settings
resources No Infrastructure resources (databases, caches, queues)
envGroups No Reusable sets of environment variables
templates No Reusable service definitions
services No Running processes (web, worker, cron, proxy, daemon, job)
environments No Per-environment overrides
vms No VM provisioning specs
vaults No Secret variable declarations for CLI import
deployments No Deployment targets mapping services to VMs
networks No Organization-level network configuration
firewall No VM-level firewall rules (ports opened to the internet)
hooks No Lifecycle commands
monitoring No Observability configuration

variables

Define reusable values once. Reference them anywhere in the file with ${var.name}. Variables support type checking and defaults, so you can parameterize your entire config without repeating yourself.

variables:
  project_name:
    type: string
    default: my-app
    description: Project identifier used in container and volume names

  app_image:
    type: string
    default: registry.example.com/my-app

  worker_memory:
    type: string
    default: 384M

  worker_cpu:
    type: integer
    default: 1

  debug_mode:
    type: boolean
    default: false

Variable definition fields

Field Required Description
type No string, integer, or boolean (default: string)
default No Default value if none provided
description No Human-readable explanation

How variables resolve

JuhJuh resolves ${var.name} references in two modes:

Full-value replacement preserves the original type. If a variable is type: integer with default: 2, and you write cpu: ${var.worker_cpu}, the resolved value is the integer 2, not the string "2".

Inline replacement always produces a string. If you write image: ${var.app_image}:latest, the result is "registry.example.com/my-app:latest".

Variables are resolved after all files are merged (in multi-file mode) and before schema validation. Any unresolved ${var.name} reference after resolution raises an error listing every unresolved path.

Type coercion

Declared type Behavior
string Converts to string. None becomes ""
integer Converts to int. Raises an error if not coercible. None becomes 0
boolean true, 1, yes (case-insensitive) become True. Everything else becomes False. None becomes False

Using variables

services:
  web:
    type: web
    image: ${var.app_image}:latest
    compute:
      memory: ${var.worker_memory}
      cpu: ${var.worker_cpu}

config

Cross-cutting settings that apply to all services.

config:
  organization: my-team
  region: your-region
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"
  network:
    name: my-app-network
    driver: bridge
  volumes:
    postgres_data:
      name: my-app-postgres-data
    redis_data:
      name: my-app-redis-data
Field Description
organization Your JuhJuh organization slug
region Default deployment region
logging Container log driver and options
network Network name and driver
volumes Named volume definitions
mounts Bind mount configurations
build Build-time settings (context, args)
deploy Default deployment strategy for all services

resources

Infrastructure resources your services depend on: databases, caches, message queues.

resources:
  postgres:
    type: postgres
    version: "16"
    database: my_app
    config:
      max_connections: 200
      shared_buffers: 256MB
    persistence:
      volume: postgres_data
      path: /var/lib/postgresql/data
      init_script: ./scripts/init-db.sh

  redis:
    type: redis
    version: "7"
    config:
      appendonly: "yes"
      maxmemory: 96mb
      maxmemory_policy: allkeys-lru

Supported resource types

Type Default port Auto-injected variables
postgres 5432 DATABASE_URL, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD
redis 6379 REDIS_URL, REDIS_HOST, REDIS_PORT
rabbitmq 5672 AMQP_URL, RABBITMQ_HOST, RABBITMQ_PORT, RABBITMQ_USER, RABBITMQ_PASSWORD
elasticsearch 9200 ELASTICSEARCH_URL
mongodb 27017 MONGODB_URL

When you link a service to a resource, JuhJuh automatically injects these connection variables. No manual wiring needed.

Resource fields

Field Description
type Resource type (see table above, or any custom type)
version Version tag (e.g., "16" for database version 16)
plan Resource tier: dev, starter, standard, premium (default: standard)
database Database name to create
config Type-specific tuning (image overrides, connection limits, memory policies)
persistence Volume mapping, data paths, init scripts, config file mounts
shared Whether this resource is shared across deployments (default: false)
from Reference to an external resource. When set, this resource is not provisioned by JuhJuh
ephemeral Whether data persists between restarts (default: false)
ipAllowList IP addresses allowed to connect directly
firewall Firewall rules for this resource (port, protocol, label, source ranges)

External resources

Use the from field to reference a resource managed outside of JuhJuh. External resources are not provisioned or started by JuhJuh, but their connection details can still be wired through links and env groups.

resources:
  external-db:
    type: postgres
    from: managed-rds-instance

Persistence options

persistence:
  volume: postgres_data
  path: /var/lib/postgresql/data
  init_script: ./scripts/init.sh
  config_files:
    - ./my.conf:/etc/my.conf:ro

Firewall rules

Each rule requires a port number. Use access to control whether the port is reachable from the public internet or only within your private network. Use label for a short display name shown in the infrastructure diagram.

resources:
  postgres:
    type: postgres
    firewall:
      - port: 5432
        protocol: tcp
        access: internal
        label: PostgreSQL
        description: Only reachable within the VPC
Field Required Description
port Yes Port number (integer)
protocol No tcp or udp (default: tcp)
access No public (open to internet) or internal (VPC only). Default: public
label No Short display name, e.g. HTTP, HTTPS, PostgreSQL. Shown in diagrams and UI
allowedSources No Allowed IP ranges (default: ["0.0.0.0/0"]). Automatically cleared when access: internal. Legacy alias: source_ranges
description No Longer explanation of the rule's purpose

envGroups

Reusable sets of environment variables. Define them once, reference them from multiple services with envFrom.

envGroups:
  shared-app:
    clear:
      POSTGRES_HOST: postgres
      POSTGRES_PORT: "5432"
      REDIS_URL: redis://redis:6379/0
      DEBUG: ${DEBUG:-False}
      ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost}
    secret:
      - POSTGRES_PASSWORD
      - SECRET_KEY

Variable types

Clear variables are plain key-value pairs. They support ${VAR:-default} syntax for runtime defaults (resolved by the container, not by JuhJuh).

Secret variables are listed by name only. Their values come from the Vault at deploy time and are never stored in the JuhJuh File. Mark any variable holding credentials, API keys, or tokens as secret.

Secret entries can also be specified as objects with additional metadata:

secret:
  - POSTGRES_PASSWORD
  - key: API_SECRET
    description: Third-party API key

templates

Reusable service definitions. Services reference templates with extends to inherit configuration without duplication.

templates:
  app-common:
    image: registry.example.com/my-app:${IMAGE_TAG:-latest}
    dockerfile: Dockerfile
    links: [postgres, redis]
    envFrom: [shared-app]
    restart: unless-stopped
    volumes:
      - static_files:/app/staticfiles
      - media_files:/app/media

  app-privileged:
    extends: app-common
    privileged: true
    envFrom: [shared-app, dind]

Templates can extend other templates. JuhJuh resolves the full chain and merges configurations, with the child's non-null values taking precedence. Volumes are merged: template volumes first, then service-specific volumes.

Field Description
type Service type (default: worker for templates)
extends Inherit from another template
image Container image with optional ${IMAGE_TAG} interpolation
dockerfile Path to Dockerfile for build-based deployments
command Override the container entrypoint command
entrypoint Override the container entrypoint
links Resources this service connects to
envFrom Environment groups to include
env Additional environment variables (clear and secret)
compute CPU and memory limits
depends_on Startup dependencies with conditions
restart Restart policy: always, unless-stopped, on-failure, never
privileged Run in privileged mode (required for sandboxed execution)
volumes Volume mounts

services

Running processes that make up your application.

services:
  web:
    type: web
    extends: app-common
    port: 8000
    command: /app/scripts/start-web.sh
    healthCheck:
      path: /health/
      interval: 30s
      timeout: 10s
      retries: 3
      startPeriod: 30s
    deploy:
      strategy: rolling
      preCommand: /app/scripts/migrate.sh

  worker-default:
    type: worker
    extends: app-common
    command: /app/scripts/start-worker.sh
    compute:
      memory: 384M
      cpu: 0.50
    worker:
      queue: default
      concurrency: 2

  beat:
    type: cron
    extends: app-common
    command: /app/scripts/start-beat.sh
    replicas: 1

  migrate:
    type: job
    extends: app-common
    command: /app/scripts/migrate.sh
    restart: never

Service types

Type Purpose Example
web HTTP-serving process Application server, API gateway
worker Background task processor Queue consumer, job runner
cron Scheduled task runner Task scheduler, periodic jobs
proxy Reverse proxy or load balancer Traffic routing, SSL termination
daemon Long-running background process Certificate renewal, log shipper
job One-shot task that runs and exits Database migrations, seed scripts

Service fields

Field Description
type One of: web, worker, cron, proxy, daemon, job
extends Template to inherit from
image Container image (overrides template). Use ./ for build mode
dockerfile Dockerfile path for build mode
port Primary listening port
ports Exposed port mappings (e.g., "80:80", "443:443"). Takes precedence over port
command Container command
entrypoint Container entrypoint
links Resource connections
dependsOn Service startup dependencies with conditions
envFrom Environment groups to include
env Inline environment variables
compute CPU and memory limits
replicas Number of instances
restart Restart policy
deploy Deployment strategy and hooks
scaling Auto-scaling policy
autoStop Cost-saving auto-stop after idle timeout
healthCheck Health check configuration
volumes Volume mounts
profiles Conditional inclusion profiles
group Grouping label for the UI
enabled Whether this service runs (default: true)
worker Worker-specific metadata (queue, concurrency, tasks)
upstream Proxy upstream target
ssl SSL/TLS configuration
watchPatterns File patterns that trigger rebuilds during development
ignorePatterns File patterns to exclude from watch
firewall Firewall rules for exposed ports (port, protocol, label)

Health checks

healthCheck:
  path: /health/
  command: ["CMD", "pg_isready"]
  interval: 30s
  timeout: 10s
  retries: 3
  startPeriod: 40s

Use path for HTTP services (generates a curl-based check against localhost:{port}{path}). Use command for everything else. If both are set, command takes precedence.

Field Default Description
path /health/ HTTP path to check
command none Shell command or exec form
interval 30s Time between checks
timeout 10s Max wait per check
retries 3 Failures before unhealthy
startPeriod 40s Grace period after container start

Compute specs

compute:
  memory: 384M
  cpu: 0.50
  memoryReservation: 128M
Field Description
memory Memory limit (e.g., 384M, 2G). Default: 384M
cpu CPU cores, fractional allowed (e.g., 0.50, 2.0). Default: 1.0
memoryReservation Guaranteed minimum memory (soft limit). If not set, estimated at 50% of the memory limit

Deployment hooks

deploy:
  strategy: rolling
  preCommand: /app/scripts/migrate.sh
  postCommand: /app/scripts/collect-assets.sh
  healthCheckGracePeriod: 60s
  stopGracePeriod: 30s

Pre-commands run before new containers start. Post-commands run after containers are healthy. Use pre-commands for database migrations, post-commands for cache warming or static file collection.

Worker metadata

worker:
  queue: default
  concurrency: 2
  tasks:
    - app.tasks.send_email
    - app.tasks.process_upload

Auto-scaling

scaling:
  min: 1
  max: 4
  targetCPU: 70
  targetMemory: 80
  cooldownScaleUp: 5m
  cooldownScaleDown: 10m

Auto-stop

autoStop:
  enabled: true
  idleTimeout: 15m

Useful for preview and development environments to reduce costs.

Dependencies

dependsOn:
  web:
    condition: service_started
  postgres:
    condition: service_healthy

Available conditions: service_started, service_healthy, service_completed_successfully.

Linking a service to a resource automatically creates a service_healthy dependency. You do not need to add it manually.

Proxy configuration

services:
  nginx:
    type: proxy
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    upstream:
      target: web
    ssl:
      enabled: "true"
    dependsOn:
      web:
        condition: service_started

The upstream field tells the proxy which service to forward traffic to.

Profiles

Profiles let you conditionally include services based on the deployment context. A service with profiles set only runs when at least one of its profiles is active.

services:
  debug-console:
    type: daemon
    extends: app-common
    profiles: [development, staging]

When resolving an environment, services whose profiles do not intersect with the active profile set are disabled.


environments

Per-environment overrides that modify the base configuration. Use environments to change resource sizes, service compute, or settings between production, staging, and development.

environments:
  production:
    config:
      region: your-region
    resources:
      postgres:
        plan: premium
        config:
          max_connections: 400
    services:
      worker-default:
        compute:
          memory: 1024M
          cpu: 1.0
        replicas: 2

  staging:
    inherit: production
    services:
      worker-default:
        compute:
          memory: 512M
        replicas: 1

Environment fields

Field Description
inherit Parent environment to inherit from (deep-merged before this environment's overrides)
resources Resource-level overrides (merged by resource name)
services Service-level overrides (merged by service name)
config Config section overrides
envOverrides Environment group overrides (add or replace clear/secret vars in named env groups)

Inheritance

Environments can inherit from other environments. Overrides are deep-merged: scalars and lists replace the parent, dicts merge recursively. Circular inheritance is detected and rejected.

Disabling services per environment

Set enabled: false in an environment override to exclude a service:

environments:
  production:
    services:
      debug-console:
        enabled: false

Environment group overrides

Use envOverrides to add or replace variables in an env group for a specific environment without redefining the entire group:

environments:
  production:
    envOverrides:
      shared-app:
        clear:
          DEBUG: "False"
          ALLOWED_HOSTS: "*.example.com"

Clear values are merged (new keys added, existing keys overwritten). New secret keys are appended if not already present.


Links connect services to resources. When you link a service to a resource, JuhJuh injects the connection details as environment variables automatically.

links: [postgres, redis]
links:
  - resource: postgres
    as:
      DATABASE_URL: PRIMARY_DB_URL
      host: PRIMARY_DB_HOST
      port: PRIMARY_DB_PORT

Use as to rename injected variables when your application expects specific names, or when a service connects to multiple databases.


vms

Define VM provisioning specs directly in the JuhJuh File. When you run juhjuh apply, JuhJuh creates or updates these VMs to match.

vms:
  production-app:
    name: my-app-prod
    env_type: production
    cpu: 4
    memory_gb: 16
    disk_gb: 200
    region: ${var.region}

  production-db:
    name: my-app-db
    env_type: production
    cpu: 2
    memory_gb: 8
    disk_gb: 100
    region: ${var.region}

VM fields

Field Required Default Description
name Yes VM instance name (lowercase, hyphens, 2-63 chars)
env_type No development production, staging, development, or testing
cpu No 2 Number of CPU cores
memory_gb No 8 Memory in GB
disk_gb No 200 Total disk in GB
boot_disk_size_gb No 20 Boot disk size in GB (when using two-disk layout)
app_disk_size_gb No 50 Application disk size in GB (when using two-disk layout)
region No Deployment region
deploy_command No Custom deploy command for non-standard setups

If you specify boot_disk_size_gb and app_disk_size_gb, JuhJuh provisions two separate disks: a smaller boot disk for the OS and a larger application disk for your data and containers. If only disk_gb is set, a single disk is used.

VMs defined here are provisioned the same way as VMs created through the dashboard wizard. They follow the same lifecycle (provisioning, staging, running) and appear on the VM dashboard.


vaults

Declare the variables each deployment needs, along with their initial values. Vault declarations let you seed secrets from the JuhJuh File or from your local environment, while actual secret values are stored securely in the Vault.

vaults:
  app-vault:
    variables:
      SECRET_KEY:
        value: ${from_env}
        is_secret: true
        description: Application secret key
      DATABASE_PASSWORD:
        value: ${from_env}
        is_secret: true
        description: Database password
      APP_ENV:
        value: production
        description: Application environment
    files:
      ssl-cert:
        path: /etc/ssl/certs/app.crt
        description: SSL certificate file

Vault variable fields

Field Required Description
value Yes The variable value. Use ${from_env} to read from your local environment at import time
is_secret No Whether the value is encrypted at rest (default: false)
description No Human-readable explanation of the variable

The ${from_env} sentinel

When a vault variable has value: ${from_env}, the CLI reads the value from your current shell environment when you run juhjuh vault import or juhjuh apply. If the environment variable is not set, the entry is skipped with a warning.

This lets you keep actual secrets in your local environment or CI pipeline, never in the config file.

Vault files

Use files to declare file-type vault entries:

files:
  ssl-cert:
    path: /etc/ssl/certs/app.crt
    description: SSL certificate for the application

When you run juhjuh vault import, JuhJuh reads these declarations and creates or updates vault entries on the server. For more on managing vault entries, see Vault.


deployments

Deployments map services and resources to target VMs. Each deployment declares what runs where, which image to use, which vault to attach, and how deployments depend on each other.

deployments:
  production-db:
    vm: production-db
    image: pgvector/pgvector
    tag: pg16
    vault: db-vault
    resources: [postgres]
    network: app-network
    expose:
      - port: 5432
        alias: postgres
        protocol: tcp

  production-app:
    vm: production-app
    image: ${var.app_image}
    tag: latest
    vault: app-vault
    services: [nginx, web, worker-default, worker-analysis, beat, migrate]
    resources: [redis]
    envGroups: [shared-app]
    registry:
      url: registry.example.com
      username: ${REGISTRY_USER}
      password: ${REGISTRY_PASSWORD}
    dependsOn:
      - deployment: production-db
        condition: healthy
    network: app-network

Deployment fields

Field Required Description
vm Yes Target VM name (must match a VM in the vms section or an existing VM)
image No Container image to deploy
tag No Image tag (default: latest)
vault No Vault name to attach for secret injection
services No Service names to include in this deployment
resources No Resource names to include in this deployment
envGroups No Environment groups to attach to this deployment
registry No Private registry credentials (url, username, password)
dependsOn No Other deployments this one depends on
expose No Ports exposed on the organization network
network No Network name for cross-deployment communication
environment No Logical environment name (staging, production, dev) — must match a key under the top-level environments: block. Do not use a deployment or application name here.
files No Local files to sync to the VM at deploy time

Deployment dependencies

Deployments can depend on other deployments. JuhJuh waits for the dependency to reach the specified condition before proceeding.

dependsOn:
  - deployment: production-db
    condition: healthy

Available conditions: healthy (default), started, completed.

JuhJuh validates the dependency graph at parse time. Circular dependencies are rejected. Deployments within the same dependency level run in parallel; levels execute sequentially.

Exposed ports

Expose services on the organization network so other deployments can reach them.

expose:
  - port: 5432
    alias: postgres
    protocol: tcp
  - port: 6379
    alias: redis

Aliases must be unique within a deployment. Other deployments reference exposed ports through deployment references.

Registry authentication

For private container registries, provide credentials:

registry:
  url: registry.example.com
  username: ${REGISTRY_USER}
  password: ${REGISTRY_PASSWORD}

Credentials support runtime variable substitution. When using vault-backed credentials, make sure the deployment references a vault that contains those keys.

Scoping services and resources

The services and resources lists scope what gets included in the generated orchestration config for this deployment. If you have 10 services defined globally but a VM only runs 3 of them, list those 3 in the deployment.

deployments:
  production-app:
    vm: production-app
    services: [nginx, web, worker-default]
    resources: [redis]

  production-db:
    vm: production-db
    resources: [postgres]

When scoping is used, only the listed services and resources are included in the orchestration output, along with their file mappings and firewall rules. If no scoping is set, all services and resources are included.

File sync

Sync local files to the target VM at deploy time:

deployments:
  production-app:
    vm: production-app
    files:
      - source: ./nginx/nginx.conf
        target: /etc/nginx/nginx.conf
        readonly: true
        description: Nginx configuration
Field Required Description
source Yes Local file path (relative to the config file)
target Yes Destination path on the VM
readonly No Mount as read-only (default: true)
description No Human-readable explanation

JuhJuh also collects files automatically from resource config_files, init_script paths, and bind-mount volumes in templates and services.


firewall

Root-level firewall rules control which ports are opened on the VM. Use access to declare whether a port faces the public internet or stays within your private network.

firewall:
  - port: 80
    label: HTTP
    access: public
    description: Web traffic
  - port: 443
    label: HTTPS
    access: public
    description: Secure web traffic
  - port: 5432
    label: PostgreSQL
    access: internal
    description: Database, VPC only

Public rules (access: public, the default) appear in the Internet zone of the infrastructure diagram with connection lines from the internet to your proxy or service. Internal rules (access: internal) stay within the VPC and do not draw internet connections.

The label field is a short display name (e.g. HTTP, HTTPS, PostgreSQL) shown in diagrams and detail panels. If label is not set, description is used as the display name.

See Firewall rules under resources for the full field reference.


networks

Define organization-level networks for cross-deployment communication.

networks:
  app-network:
    name: my-app-network
    scope: organization
    driver: bridge
    subnet: auto
Field Description
name Network name
scope Network scope (default: organization)
driver Network driver (default: bridge)
subnet Subnet allocation (default: auto)

Deployments on the same network can communicate with each other. Use expose in deployments to make specific ports reachable across the network.


hooks

Lifecycle commands that run at specific events.

hooks:
  pre_deploy:
    - /app/scripts/migrate.sh
    - /app/scripts/collect-assets.sh
  post_deploy:
    - /app/scripts/warm-cache.sh

Deployment references

When deployments depend on each other, you can reference runtime state from one deployment inside another. This lets you wire services across VMs without hardcoding IP addresses or ports.

The syntax is ${deployment.<name>.<attribute>}.

envGroups:
  shared-app:
    clear:
      POSTGRES_HOST: ${deployment.production-db.host}
      POSTGRES_PORT: ${deployment.production-db.port.postgres}
      POSTGRES_ENDPOINT: ${deployment.production-db.endpoint.postgres}

Available attributes

Attribute Returns Example
host IP address of the deployment's VM 192.0.2.5
status Current deployment status healthy
network Network name assigned to the deployment app-network
port.<alias> Port number for an exposed service 5432
endpoint.<alias> Full host:port string for an exposed service 192.0.2.5:5432

Rules

  1. You can only reference deployments listed in the referencing deployment's dependsOn. JuhJuh validates this at parse time and rejects references to deployments not declared as dependencies
  2. Port and endpoint references require the target deployment to expose that alias
  3. Deployment references are resolved at deploy time from live infrastructure state, not at parse time
  4. If a referenced deployment is not yet provisioned or has no host assigned, the deploy fails with an error

Three variable resolution phases

JuhJuh resolves variables in three phases, each with different syntax:

Phase Syntax Resolved when Example
1. Config variables ${var.name} After file merge, before validation ${var.app_image}
2. Deployment references ${deployment.name.attr} At deploy time, from live state ${deployment.prod-db.host}
3. Runtime variables ${ENV_VAR:-default} At container start, by the runtime ${DEBUG:-False}

Phase 1 variables are fully resolved before Phase 2 runs. Phase 3 variables are passed through untouched and resolved by the container runtime.


Multi-file configuration

For larger projects, split your JuhJuh File into a .juhjuh/ directory with multiple YAML files. JuhJuh loads all .yaml and .yml files in the directory, sorts them alphabetically, and deep-merges them into a single config.

.juhjuh/
  00-variables.yaml
  01-base.yaml
  02-resources.yaml
  03-env-groups.yaml
  04-templates.yaml
  05-services.yaml
  06-environments.yaml
  07-vms.yaml
  08-deployments.yaml

Why split files?

A single juhjuh file works perfectly for small-to-medium projects. Split files help when:

  • Your config exceeds a few hundred lines
  • Multiple team members edit different parts of the stack
  • You want clear separation between services, resources, and deployment targets
  • You want to review infrastructure changes in smaller, focused diffs

Naming convention

Prefix files with numbers to control merge order. Lower numbers are loaded first. Later files override earlier files for scalar and list values. Dicts merge recursively.

Example: variables file

# .juhjuh/00-variables.yaml
variables:
  project_name:
    type: string
    default: my-saas-app

  app_image:
    type: string
    default: registry.example.com/my-app

  app_cpu:
    type: integer
    default: 4

  app_memory_gb:
    type: integer
    default: 16

  db_cpu:
    type: integer
    default: 2

  db_memory_gb:
    type: integer
    default: 8

  region:
    type: string
    default: your-region

Example: base config

# .juhjuh/01-base.yaml
name: ${var.project_name}
version: "1"

config:
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"
  volumes:
    postgres_data:
      name: ${var.project_name}-postgres-data
    redis_data:
      name: ${var.project_name}-redis-data
    static_files:
      name: ${var.project_name}-static

Example: deployments file

# .juhjuh/08-deployments.yaml
deployments:
  production-db:
    vm: production-db
    image: pgvector/pgvector
    tag: pg16
    vault: db-vault
    resources: [postgres]
    network: app-network
    expose:
      - port: 5432
        alias: postgres

  production-app:
    vm: production-app
    image: ${var.app_image}
    tag: latest
    vault: app-vault
    services: [nginx, web, worker-default, beat, migrate]
    resources: [redis]
    envGroups: [shared-app]
    dependsOn:
      - deployment: production-db
        condition: healthy
    network: app-network

networks:
  app-network:
    scope: organization
    driver: bridge
    subnet: auto

Merge rules

Value type Merge behavior
Scalar (string, number, boolean) Later file wins
List Later file replaces the entire list
Dict Keys merge recursively. Nested scalars/lists follow the rules above

How JuhJuh uses the file

When you deploy through JuhJuh (dashboard or CLI), the platform:

  1. Discovers the JuhJuh File in your repository
  2. Merges all files (if using .juhjuh/ directory)
  3. Resolves config variables (${var.name})
  4. Validates the merged config against the schema
  5. Resolves the target environment (applies overrides and inheritance)
  6. Generates orchestration configuration from the resolved config
  7. Resolves deployment references (${deployment.name.attr}) from live state
  8. Provisions resources and syncs files to the target VM
  9. Injects vault secrets as environment variables
  10. Deploys the services
graph LR
    A[JuhJuh File] --> B[Merge & Resolve Variables]
    B --> C[Validate Schema]
    C --> D[Resolve Environment]
    D --> E[Generate Config]
    E --> F[Resolve Deployment Refs]
    F --> G[Provision & Deploy]

Validation

JuhJuh validates your config in 8 phases: YAML syntax, variable definitions, variable completeness, variable resolution, schema validation, cross-reference checks (VM names, resource/service names, image/registry requirements, env group references), optional online vault verification, and local file existence. Run juhjuh validate to check your config without deploying. Use --strict to treat warnings as errors.

Converting from existing orchestration

Already have container orchestration configured? JuhJuh can work with raw orchestration files if no JuhJuh File is present. Migration is optional and incremental. You can start by adding a minimal JuhJuh File alongside your existing config and expand it over time.


CLI workflow

The JuhJuh CLI reads the same file and drives the full provisioning and deployment cycle from your terminal.

# Validate your config
juhjuh validate

# Preview what would change
juhjuh apply --dry-run

# Provision VMs, create vaults, and deploy
juhjuh apply

# Deploy a specific target
juhjuh deploy create production-app --tag v2.1.0

# Import vault declarations
juhjuh vault import production-app

# Check deployment status
juhjuh deploy list

The CLI auto-discovers the JuhJuh File in the current directory. Pass --file to point to a specific file or directory. See the CLI reference for the full command list.


Complete single-file example

name: my-saas-app
version: "1"

variables:
  app_image:
    type: string
    default: registry.example.com/my-app
  worker_memory:
    type: string
    default: 512M

config:
  organization: my-team
  network:
    name: app-network
    driver: bridge
  volumes:
    postgres_data:
      name: app-postgres-data
    redis_data:
      name: app-redis-data
    static_files:
      name: app-static

resources:
  postgres:
    type: postgres
    version: "16"
    database: my_app
    persistence:
      volume: postgres_data
      path: /var/lib/postgresql/data

  redis:
    type: redis
    version: "7"
    config:
      maxmemory: 128mb
      maxmemory_policy: allkeys-lru

envGroups:
  shared:
    clear:
      POSTGRES_HOST: ${deployment.production-db.host}
      POSTGRES_PORT: ${deployment.production-db.port.postgres}
      REDIS_URL: redis://redis:6379/0
      DEBUG: ${DEBUG:-False}
    secret:
      - POSTGRES_PASSWORD
      - SECRET_KEY
      - STRIPE_SECRET_KEY

templates:
  app:
    image: ${var.app_image}:${IMAGE_TAG:-latest}
    links: [redis]
    envFrom: [shared]
    restart: unless-stopped
    volumes:
      - static_files:/app/staticfiles

services:
  nginx:
    type: proxy
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    upstream:
      target: web
    dependsOn:
      web:
        condition: service_started

  web:
    type: web
    extends: app
    port: 8000
    command: /app/scripts/start-web.sh
    healthCheck:
      path: /health/
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      strategy: rolling
      preCommand: /app/scripts/migrate.sh

  worker-default:
    type: worker
    extends: app
    command: /app/scripts/start-worker.sh
    compute:
      memory: ${var.worker_memory}
      cpu: 0.50
    worker:
      queue: default
      concurrency: 4

  beat:
    type: cron
    extends: app
    command: /app/scripts/start-beat.sh
    replicas: 1

  migrate:
    type: job
    extends: app
    command: /app/scripts/migrate.sh
    restart: never

environments:
  production:
    resources:
      postgres:
        plan: premium
    services:
      worker-default:
        compute:
          memory: 1024M
          cpu: 1.0

  staging:
    inherit: production
    services:
      worker-default:
        compute:
          memory: 384M

vms:
  production-app:
    name: my-app-prod
    env_type: production
    cpu: 4
    memory_gb: 16
    disk_gb: 200

  production-db:
    name: my-app-db
    env_type: production
    cpu: 2
    memory_gb: 8
    disk_gb: 100

deployments:
  production-db:
    vm: production-db
    image: pgvector/pgvector
    tag: pg16
    vault: db-vault
    resources: [postgres]
    expose:
      - port: 5432
        alias: postgres
    network: app-network

  production-app:
    vm: production-app
    image: ${var.app_image}
    tag: latest
    vault: app-vault
    services: [nginx, web, worker-default, beat, migrate]
    resources: [redis]
    envGroups: [shared]
    dependsOn:
      - deployment: production-db
        condition: healthy
    network: app-network

networks:
  app-network:
    scope: organization
    driver: bridge
    subnet: auto
  • CLI: the command-line tool that reads, validates, and applies your JuhJuh File
  • Infrastructure Overview: how the JuhJuh File fits into the broader infrastructure model
  • VMs: the compute instances provisioned by your file
  • Deployments: what happens when JuhJuh applies your file
  • Vault: where secret variables defined in envGroups are stored
  • Resources: external services your file's resources map to
  • Billing: understand costs for VMs and infrastructure provisioned by your file