Documentation
Everything you need to install, deploy and use dotMage — the E2E-encrypted .env manager.
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-x86_64 -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.
Deploy server
One command installs everything via Docker:
$ curl -fsSL https://raw.githubusercontent.com/dotMage/server/main/install.sh | 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
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.
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 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 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 existing environment
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 token
$ dmage token Web admin login token (one-time, 5 min): dmage_etok_a1b2c3d4e5f6... Paste this token in the web admin login page.
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
$ dmage clean
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_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
The SQLite database at /data/dotmage.db contains everything — encrypted blobs, device tokens, audit log. Back it up regularly.
# While the server is running $ docker compose exec server sqlite3 /data/dotmage.db ".backup /data/backup.db" # Copy backup out of container $ docker compose cp server:/data/backup.db ./backup-$(date +%F).db
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-amd64 \ -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-amd64 -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
Global flags
Available on every command:
| Flag | Description |
|---|---|
--env <ENV> | Override the active environment for this command |
-q, --quiet | Suppress non-error output |
--json | JSON output for scripting |
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | General error / not found |
3 | Not authenticated — run dmage auth |
4 | Conflict (e.g. concurrent push) |