Dogma bridges secrets from vault backends and infrastructure outputs into sops-encrypted files, then deploys them to your machines. It also comes with a set of tools — pre-credentialed shells, environment variable exports, and more — to make the whole workflow seamless.
Crate:
dogma-rust— Binary:dogma
# dogma.yml
name: myproject
env: [dev, prod]
admin:
- gpg: 3246EC6F403D8F3403E34A90BCDABC277B1ED8AB
vault:
hcloud-token:
pass: myproject/{env}/hcloud-token
stripe-webhook-secret:
pass: myproject/{env}/stripe-webhook-secret
infra:
credentials:
TF_VAR_hcloud_token:
from: vault
ref: hcloud-token
machines:
backend:
hostname: myproject-{env}
ip: { from: infra, unit: hetzner, output: server_ip }
secrets: [backend]
secrets:
backend:
stripe_webhook_secret: { from: vault, ref: stripe-webhook-secret }
server_ip: { from: infra, unit: hetzner, output: server_ip }$ dogma deploy prod --new
dogma: checking dependencies...
dogma: normalizing config...
dogma: refreshing infra cache (hetzner)...
dogma: generating .sops.yaml from SSH host keys...
dogma: encrypting secrets for myproject-prod...
dogma: deploying backend (1.2.3.4)...
→ nixos-rebuild switch --target-host root@1.2.3.4 --flake .#myproject-prod
dogma: tagging deploy/v26.06.0001
dogma: done in 142s
$ dogma infra apply prod hetzner
dogma: normalizing config...
dogma: resolving credentials...
dogma: tofu init...
dogma: tofu apply...
hcloud_server.backend: Creating...
hcloud_server.backend: Creation complete (id=12345678)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ dogma shell prod
dogma: entering prod shell (exit to return)
[dogma-prod 14:23:01] ~/myproject $
$ dogma env prod
export BACKEND_STRIPE_WEBHOOK_SECRET='whsec_...'
export BACKEND_SERVER_IP='1.2.3.4'
cargo install dogma-rust # binary installed as `dogma`
# or from source
cargo build --release # binary at target/release/dogmaSet DOGMA_VAULT per developer in .bashrc or per project in shell.nix:
export DOGMA_VAULT=pass # or: envvar (default)Bash — add to ~/.bashrc:
source <(dogma completions bash)Zsh — add to ~/.zshrc:
source <(dogma completions zsh)Fish — install once:
dogma completions fish > ~/.config/fish/completions/dogma.fish<env> is completed from dogma.yml. <unit> is completed from subdirectories of infra.path. <pipeline> is completed from pipeline names in dogma.yml.
Prints export VAR='value' for all infra.credentials. Eval to load.
eval $(dogma credentials dev)Prints export GROUP_FIELD='value' for every secret, uppercased.
eval $(dogma env dev)
# → export BACKEND_API_KEY='...'
# → export BACKEND_SERVER_IP='...'Reads from .dogma/cache/<env>.json.
dogma output dev # full JSON
dogma output dev hetzner # one unit
dogma output dev hetzner server_ip # raw valueSpawns a new shell with all secrets loaded as environment variables (same as dogma env). Prompt shows [dogma-<env> HH:MM:SS]. Exit to return.
dogma shell devSpawns a shell with infra.credentials loaded. Prompt shows [dogma-<env> HH:MM:SS]. Exit to return.
dogma infra auth devRuns: normalize → validate → tofu init → tofu apply. Credentials injected as env vars.
dogma infra apply dev hetzner
dogma infra apply dev hetzner --migrate-stateSame as apply but runs tofu destroy.
Runs: normalize → validate → tofu init. Always re-runs init, bypassing the already-initialized check that apply/destroy use. Escape hatch when the local .terraform/ is out of sync in a way the check can't detect (deleted plugin cache, manual edits).
dogma infra init dev hetzner
dogma infra init dev hetzner --upgradeFull deploy pipeline. Always deploys to all hosts. When no pipelines are declared in dogma.yml, an implicit nixos pipeline is used. When exactly one pipeline is declared, [pipeline] may be omitted; with several declared it is required.
A pipeline whose env attribute is set can also be run by name alone: dogma deploy <pipeline>. The single argument is resolved against declared env and pipeline names; a name that matches both is an error and requires the explicit two-argument form.
dogma deploy dev --new # new CalVer version, full pipeline
dogma deploy dev --latest # promote latest deploy/* tag
dogma deploy dev --version deploy/v26.06.0001 # promote specific tag
dogma deploy dev # interactive: pick from tag list
dogma deploy dev backend --new # named pipeline 'backend'
dogma deploy publish-prod --new # pipeline with env attribute set--new pipeline:
| Step | Action |
|---|---|
| 0 | Dep check: ssh-keyscan, ssh-to-age, sops, nixos-rebuild |
| 1 | Normalize + validate dogma.yml |
| 2 | Dirty tree check — commit or abort |
| 3 | Pre-deploy hooks |
| 4 | Infra cache refresh (all envs) |
| 5 | Generate .sops.yaml from SSH host keys |
| 6 | Encrypt secrets with sops, commit |
| 7 | nixos-rebuild switch per host |
| 8 | Git tags + push |
| 9 | Post-deploy hooks |
Flags:
| Flag | Description |
|---|---|
--new |
Create new CalVer version, run full pipeline |
--latest |
Promote latest deploy/* tag (no hooks) |
--version <tag> |
Promote specific tag (no hooks) |
--skip-infra |
Use existing infra cache |
--refetch |
Clear all caches and re-fetch |
-m <msg> |
Commit message for dirty tree (only with --new) |
Global flag. Prints elapsed ms to stderr after any command.
dogma --time deploy dev --newname: myproject # used in auto-derived vault paths
env: # list of environments
- dev
- prod
admin: # age/gpg/ssh keys for sops encryption
- gpg: <fingerprint>
- age: age1...
- ssh: ~/.ssh/id_ed25519.pub
infra:
cli: tofu # or: terraform (default: tofu)
path: ./infra # default: ./infra
credentials:
STATIC_VAR: value # injected as-is into tofu
SECRET_VAR:
from: vault
ref: <vault-key>
vault:
<key>:
pass: path/{env}/key # {env} substituted per environment
envvar: MY_VAR # flat string or per-env map; auto-derived if omitted
machines:
<name>:
hostname: host-{env} # {env} substituted; or per-env map
user: deployer # default: root
ip: "1.2.3.4" # static IP
ip: # or from infra output
from: infra
unit: <unit>
output: <output-key>
secrets: # secret groups to encrypt for this machine
- <group>
deployer: nixos-rebuild # default; only option currently
secrets:
<group>:
<field>:
from: vault
ref: <vault-key>
<field>:
from: infra
unit: <unit>
output: <output-key>
nix:
path: ./nix # default: ./nix
secrets: ./nix/secrets # default: ./nix/secrets
sops: ./nix/.sops.yaml # default: ./nix/.sops.yaml
deploy:
strategy: nixos-rebuild # default
hooks: # for the implicit default pipeline — only valid
post-deploy: # when no pipeline: block is declared below
- ./custom/notify-slack.sh
pipeline: # named pipelines (dogma deploy <env> [pipeline])
- name: backend
type: nixos # nixos | custom (default: custom)
env: dev # optional fixed env — enables `dogma deploy backend`
version_prefix: deploy # default: deploy → tags deploy/v26.06.0001
version_scheme: calver # calver (default) | semver | custom
version_script: ./custom/next-version.sh # required when version_scheme: custom
deployed_prefix: deployed # default: deployed → tags deployed-<env>-v26.06.0001
hooks: # only two hook names: pre-deploy, post-deploy
pre-deploy:
- ./custom/bump-version.sh
post-deploy:
- ./custom/notify-slack.sh
- name: publish
type: custom # requires command; {env} {version} {pipeline} substituted
command: ./scripts/publish.sh {env} {version}Set DOGMA_VAULT per developer — not in dogma.yml.
| Backend | DOGMA_VAULT |
How it resolves |
|---|---|---|
envvar |
(default) | Reads $VAR from process env. Var name from vault.<key>.envvar.<env>, auto-derived as UPPER_SNAKE if omitted |
pass |
pass |
Calls pass <path>. Path from vault.<key>.pass.<env>, auto-derived as <name>/<env>/<key> if omitted |
Declared per pipeline under pipeline[].hooks. When no pipeline: block is declared, a top-level hooks: block applies to the implicit default pipeline instead; if pipelines are declared, a top-level hooks: block is ignored with a warning. Exactly two hook names exist (kebab-case):
pre-deploy— runs only on--newdeploys (promotions via--latest/--versionskip it). Tracked file changes made by the hooks are folded into the deploy commit.post-deploy— runs after a successful deploy.
Each entry is a path to an executable script, relative to the repo root, optionally with arguments. {env}, {version}, and {pipeline} placeholders are substituted before running. Unknown hook names are ignored with a warning at normalize time.
Both hooks receive:
| Variable | Example | Notes |
|---|---|---|
DOGMA_VERSION |
deploy/v26.06.0001 |
Version tag |
DOGMA_ENV |
dev |
Target environment |
DOGMA_PIPELINE |
backend |
Pipeline name |
#!/usr/bin/env bash
# custom/bump-version.sh — write version into package.json
version="${DOGMA_VERSION#deploy/}"
jq --arg v "$version" '.version = $v' backend/package.json > tmp && mv tmp backend/package.jsonAdd to .gitignore:
.dogma/dogma-expanded.yml
.dogma/cache/
.dogma/age-keys/
| File | Purpose |
|---|---|
.dogma/dogma-expanded.yml |
Normalized config with all defaults filled in |
.dogma/cache/<env>.json |
Cached infra outputs |
.dogma/age-keys/<host>.pub |
Cached SSH→age host keys |
The binary runs on any Linux with no runtime deps. External tools are checked lazily — only when the command that needs them actually runs.
| Tool | Required by |
|---|---|
pass |
DOGMA_VAULT=pass |
tofu / terraform |
infra apply, infra destroy, deploy |
ssh-keyscan |
deploy --new |
ssh-to-age |
deploy --new |
sops |
deploy --new |
nixos-rebuild |
deploy (nixos-rebuild strategy) |