Skip to content

Docker Deployment

Images

slozy-web (Multi-Stage)

bash
docker build -t slozy-web:latest .
  • Builder stage: golang:1.25.0-alpine, downloads modules, builds ./cmd/slozy-web with CGO_ENABLED=0
  • Runtime stage: alpine:3.19, non-root slozy user (UID 1000), exposes :8080, HEALTHCHECK at /health
  • Labels: maintainer, version, org.opencontainers.image.source

slozy-ingestor

bash
docker build -f Dockerfile.ingestor -t slozy-ingestor:latest .

Same multi-stage pattern, builds ./cmd/slozy-ingestor. No HTTP server — runs the Prometheus metric ingestion loop.

Frontend

bash
cd frontend && docker build -t slozy-frontend:latest .

Builds with node:20-alpine, serves static files via nginx:alpine on port 80.

Docker Compose Files

There are two compose files for different environments:

docker-compose.yml (Development)

All-in-one stack for local development. Starts everything you need with one command:

ServicePurposePort
postgresPostgreSQL 16, credentials from .env, /tmp locked with noexec127.0.0.1:5432
redisRedis 7 with password auth from .env127.0.0.1:6379
prometheusMetrics storage, 30d retention127.0.0.1:9090
grafanaDashboards, password from .env127.0.0.1:3001
slozy-webMain Go application, hot-reload via air127.0.0.1:8080
slozy-frontendVite + React dev server127.0.0.1:3000

Use case: local development, testing, demo. PostgreSQL and Redis run as containers — no external dependencies.

bash
docker compose up -d

docker-compose.prod.yml (Production)

Minimal production stack. Does not include PostgreSQL or Redis — it assumes managed external services (e.g. RDS, ElastiCache) connected via .env.

ServicePurposePort
slozy-webGo application, resource limits (2 CPU / 4 GB)127.0.0.1:8080,9090
slozy-ingestorMetric ingestion worker, resource limits (1 CPU / 2 GB)none
nginxReverse proxy, SSL termination on 80/44380, 443

Use case: production deployments where PostgreSQL and Redis are managed externally for reliability and backup.

bash
docker compose -f docker-compose.prod.yml up -d

Why two files?

One docker-compose.yml is not enough because:

  • Dev needs everything local — PostgreSQL, Redis, Prometheus, Grafana, frontend as containers for zero-setup development
  • Prod needs external DBs — managed PostgreSQL (RDS, CloudSQL) and Redis (ElastiCache) provide backups, failover, and maintenance that containerized databases cannot
  • Different security posture — prod binds all ports to 127.0.0.1, adds resource limits, uses separate ingestor worker, and a reverse proxy for SSL
  • Different build — dev builds from source with hot-reload, prod uses pre-built images with restart: unless-stopped

Security hardening (both files)

All compose files apply these protections:

  • security_opt: no-new-privileges:true — prevents privilege escalation
  • tmpfs: /tmp:noexec,nosuid,size=100M — prevents executing malware from /tmp
  • Ports bound to 127.0.0.1 — not exposed to the network
  • Credentials from .env — never hardcoded in compose files

.env configuration

All secrets are injected via .env file. See .env.example for the full list.

bash
cp .env.example .env
# Then edit .env with your values

Also see deploy/production/docker-compose.prod.yml and deploy/production/cloud-init.yaml for cloud provisioning.