Documentation
Install, deploy and use dotMage — the end-to-end-encrypted .env manager.
Overview
How it works
- A single binary, no runtime dependencies. Run
dmage authonce per machine;pull,pushandsyncthen operate on the same.envfrom each machine. - Reads and writes plain
.envfiles.dmage execpasses secrets to a process through its environment, without writing them to disk. - Secrets are stored on a server you run and synced to the machines you authorize. The full stack (server and web admin) uses about 75 MiB of RAM.
- Team mode adds users, roles, an audit log and offboarding. The server enforces roles but has no key to read secret values.
Security model
- End-to-end encryption: the server stores only ciphertext. If the server or its database is compromised, the stored secrets remain encrypted — the decryption key is not on the server.
- The master password stays on the client. It derives a key (Argon2id) that unwraps the account key locally; the server stores only the wrapped key.
- Each revision is encrypted with XChaCha20-Poly1305 and bound via AEAD to its app, environment and revision number, so the server cannot substitute or reattribute a blob.
- Device tokens are stored as hashes and expire; a lost device can be revoked individually. The web admin shows metadata only.
Install CLI
dotMage CLI is a single Rust binary with no dependencies. Choose your platform:
$ brew install dotMage/dotmage/dotmage
$ curl -fsSL https://github.com/dotMage/dotmage/releases/latest/download/dmage-linux-$(uname -m) -o /usr/local/bin/dmage $ chmod +x /usr/local/bin/dmage
> irm https://raw.githubusercontent.com/dotMage/dotmage/main/install.ps1 | iex
Downloads the binary to %LOCALAPPDATA%\dotmage\ and adds it to PATH. Restart your terminal — dmage works from anywhere.
Or download from GitHub releases.
Upgrading
Direct-binary installs upgrade themselves — the CLI downloads the release, verifies it against the release's SHA256SUMS, and swaps atomically. Homebrew installs use brew:
$ dmage upgrade $ brew upgrade dotmage # if installed via Homebrew $ dmage upgrade --channel dev # opt into prerelease builds
The default channel is stable and ignores prereleases. --channel dev installs the newest build including vX.Y.Z-dev.N prereleases; the next stable release is a regular upgrade off the channel.
Deploy server
One command installs everything via Docker:
$ curl -fsSL https://raw.githubusercontent.com/dotMage/server/main/install.sh | bash
For a team server, add one flag — the same installer starts the server with team endpoints enabled:
$ curl -fsSL https://raw.githubusercontent.com/dotMage/server/main/install.sh | DOTMAGE_MODE=team bash
This creates a dotmage/ directory with docker-compose.yml, pulls the server and web-admin images, and starts the containers on port 9470. The bootstrap secret is printed to the console — save it.
Quick start
After the server is running (example: VPS at 167.71.55.12, port 9470):
# 1. Authenticate with the server $ dmage auth --server http://167.71.55.12:9470 # 2. Init an app from your .env $ dmage init myapp ✓ Created app 'myapp'. Pushed revision 1 from .env (12 keys). # 3. Run your app with injected secrets (no .env on disk) $ dmage exec myapp -- npm run dev
Adding another device
On any new machine — just the bootstrap secret and your master password. No first device needed, no tokens to generate.
$ dmage auth --server http://167.71.55.12:9470 New device — register with bootstrap secret Bootstrap secret: **** Master password: **** ✓ Device registered. Key cached (expires in 7d). $ dmage pull myapp ✓ Decrypted 12 keys → .env
Your identity is a set of devices, so laptop, home PC and a machine in another city can all use the same account. On a device you're already signed in on, mint a one-time enrollment token — dmage token, or the admin panel's Devices → Add a device — then on the new machine:
$ dmage auth --server https://your-server --enroll dmage_etok_... # enter your master password → your key is unwrapped locally
Each machine is its own device: independent token, its own TTL, revocable on its own from the Devices page if it's lost. Registering a device always needs proof — an enrollment token from a device you already have (or the server's bootstrap secret) — never just the password.
dmage exec (secrets stay in memory, nothing written to disk) and dmage lock afterwards — or just use the web panel, which only ever shows metadata, never secret values. Revoke the device when you're done.
Organizing apps with folders
Use / in app names to group them into folders. No configuration needed — the folder is just a naming convention.
$ dmage init work/myapp $ dmage init work/backend $ dmage init personal/blog $ dmage apps work/ myapp 2 envs 2026-06-11 backend 1 envs 2026-06-10 personal/ blog 3 envs 2026-06-11
All commands work the same — just use the full name with the folder prefix:
$ dmage push work/myapp $ dmage pull work/backend --env prod $ dmage exec personal/blog -- npm run dev
The web admin groups apps by folder automatically. Apps without a / appear at the root level as before.
Multiple servers (work / personal)
Optional — with a single server nothing changes. Map project directories to servers in the global config (like git's includeIf) and every command picks the right server from the directory you run it in. No files are created inside your projects.
$ dmage server add work https://secrets.corp.com --path ~/code/work $ dmage auth --server work $ dmage server add personal https://home.example.com --path ~/code/personal $ dmage auth --server personal # inside a mapped directory the app name defaults to the dir name $ cd ~/code/work/billing-api $ dmage push ✓ Pushed revision 5 (12 keys). → work (secrets.corp.com)
Server resolution order: --server <name> flag → DOTMAGE_SERVER env var → longest matching mapped path → default (dmage server use <name>). Ambiguity is an error listing candidates, never a silent guess. dmage status shows which server was picked and why; dmage server list shows all servers with auth state and paths.
Beyond .env — any file
Store any config file — DataGrip datasource XML, kubeconfig, service-account JSON. The file name and format are encrypted together with the contents (the server never sees them), and pull on another machine recreates the file under its original name:
$ dmage init dbconf --file dataSources.xml ✓ Created app 'dbconf'. Pushed revision 1 from dataSources.xml (4.2 KB, text). # on another machine $ dmage pull dbconf # writes dataSources.xml $ dmage push dbconf # picks up dataSources.xml automatically
Formats — env (key diff, exec injection), text (line/byte diff), binary (sha256 compare) — are detected from the extension; override with --format. exec works only with env apps. Non-env apps require dmage ≥ 1.3 on all devices; existing .env apps are unaffected.
Key rotation
If a device with a cached key leaves your control, rotate the Account Key — every revision is re-encrypted client-side with a fresh key (the server cannot read blobs, so it cannot do this for you). The operation is resumable: interrupt it at any point and re-run.
$ dmage rotate-key Master password: ******** re-encrypted 51/51 revisions ✓ Key rotated to generation 2 (51 revision(s) re-encrypted).
After a rotation: old cached keys stop working for new data (devices re-run dmage auth), CI tokens must be regenerated, and — important — backups taken before the rotation remain decryptable by the old key: destroy or re-encrypt them if you rotated because of a compromise.
Team mode v2
One product, two modes: solo is a team of one, and nothing changes until you invite a second person. Each member gets their own master password wrapping the shared vault key — offboarding stops being "change the shared password and hope".
# fresh server — one flag on the installer $ curl -fsSL https://raw.githubusercontent.com/dotMage/server/main/install.sh | DOTMAGE_MODE=team bash # existing server — add to docker-compose.yml and restart # environment: # DOTMAGE_MODE: "team" $ docker compose up -d # owner invites a member $ dmage user invite kolya --role editor dmage_uinv_eyJp... # one-time token — send over a PRIVATE channel # on their machine — they pick their OWN password $ dmage auth --invite dmage_uinv_eyJp... ✓ Joined as 'kolya' (editor). Key cached. $ dmage user list USER ROLE STATUS SINCE owner owner active 2026-07-03 kolya editor active 2026-07-03
Roles — owner (everything), editor (push/pull/rollback), viewer (read-only) — are enforced by the server. Note the limit: roles are authorization, not cryptography — every member holds the vault key and could technically decrypt any blob they can download. The audit log now names people, not just devices.
Offboarding (someone leaves)
$ dmage user rm kolya
✓ removed 'kolya' (1 device(s) revoked)
Rotate the Account Key now (recommended)? [Y/n]
Removal deletes their key wraps and revokes their devices; the chained rotate-key makes their cached key useless for anything pushed afterwards. Then: rotate the secret values they saw (what they read, they read) and destroy pre-rotation backups — rotation cannot reach into old backups.
Migrating from a shared password
If two of you have been sharing one master password (the pre-v2 workaround):
# 1. upgrade the server, set DOTMAGE_MODE=team $ dmage user invite kolya --role editor # you (owner) $ dmage auth --invite dmage_uinv_... # colleague: sets their OWN password # 2. colleague re-auths their other devices as themselves; you revoke their old devices $ dmage rotate-key # kills every old cached key # 3. change your master password; destroy pre-rotation backups
Python SDK
Use dotMage from Python code — CI jobs, backend services, scripts. The dotmage package is a second, independent client: a typed API client plus the full client-side crypto, byte-compatible with the dmage CLI. Secrets are encrypted and decrypted locally, exactly as with the CLI — the server still sees only blobs.
$ pip install dotmage
# first device on a fresh server — creates the vault from dotmage import DotMage client, recovery_code = DotMage.init_vault( "https://secrets.example.com", BOOTSTRAP_SECRET, MASTER_PASSWORD, device_name="laptop", ) with client: client.create_app("work/api") client.create_env("work/api", "prod") client.push("work/api", "prod", {"DATABASE_URL": "postgres://...", "DEBUG": "0"}) secrets = client.pull("work/api", "prod") # decrypted locally
Other entry points mirror the CLI flows: DotMage.enroll(...) (add a machine via enrollment token), DotMage.from_ci(...) (scoped CI token), DotMage.join(...) (accept a team invitation). An async client (AsyncDotMage) is included.
# CI: pull one app/env with a scoped token
client = DotMage.from_ci(SERVER_URL, ci_token=CI_TOKEN, master_password=MASTER_PASSWORD)
with client:
secrets = client.pull("work/api", "prod")
Full docs — getting started, API reference, security model and the crypto contract — live in the repository.
dmage auth
--enroll for programmatic enrollment.# First device (creates account) $ dmage auth --server http://167.71.55.12:9470 # Additional devices (bootstrap secret + master password) $ dmage auth --server http://167.71.55.12:9470 # Programmatic enrollment (CI, scripts) $ dmage auth --server http://167.71.55.12:9470 --enroll <token>
--server <URL>- Server URL (required on first auth)
--enroll <TOKEN>- Enrollment token for 2nd+ devices
--ttl <DURATION>- Cache TTL, e.g.
7d,30d
dmage init
.env file. Encrypts and pushes as revision 1.$ dmage init myapp $ dmage init myapp --file .env.local
<NAME>- Application name (required)
--file <PATH>- Path to .env file (default:
.env)
dmage push
.env as a new encrypted revision.$ dmage push myapp $ dmage push myapp --file .env.production --env prod
<NAME>- Application name (required)
--file <PATH>- Path to .env file (default:
.env)
dmage pull
.env.$ dmage pull myapp $ dmage pull myapp --rev 3 --env prod $ dmage pull myapp --stdout
<NAME>- Application name (required)
--rev <N>- Specific revision number (default: latest)
--output <PATH>- Output file path
--stdout- Print to stdout instead of writing file
--force- Overwrite without confirmation
dmage sync
dmage in a project also hints when you have unpushed changes.$ dmage sync ✓ in sync (rev 7). $ dmage sync # after editing .env local changes → pushing ✓ Pushed revision 8.
dmage exec
$ dmage exec myapp -- npm run dev $ dmage exec myapp --env prod -- python manage.py migrate
<NAME>- Application name (required)
<COMMAND> [ARGS...]- Command to run (after
--)
dmage diff
.env and the latest remote revision.$ dmage diff myapp $ dmage diff myapp --show-values
<NAME>- Application name (required)
--show-values- Show actual secret values in the diff
dmage history
$ dmage history myapp
<NAME>- Application name (required)
dmage rollback
$ dmage rollback myapp --rev 2
<NAME>- Application name (required)
--rev <N>- Target revision number (required)
dmage apps
$ dmage apps
dmage app rm
$ dmage app rm myapp $ dmage app rm work/backend --yes
<NAME>- Application name (required)
--yes- Skip confirmation prompt
dmage env
# Show active environment $ dmage env # List environments for an app $ dmage env list myapp # Create a new environment $ dmage env new myapp staging $ dmage env new myapp prod --copy-from dev # Delete an environment $ dmage env rm myapp staging --yes
env- Show active environment
env list <APP>- List environments for an app
env new <APP> <NAME>- Create new environment
--copy-from <ENV>- Copy secrets from an existing environment. The copy happens on your machine (decrypt → re-encrypt for the new env) — the server can't copy blobs it can't read. Requires CLI ≥ 2.1
env rm <APP> <NAME>- Delete an environment
--yes- Skip confirmation prompt
dmage status
$ dmage status
dmage gen-token
--enroll). For CI/CD, use gen-ci-token instead.$ dmage gen-token --name "work-pc" --ttl 24h
--name <NAME>- Token label (optional)
--ttl <DURATION>- Time to live, e.g.
24h(default: 24h)
dmage open
--print to get the link to open elsewhere.$ dmage open ✓ Opening admin panel — http://your-server:9471 Logging you in automatically (one-time link, valid 5 min).
dmage token
dmage open.$ dmage token Web admin login token (one-time, 5 min): dmage_etok_a1b2c3d4e5f6... Paste this token in the web admin login page.
dmage completions
# zsh — one of the dirs in your $fpath $ dmage completions zsh > ~/.zfunc/_dmage # bash $ dmage completions bash > ~/.local/share/bash-completion/completions/dmage # fish $ dmage completions fish > ~/.config/fish/completions/dmage.fish # powershell — add to $PROFILE $ dmage completions powershell | Out-String | Invoke-Expression
dmage gen-ci-token
$ dmage gen-ci-token --app myapp --env prod --ttl 30d CI token for myapp/prod: dmage_ci_eyJ0Ijoi... Store it as a CI secret (e.g. DOTMAGE_CI_TOKEN).
--app <NAME>- Application name (required)
--env <ENV>- Environment name (required)
--ttl <DURATION>- Token lifetime, e.g.
30d(default: 30d)
dmage lock
$ dmage lock
dmage logout
$ dmage logout
dmage clean
--server <name> wipes just one server.$ dmage clean $ dmage clean --server work # just one
dmage server
$ dmage server list $ dmage server add work https://secrets.corp.com --path ~/code/work $ dmage server rename <old> <new> # local label only, URL unchanged $ dmage server map work ~/code/work $ dmage server unmap work ~/code/work $ dmage server use work # default for unmapped dirs $ dmage server rm work # remove + wipe its tokens/key
list- All servers with auth state and mapped paths
add <name> <url> [--path DIR]- Add or update a server;
--pathmay repeat rename <old> <new>- Rename locally (URL unchanged)
map / unmap <name> <dir>- Bind/unbind a directory so commands there auto-route
use <name>- Set the fallback default server
rm <name> [--yes]- Remove a server and wipe its local tokens + key
dmage whoami
$ dmage whoami
dmage user
$ dmage user list $ dmage user invite kolya --role editor # prints a one-time token $ dmage user role kolya owner $ dmage user rm kolya # the invitee, on their machine — picks their OWN password: $ dmage auth --invite dmage_uinv_...
invite <name> [--role R] [--ttl T]- Roles: owner / editor / viewer. TTL default 24h
role <name> <role>- Change a member's role (owner only)
rm <name>- Offboard: wraps removed, devices revoked, rotation offered
dmage rotate-key
$ dmage rotate-key
Server configuration
All settings are configured via environment variables, prefixed with DOTMAGE_.
| Variable | Default | Description |
|---|---|---|
DOTMAGE_DB_URL | sqlite:////data/dotmage.db | Database URL. SQLite or PostgreSQL DSN. |
DOTMAGE_BOOTSTRAP_SECRET | auto-generated | One-time secret for account init. If empty, printed to stderr at startup. |
DOTMAGE_MODE | solo | solo | team — team endpoints (users, invitations) exist only in team mode; see Team mode. |
DOTMAGE_TOKEN_TTL | 24h | Device access token lifetime. Formats: 123s, 30m, 24h, 7d. |
DOTMAGE_REFRESH_TTL | 30d | Refresh token lifetime for token rotation. |
DOTMAGE_RATE_LIMIT | 10/min | Rate limit on auth endpoints. |
DOTMAGE_LOG_LEVEL | info | Log level: debug, info, warning, error. |
DOTMAGE_STATIC_DIR | /app/static | Path to web admin static files. |
Docker
Quickest way to run the server:
$ curl -fsSL https://raw.githubusercontent.com/dotMage/server/main/install.sh | bash $ cd dotmage && docker compose up -d
Or with a custom docker-compose.yml:
version: "3.8" services: server: image: ghcr.io/dotmage/server:latest restart: unless-stopped environment: DOTMAGE_DB_URL: "sqlite:////data/dotmage.db" DOTMAGE_TOKEN_TTL: "24h" volumes: - dotmage-data:/data ports: - "9470:8000" volumes: dotmage-data:
The server will be available at http://<your-ip>:9470. Point the CLI at this address when authenticating.
TLS / Production
For production, put a reverse proxy in front of the server. Example with Caddy (automatic HTTPS with Let's Encrypt):
services: server: image: ghcr.io/dotmage/server:latest restart: unless-stopped environment: DOTMAGE_DB_URL: "sqlite:////data/dotmage.db" volumes: - dotmage-data:/data expose: - "8000" proxy: image: caddy:2 restart: unless-stopped ports: - "443:443" - "80:80" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy-data:/data volumes: dotmage-data: caddy-data:
Caddyfile:
secrets.example.com {
reverse_proxy server:8000
}
Without a domain, you can also use nginx or Caddy with a self-signed cert, or just run plain HTTP on a private network:
$ dmage auth --server http://192.168.1.50:9470
Backup & restore
The SQLite database at /data/dotmage.db contains everything — encrypted blobs, the password-wrapped Account Key, device tokens, audit log.
This is a consequence of the end-to-end design, not an oversight: the server only ever stores ciphertext it cannot open, so there is no "forgot password" flow and no server-side recovery. The server cannot restore what it never sees. That makes the database file the whole vault — and backups your responsibility.
One-off backup
SQLite's online backup is safe while the server is running — no downtime, no locked writes:
# Online backup via Python (safe while server is running) $ docker compose exec server python -c "import sqlite3; s=sqlite3.connect('/data/dotmage.db'); d=sqlite3.connect('/data/backup.db'); s.backup(d); d.close()" # Copy backup out of container $ docker compose cp server:/data/backup.db ./backup-$(date +%F).db
Scheduled backup (cron)
The same two commands as a nightly cron job on the Docker host (note the \% — cron treats a bare % as a newline):
# crontab -e — nightly at 03:00, keep two weeks 0 3 * * * cd /opt/dotmage && docker compose exec -T server python -c "import sqlite3; s=sqlite3.connect('/data/dotmage.db'); d=sqlite3.connect('/data/backup.db'); s.backup(d); d.close()" && docker compose cp server:/data/backup.db /var/backups/dotmage-$(date +\%F).db 30 3 * * * find /var/backups -name 'dotmage-*.db' -mtime +14 -delete
Ship the files off the machine with whatever you already use — rsync, restic, rclone. The blobs inside are E2E-encrypted, but treat backups as sensitive anyway: they contain the password-wrapped Account Key, so a weak master password could be brute-forced offline.
Verify a backup
A backup you never checked is a hope, not a backup:
$ sqlite3 dotmage-2026-07-16.db "PRAGMA integrity_check;" ok # no sqlite3 on the host? borrow the server image: $ docker run --rm -v "$PWD:/b" ghcr.io/dotmage/server:latest python -c "import sqlite3; print(sqlite3.connect('/b/dotmage-2026-07-16.db').execute('PRAGMA integrity_check').fetchone()[0])" ok
Restore
Restoring replaces the database in the data volume with a backup file. Accounts, devices and secrets return to their state at backup time; anything pushed after that moment is gone.
# 1. stop the stack $ cd dotmage && docker compose down # 2. copy the backup into the data volume (name: <project>_dotmage-data) $ docker volume ls | grep dotmage-data $ docker run --rm -v dotmage_dotmage-data:/data -v "$PWD:/host" alpine cp /host/dotmage-2026-07-16.db /data/dotmage.db # 3. start and verify $ docker compose up -d $ curl -s http://localhost:9470/health # → "account_exists": true $ dmage status
dmage push.
Upgrading the server
dotMage server is distributed as a Docker image. To upgrade an existing installation:
$ curl -fsSL https://raw.githubusercontent.com/dotMage/server/main/install.sh | bash
Prefer to do it by hand? The steps below are what the installer runs.
1. Backup the database
$ cd dotmage $ docker compose exec server python -c "import sqlite3; s=sqlite3.connect('/data/dotmage.db'); d=sqlite3.connect('/data/backup.db'); s.backup(d); d.close()" $ docker compose cp server:/data/backup.db ./backup-$(date +%F).db
2. Pull the latest image and restart
$ docker compose pull $ docker compose up -d
Docker will download the new image and recreate the container. The data volume is preserved — your database, accounts, and all encrypted secrets remain intact.
3. Verify
$ docker compose logs --tail 20 server
Check the logs to confirm the server started cleanly. The CLI does not require an update for server-only upgrades — but it's a good idea to keep both in sync.
brew upgrade dotmage on macOS, or re-run the curl/PowerShell command on Linux/Windows.
CI/CD setup
dotMage can inject secrets into CI/CD pipelines using scoped CI tokens. Each token gives access to exactly one app+environment — nothing more.
1. Generate a scoped CI token
$ dmage gen-ci-token --app myapp --env prod --ttl 30d # → prints dmage_ci_eyJ0Ijoi...
Store this as a CI secret named DOTMAGE_CI_TOKEN. That's it — one variable, no dmage auth in the pipeline.
Pipeline examples
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # ── dotMage: pull secrets ── - name: Pull secrets env: DOTMAGE_CI_TOKEN: ${{ secrets.DOTMAGE_CI_TOKEN }} run: | curl -fsSL https://github.com/dotMage/dotmage/releases/latest/download/dmage-linux-$(uname -m) \ -o /usr/local/bin/dmage && chmod +x /usr/local/bin/dmage dmage pull myapp --env prod --output .env # ── your deploy steps ── - name: Deploy run: npm start
# Reusable template — paste once .dmage: before_script: - curl -fsSL https://github.com/dotMage/dotmage/releases/latest/download/dmage-linux-$(uname -m) -o /usr/local/bin/dmage && chmod +x /usr/local/bin/dmage - dmage pull $DMAGE_APP --env $DMAGE_ENV --output .env # Your job — just extend .dmage deploy: extends: .dmage variables: DOTMAGE_CI_TOKEN: $DOTMAGE_CI_TOKEN DMAGE_APP: myapp DMAGE_ENV: prod script: - source .env && npm start
How does it compare to Infisical, Vaultwarden, sops & Doppler?
dotMage syncs .env-style secrets between machines with end-to-end encryption, using a small self-hosted server. The tools below overlap with it in some areas and differ in others.
| Tool | E2E by default | Self-hosted | Weight & dependencies |
|---|---|---|---|
| dotMage | yes — the server only ever stores ciphertext | yes; no vendor account | one container + SQLite, ~75 MiB RAM for the whole stack |
| Infisical | no — encryption is server-side, so the backend can power rotation and integrations | yes | Postgres + Redis + API/UI — a small cluster |
| Vaultwarden | yes (Bitwarden protocol) | yes | one container + SQLite |
| sops + git | yes (age/GPG) — files are encrypted before commit | no server at all | zero infrastructure |
| dotenv-vault | partially — .env.vault is encrypted, but keys flow through dotenv.org | no — cloud only | zero infra + vendor cloud |
| Doppler | no — server-side, that's what makes its integrations possible | no — SaaS only, closed source | zero infra (their cloud) |
When to pick them instead
- Infisical — a web UI where the team edits secret values, plus dynamic secrets, credential rotation, a Kubernetes operator and SSO. Encryption is server-side (the backend can read secrets), and it runs Postgres and Redis.
- Vaultwarden — a password manager: logins, browser extensions, mobile apps. It does not sync
.envfiles or inject secrets into CI (Bitwarden's Secrets Manager isn't part of Vaultwarden). - sops + git — GitOps: secrets versioned next to code, reviewed in PRs, decrypted by flux/argo, keys in a cloud KMS. There is no server; in exchange there is no push/pull between machines, no audit, no roles or offboarding, and key distribution is manual.
- dotenv-vault — deprecated by its own authors in favour of dotenvx, which encrypts
.envin-repo without a server. dotenvx is a reasonable sops-style option if you prefer secrets in git rather than synced through a server. - Doppler — a closed-source SaaS with a large integration catalogue. Encryption is server-side, and it is not self-hostable.
Limitations
- No editing of secret values in the web admin — it shows metadata only.
- No dynamic secrets, no automatic rotation of cloud credentials, no Kubernetes operator, no SSO.
- Single node on SQLite: suitable for a team, not for organizations of hundreds or HA clusters.
- Young project with a small community. Cryptography is XChaCha20-Poly1305 and Argon2id. The ecosystem is the CLI and a Python SDK.
Global flags
Available on every command:
| Flag | Description |
|---|---|
--env <ENV> | Override the active environment for this command |
--server <NAME> | Use a specific configured server for this command |
-q, --quiet | Suppress non-error output |
--json | Machine-readable JSON output on status, diff, history, apps, whoami. One JSON document on stdout, errors stay on stderr; schemas are a semver contract. diff --json never includes secret values |
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | General error / not found |
3 | Not authenticated — run dmage auth |
4 | Conflict (e.g. concurrent push) |