Skip to main content

Docker Integration

Arcan integrates with Docker Compose and Docker Swarm to inject secrets into containerized workloads.

Docker Compose

Generate .env files from Arcan secrets for use with docker compose up.

Basic workflow

# Store secrets in Arcan
arcan kv set DATABASE_URL "postgres://user:pass@db:5432/app" -r myapp -e prod
arcan kv set REDIS_URL "redis://cache:6379" -r myapp -e prod

# Generate .env file
arcan docker compose -r myapp -e prod

# Start containers
docker compose up -d

Using with docker-compose.yaml

Reference the generated .env file in your compose configuration:

# docker-compose.yaml
services:
api:
image: myapp:latest
env_file:
- .env
ports:
- "8080:8080"

worker:
image: myapp:latest
env_file:
- .env
command: ["worker"]

Per-environment files

arcan docker compose -r myapp -e dev -o .env.dev
arcan docker compose -r myapp -e staging -o .env.staging
arcan docker compose -r myapp -e prod -o .env.prod

JSON format

arcan docker compose -r myapp -e prod --format=json

Piping to other tools

arcan docker compose -r myapp -e prod --stdout | grep DATABASE

Docker Swarm

Sync Arcan secrets to Docker Swarm's built-in secrets store. Swarm secrets are encrypted at rest and only available to services that explicitly reference them.

Sync workflow

# Sync secrets from Arcan to Swarm
arcan docker swarm sync -r myapp -e prod

# Preview what would be synced
arcan docker swarm sync -r myapp -e prod --dry-run

# List managed secrets
arcan docker swarm ls

# Clean up
arcan docker swarm rm -r myapp -e prod
arcan docker swarm rm --all

Using Swarm secrets in services

After syncing, reference the secrets in your docker-compose.yaml (deploy mode):

# docker-compose.yaml (Swarm stack)
services:
api:
image: myapp:latest
secrets:
- arcan_prod_DATABASE_URL
- arcan_prod_API_KEY
environment:
DATABASE_URL_FILE: /run/secrets/arcan_prod_DATABASE_URL

secrets:
arcan_prod_DATABASE_URL:
external: true
arcan_prod_API_KEY:
external: true

Swarm mounts secrets as files at /run/secrets/<name>. Your application reads them from disk.

Custom prefix

By default, secrets are named arcan_<env>_<KEY>. Override with --prefix:

arcan docker swarm sync -r myapp -e prod --prefix="myapp_"
# Creates: myapp_DATABASE_URL, myapp_API_KEY, etc.

arcan docker swarm sync -r myapp -e prod --prefix=""
# Creates: DATABASE_URL, API_KEY (no prefix)

Complete Docker Compose Example

A full docker-compose.yaml for a web application stack using Arcan-generated .env files:

# docker-compose.yaml
services:
api:
image: myapp-api:latest
build: ./api
env_file:
- .env
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3

worker:
image: myapp-api:latest
env_file:
- .env
command: ["node", "worker.js"]
depends_on:
db:
condition: service_healthy

db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp"]
interval: 5s
timeout: 3s
retries: 5

cache:
image: redis:7-alpine
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
volumes:
- redisdata:/data

volumes:
pgdata:
redisdata:

Generate the .env file and start the stack:

# Generate .env from Arcan
arcan docker compose -r myapp -e prod

# Verify the generated file (keys only, values are present)
cat .env
# DATABASE_URL=postgres://myapp:****@db:5432/myapp
# REDIS_URL=redis://:****@cache:6379
# STRIPE_SECRET=sk_live_****
# SENTRY_DSN=https://****@sentry.io/12345

# Start everything
docker compose up -d

# Check that the API can reach the database
docker compose exec api curl -s http://localhost:8080/health

Complete Swarm Service Example

A Swarm stack file that references Arcan-synced secrets:

# stack.yaml (deployed with: docker stack deploy -c stack.yaml myapp)
version: "3.9"

services:
api:
image: registry.example.com/myapp-api:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
secrets:
- source: arcan_prod_DATABASE_URL
target: database_url
- source: arcan_prod_REDIS_URL
target: redis_url
- source: arcan_prod_STRIPE_SECRET
target: stripe_secret
environment:
# Tell the app where to find secrets on disk
DATABASE_URL_FILE: /run/secrets/database_url
REDIS_URL_FILE: /run/secrets/redis_url
STRIPE_SECRET_FILE: /run/secrets/stripe_secret
ports:
- "8080:8080"

secrets:
arcan_prod_DATABASE_URL:
external: true
arcan_prod_REDIS_URL:
external: true
arcan_prod_STRIPE_SECRET:
external: true

Sync secrets from Arcan and deploy:

# Preview what will be synced
arcan docker swarm sync -r myapp -e prod --dry-run
# Would create: arcan_prod_DATABASE_URL
# Would create: arcan_prod_REDIS_URL
# Would create: arcan_prod_STRIPE_SECRET

# Sync secrets to Swarm
arcan docker swarm sync -r myapp -e prod

# Deploy the stack
docker stack deploy -c stack.yaml myapp

