Best Practices
This guide collects practical recommendations for using Dockform v0.8 safely and effectively across development, staging, and production environments.
Core Principles
- Treat the manifest file as the source of truth; avoid imperative Docker commands
- Use a clear, stable
identifier(e.g.,myapp,server-name) - Leverage automatic discovery—structure directories, minimize manifest config
- Prefer small, focused changes and review with
dockform planbeforeapply
Project Structure
Follow the discovery convention for automatic stack detection:
my-project/
├── dockform.yml
├── default/ # Context name
│ ├── traefik/ # Stack: default/traefik
│ │ ├── compose.yaml
│ │ └── volumes/
│ │ └── config/
│ ├── web/ # Stack: default/web
│ │ ├── compose.yaml
│ │ ├── environment.env
│ │ └── secrets.env
│ └── db/
│ └── compose.yaml
└── production/ # Another context
└── web/
└── compose.yaml
Recommendations:
- Name context directories to match Docker context names
- Keep one Compose file per stack (use profiles for variants)
- Store filesets in
volumes/subdirectories within each stack - Use
environment.envandsecrets.envfor auto-discovery
Contexts and Multi-Host
- Define one context per Docker daemon (local, staging, production)
- Use Docker contexts for remote daemons via SSH
- Keep context-specific resources (volumes, networks) under that context
contexts:
local:
volumes:
dev-data: {}
staging:
volumes:
staging-data: {}
production:
volumes:
prod-data: {}
networks:
traefik: {}
Creating remote contexts
Environments
Dockform doesn't have an "environment" concept of its own. What you have instead are contexts (one per Docker daemon) and interpolation (shell variables expanded into the manifest at load time). Those two primitives cover the usual dev staging prod split, but how you wire them up changes the feel of the repo.
There are two patterns that work. Pick the one that matches how you actually run the tool.
Pattern A: one context per environment
Each environment is a separate context. Dockform's auto-discovery already scopes stacks and env files by context directory, so you get per-environment isolation for free.
identifier: homelab
contexts:
development:
host: ssh://dev-machine
production:
host: ssh://prod-machine
development/web/compose.yaml
development/web/environment.env
development/web/secrets.env
production/web/compose.yaml # symlink → ../../development/web/compose.yaml
production/web/environment.env
production/web/secrets.env
The symlink keeps compose.yaml as a single source of truth. If symlinks are awkward in your setup, move the compose file to a shared directory and point both stacks at it with an explicit stacks: block:
Good when. You want to plan or apply against any environment from any shell. You run CI that deploys to multiple hosts. You want dockform plan --context production to work without pre-setup.
Pattern B: one context, shell picks the environment
You are almost always working against a single environment at a time: on your laptop you target dev, on the server you target prod. A shell helper like direnv switches which values are exported, and the manifest reads them with ${VAR}.
dotenv environment.env # sets ENVIRONMENT=development
dotenv ${ENVIRONMENT}.env # loads development.env or production.env
identifier: homelab
contexts:
server:
host: ${CONTEXT_SERVER_HOST}
discovery:
environment_file: ${ENVIRONMENT}.env
secrets_file: ${ENVIRONMENT}.secrets.env
server/web/compose.yaml
server/web/development.env
server/web/development.secrets.env
server/web/production.env
server/web/production.secrets.env
Good when. You only ever deploy one environment from a given shell. The compose files live in one place, no symlinks.
Watch out for. Running dockform plan in a shell where direnv didn't load will fail with a confusing interpolation error. You cannot diff dev against prod in the same session. CI jobs need to export the right variables before calling Dockform.
Volumes
- Declare named volumes under
contexts.<name>.volumes: - Reference them as
external: truein Compose files - Avoid defining named volumes directly in Compose—let Dockform manage them
- Regularly backup volumes;
destroyandprunewill remove labeled volumes
Networks
- Declare networks under
contexts.<name>.networks: - Reference them as
external: truein Compose files - Use environment-specific network names for strict isolation
- Dockform detects drift and recreates networks when configuration changes
Filesets
- Use the
volumes/directory convention for auto-discovery - Keep filesets focused—configs, static assets, seeds (not large data)
- Use
.dockform-excludefiles to skip build artifacts, VCS metadata, OS files - Set ownership via
.dockform-ownership.yamlwhen needed
default/web/volumes/
├── config/
│ ├── .dockform-exclude
│ └── nginx.conf
└── static/
├── index.html
└── styles.css
Example exclude file:
Stacks and Discovery
- Let discovery find your stacks—minimize explicit
stacks:entries - Use
stacks:only for augmentation: profiles, extra env, secrets, project name - Set a stable
project.namefor predictable container naming
Environment and Secrets
- Use
environment.envin stack directories for auto-discovery - Add stack-specific overrides via
stacks.<key>.environment.inline - Keep secrets in
secrets.env(SOPS-encrypted) for auto-discovery - Never commit unencrypted secrets; always use SOPS
- Use
dockform doctorto verify SOPS and key configuration - For CI/CD, inject key files via secrets management
Deployments
Use deployment groups for targeting specific environments:
deployments:
dev:
description: Local development
contexts: [local]
staging:
description: Staging environment
contexts: [staging]
production:
description: Production services
stacks:
- production/web
- production/api
- production/traefik
CI/CD Recommendations
- Use
dockform planin PRs to preview changes; require approval beforeapply - Pin the Docker context and set a clear identifier per environment
- Provide required env vars (including SOPS keys) via CI secrets
- Run
dockform validateas a pre-check step
jobs:
deploy:
steps:
- uses: actions/checkout@v4
- name: Validate manifest
run: dockform validate
- name: Plan changes
run: dockform plan --deployment ${{ github.event.inputs.environment }}
- name: Apply changes
run: dockform apply --deployment ${{ github.event.inputs.environment }} --skip-confirmation
Safety and Destructive Operations
Danger
destroy removes all labeled resources for the active identifier (containers, networks, volumes). Use with care and ensure backups exist.
- Always run
dockform planbeforeapplyto review changes - Keep recent backups for stateful volumes before destructive commands
- Consider using docker-volume-backup for automated backups
Performance
- Keep fileset sizes reasonable; use
.dockform-excludeaggressively - Minimize changes per deployment for faster diffs
- Use stable project and resource names to reduce churn and restarts
Troubleshooting
| Issue | Solution |
|---|---|
| Compose fails | Run docker compose -f <file> config to validate YAML |
| Service doesn't update | Check labels and config-hash drift with dockform plan |
| Volume/network missing in Compose | Ensure declared in manifest and referenced as external |
| Stack not discovered | Verify directory structure matches <context>/<stack>/compose.yaml |
| Secrets not decrypted | Run dockform doctor to check SOPS configuration |
Quick Reference
| Command | Purpose |
|---|---|
dockform plan |
Preview changes before applying |
dockform apply |
Apply changes to infrastructure |
dockform destroy |
Remove all managed resources |
dockform validate |
Validate manifest syntax |
dockform doctor |
Check environment and dependencies |
dockform dashboard |
Interactive TUI for monitoring |