Docker
The shortest path to a running instance. One Compose project holds PostgreSQL, the backend and the frontend, with named volumes for everything that must outlive a container.
Prerequisites
- Docker Engine with the Compose plugin (
docker compose versionmust answer). - Two host names pointing at the machine, and a reverse proxy holding the certificate.
- The four secrets from the shared prerequisites.
The Compose file
This is the shape the project's own deploy/compose/remote.compose.yml uses, reduced to what a first installation needs. Save it as compose.yml.
services:
front:
container_name: aetheus-front
image: aetheus-front:latest
# Loopback only: the reverse proxy is the sole entry point. Publishing on 0.0.0.0 would
# expose the app in cleartext, and Docker's iptables rules bypass a host firewall.
ports:
- "127.0.0.1:10025:8080"
environment:
- API_BASE_URL=https://api.example.com
- APP_VERSION=1.0.0
depends_on:
back:
condition: service_healthy
mem_limit: 256m
restart: unless-stopped
back:
container_name: aetheus-back
image: aetheus-back:latest
ports:
- "127.0.0.1:10026:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Production
# The entrypoint owns migration execution, so the application must not run them again.
- Database__SkipMigrations=true
- AETHEUS_RUN_MIGRATIONS=true
- DataProtection__KeyPath=/app/data/dp-keys
- ConnectionStrings__Default=Host=aetheus-database;Port=5432;Database=aetheus;Username=aetheus;Password=${DB_PASSWORD}
- Auth__JwtKey=${JWT_KEY}
- Auth__AdminPassword=${ADMIN_PASSWORD}
- Auth__EncryptionKey=${ENCRYPTION_KEY}
- Auth__EncryptionSalt=${ENCRYPTION_SALT}
- Aetheus__PublicApiBaseUrl=https://api.example.com
- Cors__Origins__0=https://app.example.com
volumes:
- git-repos:/app/data/git-repos
- dp-keys:/app/data/dp-keys
- artifacts:/app/data/artifacts
- packages:/app/data/packages
depends_on:
database:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://127.0.0.1:8080/health/live"]
interval: 30s
timeout: 5s
retries: 3
start_period: 120s
mem_limit: 1g
networks:
- default
- backend
restart: unless-stopped
database:
container_name: aetheus-database
image: postgres:18-alpine
environment:
- POSTGRES_DB=aetheus
- POSTGRES_USER=aetheus
- POSTGRES_PASSWORD=${DB_PASSWORD}
- PGDATA=/var/lib/postgresql/data
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U aetheus -d aetheus"]
interval: 5s
timeout: 3s
retries: 10
# Not on the default network: the database is reachable by the backend and by nothing else.
networks:
- backend
restart: unless-stopped
networks:
backend:
driver: bridge
volumes:
db-data:
git-repos:
dp-keys:
artifacts:
packages:
The .env file
Compose reads .env from the same directory. Keep it out of version control and readable only by the account that runs Docker.
DB_PASSWORD=...
JWT_KEY=...
ADMIN_PASSWORD=...
ENCRYPTION_KEY=...
ENCRYPTION_SALT=...
chmod 600 .env
Getting the images
Build them from a checkout of the repository. Both Dockerfiles expect the repository root as their build context.
docker build \
--file deploy/docker/Dockerfile.back \
--build-arg APP_VERSION=1.0.0 \
--tag aetheus-back:latest .
docker build \
--file deploy/docker/Dockerfile.front \
--tag aetheus-front:latest .
The frontend image is a small static server that rewrites wwwroot/appsettings.json at startup from API_BASE_URL. That is why the frontend's API address is an environment variable here, while the binaries route has to edit the file by hand.
First start
docker compose up -d
docker compose logs -f back
On a fresh database the backend entrypoint applies the migrations before the application starts, so the first start takes longer than later ones. That is what the 120-second start_period on the healthcheck accounts for.
When the backend is healthy:
curl -sf http://127.0.0.1:10026/health/ready
Then point your reverse proxy at 127.0.0.1:10025 for the app host and 127.0.0.1:10026 for the API host, and sign in as admin.
The volumes, and why they matter
| Volume | Holds |
|---|---|
db-data | PostgreSQL. Everything. |
git-repos | Internally hosted Git repositories, including pipeline definitions. |
artifacts | Build artefacts, and therefore the ability to redeploy or roll back. |
dp-keys | Data-protection keys. Losing them invalidates sessions and protected payloads. |
packages | Packages served by the internal package registry. |
A docker compose down keeps named volumes. A docker compose down -v deletes them, and there is no undo.
Upgrading
Back up the database and the volumes.
docker compose exec -T database \ pg_dump -U aetheus aetheus > aetheus-$(date +%Y%m%dT%H%M%SZ).sqlBuild or pull the new images and tag them.
Recreate the services. Migrations run in the backend entrypoint as it comes up.
docker compose up -dConfirm readiness before sending traffic back.
curl -sf http://127.0.0.1:10026/health/ready
Zero-downtime upgrades
The layout above restarts the containers in place, so there is a short outage. The repository also carries a blue-green Compose file, deploy/compose/remote-bluegreen.compose.yml, where two coloured stacks share one database and the reverse proxy switches between them. That is how Aetheus deploys itself, and it is what the pipeline route drives.