# Verify secrets are mounted
docker exec $(docker ps -q -f name=myapp_api) ls /run/secrets/
# database_url
# redis_url
# stripe_secret

Your application reads secrets from files instead of environment variables:

// Read secret from Swarm-mounted file
func readSecret(name string) string {
// Check for _FILE variant first (Swarm pattern)
filePath := os.Getenv(name + "_FILE")
if filePath != "" {
data, err := os.ReadFile(filePath)
if err == nil {
return strings.TrimSpace(string(data))
}
}
// Fall back to direct environment variable
return os.Getenv(name)
}

When to Use Each Approach

ApproachBest forSecret storageRotation
Compose .envLocal development, single-host stagingPlaintext file on disk (0600 perms)Re-run arcan docker compose, then docker compose up -d
Swarm secretsProduction multi-node clustersEncrypted in Swarm Raft log, mounted as tmpfsRe-run arcan docker swarm sync, then rolling update
SidecarKubernetes, ECS, any orchestratorIn-memory only, never on diskSidecar polls Arcan, injects fresh values

Compose .env is simplest but stores secrets on disk. Use it for development and simple staging deployments.

Swarm secrets are encrypted at rest and only decrypted in memory on nodes running the service. Use them for production Swarm deployments.

Sidecar (see Sidecar docs) keeps secrets entirely in memory and supports automatic refresh. Use it when you need the strongest security posture or are running on Kubernetes/ECS.

Complete Workflow: Development to Production

This section walks through using Arcan with Docker Compose from initial setup to running your application.

1. Store your secrets in Arcan

# Store production secrets
arcan kv set DATABASE_URL "postgres://myapp:s3cure@db.example.com:5432/myapp" -r myapp -e prod
arcan kv set REDIS_URL "redis://:r3d1s@cache.example.com:6379" -r myapp -e prod
arcan kv set STRIPE_SECRET "sk_live_abc123def456" -r myapp -e prod
arcan kv set SENTRY_DSN "https://abc123@o12345.ingest.sentry.io/67890" -r myapp -e prod
arcan kv set JWT_SECRET "your-256-bit-jwt-signing-key" -r myapp -e prod

# Store development secrets (different values)
arcan kv set DATABASE_URL "postgres://dev:dev@localhost:5432/myapp_dev" -r myapp -e dev
arcan kv set REDIS_URL "redis://localhost:6379" -r myapp -e dev

2. Generate the .env file

# For development
arcan docker compose -r myapp -e dev -o .env

# Verify (keys shown, values present)
cat .env
# DATABASE_URL=postgres://dev:dev@localhost:5432/myapp_dev
# REDIS_URL=redis://localhost:6379

3. Use the complete docker-compose.yaml

# docker-compose.yaml
services:
api:
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s

worker:
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
command: ["node", "worker.js"]
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy

db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp_dev
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev -d myapp_dev"]
interval: 5s
timeout: 3s
retries: 5

cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5

volumes:
pgdata:
redisdata:

4. Start and verify

# Generate fresh .env and start
arcan docker compose -r myapp -e dev
docker compose up -d

# Check all services are healthy
docker compose ps
# NAME SERVICE STATUS
# api api running (healthy)
# worker worker running
# db db running (healthy)
# cache cache running (healthy)

# Test the API
curl http://localhost:3000/health
# {"status":"ok","db":"connected","cache":"connected"}

5. Rotate a secret

# Update the secret in Arcan
arcan kv set DATABASE_URL "postgres://myapp:n3wpassword@db.example.com:5432/myapp" -r myapp -e prod

# Regenerate .env and restart
arcan docker compose -r myapp -e prod -o .env
docker compose up -d

Choosing the Right Approach

Compose .envSwarm SecretsSidecar
Use whenLocal dev, single-host staging, CI/CDProduction Swarm clustersKubernetes, ECS, any orchestrator
Secrets storedPlaintext file on disk (0600 perms)Encrypted in Swarm Raft log, mounted as tmpfsIn-memory only, never on disk
RotationRe-run arcan docker compose, then docker compose up -dRe-run arcan docker swarm sync, then rolling updateSidecar polls Arcan, injects fresh values automatically
Security levelBasic -- file on diskGood -- encrypted at rest, tmpfs in containerBest -- never persisted, auto-refreshes
Setup complexityMinimalModerate (Swarm init, service configs)Higher (sidecar container, shared volume)
Best forGetting started fast, developmentTeams already on Docker SwarmProduction workloads needing strongest security

Rule of thumb:

  • Development: Compose .env (simple, fast)
  • Staging on Swarm: Swarm secrets (encrypted, production-like)
  • Production on Kubernetes/ECS: Sidecar (see Sidecar docs)

Security Notes

  • .env files are written with 0600 permissions (owner read/write only)
  • Add .env* to your .gitignore to prevent accidental commits
  • Values containing spaces, quotes, or shell metacharacters are automatically quoted
  • Swarm secrets are labeled with managed-by=arcan, arcan-realm, and arcan-env for tracking
  • Swarm secrets are immutable; updates remove and recreate the secret