Configuration
Complete YAML configuration reference — hosts, repos, projects, triggers, and path matching.
Configuration is a single YAML file, by default at /etc/deeplo/config.yml. The default path can be overridden with deeplo check --config or the DEEPLO_CONFIG_FILE env var.
Defaults prefer .yml, but explicit .yaml paths and compose file names still work when you configure them directly.
The YAML file contains only structural deployment config: which hosts exist, which repos to track, and which projects to deploy. Runtime settings (SSH keys, data directory, listen addresses, GitHub tokens, secrets) are configured exclusively via environment variables — see Bootstrap config.
Validate any config before deploying:
deeplo check --config /path/to/config.ymlTop-level structure
version: 1
hosts: [...]
repos: [...]
projects: [...]| Field | Type | Required | Description |
|---|---|---|---|
version | int | yes | Config schema version. Must be 1. |
hosts | list | yes | One or more remote Docker hosts. |
repos | list | yes | One or more git repositories to watch. |
projects | list | yes | One or more Compose projects to deploy. |
hosts
Each entry is a remote host where Docker Compose stacks run.
hosts:
- name: web-1
address: 10.0.0.10
remote_base_dir: /srv/apps
- name: web-2
address: 10.0.0.11
remote_base_dir: /srv/apps
user: deploy # optional per-host override
port: 2222 # optional per-host override
compose_bin: docker-compose # optional override| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Unique identifier. Referenced by projects[].targets. |
address | string | yes | — | Hostname or IP address. |
remote_base_dir | string | yes | — | Base directory on the host where project directories are created. |
user | string | no | DEEPLO_SSH_USER | SSH username override for this host. |
port | int | no | DEEPLO_SSH_PORT | SSH port override for this host. |
compose_bin | string | no | docker | docker uses docker compose; docker-compose uses the v1 standalone binary. |
repos
Each entry is a git repository to watch. Projects reference repos by name.
repos:
- name: infra
url: git@github.com:yourorg/infra.git
branch: main
trigger_mode: hybrid
poll_interval: 60s
- name: myapp
url: https://github.com/yourorg/myapp.git
# branch defaults to "main"
# trigger_mode defaults to "webhook"| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Unique identifier. Referenced by projects[].repo. |
url | string | yes | — | Git clone URL (HTTPS or SSH). |
branch | string | no | main | Branch to watch. |
trigger_mode | string | no | webhook | webhook, poll, or hybrid. See Triggers. |
poll_interval | duration | no | 60s | How often to poll. Only used when mode is poll or hybrid. |
projects
Each entry is a Docker Compose project within a repo.
projects:
- name: paperless
repo: infra
repo_subdir: apps/paperless
targets:
- web-1
# compose_files defaults to [compose.yml]
# watch_paths defaults to [apps/paperless/**]
- name: nginx
repo: infra
repo_subdir: apps/nginx
compose_files:
- compose.yml
targets:
- web-1
- web-2
watch_paths:
- apps/nginx/**
env_files:
- .env
remote_dir_name: nginx # optional; defaults to project name| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Unique identifier. Used as the remote directory name by default. |
repo | string | yes | — | Name of the repo this project belongs to. |
repo_subdir | string | yes | — | Path within the repo where compose files live (relative to repo root). |
compose_files | list of strings | no | [compose.yml] | Compose file names, relative to repo_subdir. |
targets | list of strings | yes | — | Host names to deploy to (from the hosts list). |
watch_paths | list of strings | no | ["{repo_subdir}/**"] | Glob patterns against changed file paths. A deploy fires only if at least one changed file matches. |
env_files | list of strings | no | — | .env file names, relative to repo_subdir. Included in the deploy bundle alongside compose files. |
remote_dir_name | string | no | name | Directory name under host.remote_base_dir on the target. |
Path matching
watch_paths patterns are matched against the full paths of changed files relative to the repo root.
*matches any characters within a single path segment**matches zero or more path segments- Standard glob characters (
?,[range]) work within a segment
watch_paths:
- apps/myapp/** # any file under apps/myapp/
- shared/lib/** # also redeploy if shared library changes
- config/*.yml # yml files directly in config/If watch_paths is empty, the project deploys on every push to the repo regardless of which files changed.
If the diff is unavailable (no local mirror yet, or polling without a mirror), the project deploys unconditionally — the unknown diff is treated as "everything changed".
Invalid watch_paths patterns are rejected by deeplo check. Malformed glob syntax does not fall back silently at runtime.
Defaults summary
| Field | Default |
|---|---|
hosts[].compose_bin | docker |
repos[].branch | main |
repos[].trigger_mode | webhook |
repos[].poll_interval | 60s |
projects[].compose_files | [compose.yml] |
projects[].watch_paths | ["{repo_subdir}/**"] |
projects[].remote_dir_name | project name |
Example: monorepo with multiple projects
All projects live in one repo. Each has its own watch_paths so only the affected project deploys on a given push.
version: 1
hosts:
- name: vm-1
address: 10.0.0.10
remote_base_dir: /srv/apps
- name: vm-2
address: 10.0.0.11
remote_base_dir: /srv/apps
repos:
- name: infra
url: git@github.com:yourorg/infra.git
trigger_mode: hybrid
poll_interval: 5m
projects:
- name: api
repo: infra
repo_subdir: services/api
targets: [vm-1, vm-2]
watch_paths:
- services/api/**
- shared/** # shared code also triggers api
- name: worker
repo: infra
repo_subdir: services/worker
targets: [vm-1]
- name: nginx
repo: infra
repo_subdir: services/nginx
targets: [vm-1, vm-2]Env var interpolation
You can use ${VAR} and ${VAR:-default} in YAML values. This is expanded before parsing:
repos:
- name: myapp
url: ${GIT_REPO_URL}
branch: ${DEPLOY_BRANCH:-main}Best practices
- Mount secrets as files and pass their paths via
DEEPLO_SSH_KEY_FILE,DEEPLO_GITHUB_WEBHOOK_SECRET_FILE, etc. No secrets belong in the YAML config. - Use explicit
watch_pathsin monorepos. Without them, every push to the repo deploys every project. - Keep
remote_base_dirconsistent across hosts. It simplifies configuration and debugging. - Prefer hybrid mode for repos you also watch via webhook. It catches missed webhooks without polling aggressively.
- Use a dedicated deploy user on each host. Grant it access to the
dockergroup, nothing more.