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:

```yaml 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.

```yaml 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

yaml 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.

yaml 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.

```yaml 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.

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

Persistence options

yaml 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.

yaml 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.

yaml 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:

yaml 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.

```yaml 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.

```yaml 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

yaml 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

yaml 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

yaml 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

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

Auto-scaling

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

Auto-stop

yaml autoStop: enabled: true idleTimeout: 15m

Useful for preview and development environments to reduce costs.

Dependencies

yaml 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

yaml 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.

yaml 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.

```yaml 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:

yaml 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:

yaml 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.

yaml links: [postgres, redis]

yaml 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.

```yaml 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.

yaml 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:

yaml 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.

```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 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.

yaml 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.

yaml 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:

yaml 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.

```yaml 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:

yaml 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.

yaml 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.

yaml 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.

yaml 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>}.

yaml 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

```yaml

.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

```yaml

.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

```yaml

.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.

```bash

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

```yaml 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