Docker Swarm, made simple. Dev-first deployments, production overlays, built-in audit trail.
dargstack is a CLI tool and project structure specification that reduces Docker Swarm complexity to a minimal command set. Define your development setup as the base, express production as incremental changes on top.
dargstack does not replace docker stack. You can interact with docker stack on the same stack that you manage with dargstack.
The following projects successfully employ dargstack in production:
Migrating from v3? See MIGRATION.md. Contributing? See CONTRIBUTING.md.
Deploying the same app to development and production with Docker Swarm usually means maintaining two nearly identical compose files. Change one thing? Manually copy, edit, hope nothing breaks.
dargstack inverts this: define development as the source of truth, then express production as changes on top. One deploy command. One audit trail. Done.
| dargstack | docker stack |
|---|---|
| β A single resource and diff specification | β Two compose files for dev and prod β risk of configuration drift |
| β Clear file separation by service | β Monolithic compose file β hard to maintain if big |
| β Snapshot for every deploy; easy inspect and diff | β Volatile audit trail β live console tracing only |
| β Safer secret management with auto-generation and templating | β Manual secret management β tedious, often insecure defaults |
| β Development certificates auto-generated | β No TLS certificates β out of scope, traffic unencrypted |
| β Zero downtime service update motivation | β Stop-first update order by default β unreliable availability in production |
ARCHIVE="dargstack_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/').tar.gz"
curl -sfL -o "$ARCHIVE" "https://github.com/dargstack/dargstack/releases/latest/download/$ARCHIVE"
curl -sfL https://github.com/dargstack/dargstack/releases/latest/download/checksums.txt | sha256sum -c - --ignore-missing
tar xzf "$ARCHIVE" && rm "$ARCHIVE"
sudo mv dargstack /usr/local/bin/Prerequisite β Go installed, see go.dev: Download and install.
go install github.com/dargstack/dargstack/v4/cmd/dargstack@latestPackage integrity is enforced by the Go module proxy and the module's go.sum lockfile.
Pin to a specific version (e.g., @v4.1.0) for a reproducible, auditable install.
Prerequisite β Docker installed, see docs.docker.com: Install Docker Engine.
-
Initialize a new dargstack project:
dargstack initialize
-
Fill in your service configuration according to the docker.com: Compose file reference.
-
Deploy:
cd <project_name> dargstack deploy
Done! π Your stack is live.
Suppose you have an api service as part of an example project:
example/
βββ api/ # The service's source code
β βββ Dockerfile # Dockerfile for the api service
β βββ ...
βββ stack/
β βββ artifacts/ # Generated files
β β βββ audit-log/ # Deployment snapshots (gitignored)
β β βββ certificates/ # Local TLS certificates (gitignored)
β β βββ docs/
β β β βββ README.md # Generated stack documentation
β β βββ .gitignore
β β βββ README.md # Explains artifacts folder contents
β βββ src/
β β βββ development/
β β β βββ api/
β β β β βββ compose.yaml # Full Docker Compose document
β β β β βββ configuration.toml # File-based volume mount
β β β β βββ key.secret # Secret used by the service
β β β β βββ ...
β β β βββ .env # Environment variables
β β βββ production/
β β βββ api/
β β β βββ compose.yaml # YAML deep-merge override
β β β βββ configuration.toml # File-based override
β β β βββ ...
β β βββ .env # Key-based override
β βββ dargstack.yaml # Project configuration
βββ ...
Each service file is a full Docker Compose document β files are deep-merged by spruce. See github.com: What are all the Spruce operators? for special keywords controlling merge behavior.
# src/development/api/compose.yaml
services:
api:
image: api:latest
ports:
- "3000:3000"
secrets:
- api-key
deploy:
labels:
- (( append ))
- dargstack.development.build=../../../../api
- traefik.http.routers.api.rule=Host(`api.${STACK_DOMAIN}`)
- traefik.http.routers.api.tls=true
- traefik.http.services.api.loadbalancer.server.port=8080
- some.label=for-development # dargstack:dev-only
user: (( prune ))
secrets:
api-key:
file: ./key.secret
x-dargstack:
secrets:
api-key:
length: 32
special_characters: falseKey rules:
- Each service file has a
services:top-level key - Secrets, volumes, networks, and configs are declared in the same file as the service that uses them
- Production files contain only differences from development:
# src/production/api/compose.yaml
services:
api:
image: ghcr.io/myorg/api:v1.0.0
deploy:
replicas: 3
update_config:
parallelism: 1
order: start-first # Zero-downtime (use stop-first for stateful services)The dargstack.development.git.ssh and dargstack.development.git.https labels instruct dargstack to clone a git repository before building a service's Docker image. The repository is cloned to a sibling directory of the stack, named after the repository:
services:
webapp:
deploy:
labels:
dargstack.development.git.ssh: "git@github.com:mystack/webapp.git"
image: mystack/webapp:developmentThis clones webapp.git to a webapp/ directory next to the stack directory, and automatically sets the build context to that directory. You can override the build context with dargstack.development.build to point to a subdirectory:
services:
webapp:
deploy:
labels:
dargstack.development.git.ssh: "git@github.com:mystack/webapp.git"
dargstack.development.git.https: "https://github.com/mystack/webapp.git"
dargstack.development.build: "../../../../repository/packages/frontend"
image: mystack/webapp:developmentThe SSH URL is used as the primary clone URL, with the HTTPS URL as a fallback. Providing both ensures cloning works regardless of network restrictions. The repository is cloned once (on first deploy) and left untouched on subsequent deploys.
metadata:
compatibility: ">=4.0.0 <5.0.0" # required, string (semver range)
name: my-stack # optional, defaults to parent directory name
source: # optional
name: my-repo
url: https://github.com/org/repo
external_services: # optional
status:
description: Service status dashboard
runtime:
sudo: auto # optional, `auto` | `always` | `never`
build:
mode: always # optional, `always` | `missing`
deploy:
volumes:
prompt: true # optional, prompt to remove volumes on first deploy
environment:
development:
domain: app.localhost # optional, defaults to "app.localhost"
certificate:
include: [] # optional, domains added to TLS cert
exclude: [] # optional, domains removed from TLS cert
production:
branch: main # optional, defaults to "main"
domain: app.localhost # optional, defaults to "app.localhost"
tag: latest # optional, `latest` | stringDeploy named groups of services to save resources:
# src/development/adminer/compose.yaml
services:
adminer:
image: adminer
deploy:
labels:
dargstack.profiles: db # Only deployed with --profiles dbMultiple profiles: dargstack.profiles: "db,monitoring" (comma-separated).
Profiles can also be activated via the COMPOSE_PROFILES environment variable (comma-separated). The --profiles flag takes precedence over COMPOSE_PROFILES.
If no profile selection is made and any service declares default, only services with dargstack.profiles: default are deployed; unlabeled services are excluded.
If no profile selection is made and no service declares default, all services (including unlabeled) are deployed.
When one or more profiles are explicitly activated with --profiles, only services whose dargstack.profiles intersect the active profile set are deployed. Services without a dargstack.profiles label are deployed only if the special unlabeled profile is explicitly activated (for example: --profiles unlabeled or --profiles db --profiles unlabeled).
Define secrets with generation settings and template resolution:
x-dargstack:
secrets:
postgres-password:
type: random_string
length: 32
special_characters: true
jwt-signing-key.secret:
type: private_key
key_type: ed25519
external-api-token:
type: third_party
hint: "Get yours at https://example.com/settings/tokens"
dev-only-secret:
type: insecure_default
insecure_default: "CHANGE_ME"
api-db_url:
type: template
template: "postgresql://postgres:{{secret:postgres-password}}@postgres:5432/app"type controls secret behavior. Supported values: random_string, wordlist_word, private_key, third_party, insecure_default, template. If omitted, the type is inferred from the fields provided:
private_keyifkey_typeorkey_sizeis setthird_partyifthird_partyis settemplateiftemplateis setinsecure_defaultifinsecure_defaultis setrandom_stringiflengthorspecial_charactersis set
random_string properties:
lengthβ Random string length (default:32)special_charactersβ Include special characters (default:true; setfalseto opt out)
private_key properties:
key_typeβ Key algorithm:ed25519(default),rsa,ecdsakey_sizeβ Key size: RSA default2048; ECDSA256(P-256),384(P-384),521(P-521)
third_party properties:
hintβ Human-readable hint for expected value (shown when the secret is unset)
insecure_default properties:
insecure_defaultβ Default value used for the secret
template properties:
templateβ Template string, supporting the following tokens:{{secret:<name>}}(or legacy{{<name>}}) β Reference another secret{{random_string}},{{random_string:<length>}},{{random_string:<length>:<special>}}β Inline random generation{{wordlist_word}}β Inline word generation{{private_key}}β Inline private key generation
.env files use KEY=VALUE format. During deploy, missing values are prompted. Production blocks on missing values.
dargstack is linux-first. When running on macOS, some compose configurations are invalid or undesirable: volume mounts referencing Linux-only paths (/dev/disk/by-id/, /dev/kmsg), privileged: true where not needed, or services like cadvisor that depend on Linux kernel features.
Platform overrides live under x-dargstack.platform.<os> and use spruce operators to modify the base configuration. Supported platforms: darwin, linux, windows.
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.50.0
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/by-id/:/dev/disk/by-id:ro
devices:
- /dev/kmsg
privileged: true
x-dargstack:
platform:
darwin:
services:
cadvisor:
volumes:
- (( prune ))
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
devices:
- (( prune ))
privileged: falseMultiple platforms can coexist in one file. The active platform is auto-detected via runtime.GOOS, or overridden with --platform darwin.
| Command | Description |
|---|---|
| dargstack audit | View deployment audit log |
| dargstack build | Build development Dockerfiles |
| dargstack certify | Generate TLS certificates |
| dargstack clone | Clone an existing dargstack project |
| dargstack deploy | Deploy the stack |
| dargstack document | Generate the stack documentation |
| dargstack initialize | Bootstrap a new dargstack project |
| dargstack profiles | List discovered deploy profiles |
| dargstack remove | Remove the deployed stack |
| dargstack secret | Manage stack secrets |
| dargstack update | Update dargstack to the latest version |
| dargstack validate | Validate stack resources |
See docs/dargstack.md for global flags and detailed command documentation.
Licensed under GPLv3.