From f1d4dd8cb88b9400f3a96baac268d64544358a5a Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 11 Apr 2026 08:32:21 +0200 Subject: [PATCH] production docker --- .env.prod.example | 15 ++++++ compose.prod.yml | 118 +++++++++++++++++++++++++++++++++++++++++ scripts/deploy_prod.sh | 89 +++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 .env.prod.example create mode 100644 compose.prod.yml create mode 100755 scripts/deploy_prod.sh diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 0000000..a3a0730 --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,15 @@ +# Copy to .env.prod, fill in, then: set -a && source .env.prod && set +a +# Or use: export $(grep -v '^#' .env.prod | xargs) + +POSTGRES_HOST=postgres +POSTGRES_DB=gc_index_relay_prod +POSTGRES_USER=postgres +POSTGRES_PASSWORD=change-me +POSTGRES_RUNTIME_USER=gc_index_relay +POSTGRES_RUNTIME_PASSWORD=change-me-runtime + +SECRET_KEY_BASE=generate-with-mix-phx-gen-secret +PHX_HOST=gc-http-relay.imwald.eu + +# Optional: pin release image (default latest) +# TAG=0.2.0 diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 0000000..c332c9e --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,118 @@ +# Production stack: Apache (on the host) terminates TLS and proxies to 127.0.0.1:4000. +# +# Helper script: ./scripts/deploy_prod.sh --help +# +# --- Local: build and push --- +# cp .env.prod.example .env.prod && edit secrets +# export TAG=0.2.0 # optional; relay/migrator use :latest if unset +# docker login +# ./scripts/deploy_prod.sh build-push +# +# --- Remote: pull and run --- +# ./scripts/deploy_prod.sh deploy +# +# Images (repository: silberengel/gc-http-relay): +# :${TAG} — Phoenix release (relay + migrator) +# :setup — one-shot DB user bootstrap (tag is literal "setup") + +services: + postgres: + image: docker.io/apache/age:release_PG17_1.6.0 + restart: unless-stopped + user: 1000:1000 + volumes: + - pgdata:/var/lib/postgresql/data + environment: + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env.prod} + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env.prod} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env.prod} + command: > + postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + deploy: + resources: + limits: + cpus: "1.00" + memory: 1G + reservations: + cpus: "0.50" + memory: 512M + networks: + - internal + + setup: + image: docker.io/silberengel/gc-http-relay:setup + build: + context: . + dockerfile: ./docker/setup.Dockerfile + command: ["/usr/local/bin/usersetup.sh"] + restart: "no" + depends_on: + postgres: + condition: service_healthy + environment: + POSTGRES_HOST: ${POSTGRES_HOST:-postgres} + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env.prod} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env.prod} + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env.prod} + POSTGRES_RUNTIME_USER: ${POSTGRES_RUNTIME_USER:?set POSTGRES_RUNTIME_USER in .env.prod} + POSTGRES_RUNTIME_PASSWORD: ${POSTGRES_RUNTIME_PASSWORD:?set POSTGRES_RUNTIME_PASSWORD in .env.prod} + networks: + - internal + + migrator: + image: docker.io/silberengel/gc-http-relay:${TAG:-latest} + build: + context: . + dockerfile: ./docker/server.Dockerfile + command: ["/app/bin/migrate"] + restart: "no" + depends_on: + postgres: + condition: service_healthy + setup: + condition: service_completed_successfully + environment: + DATABASE_URL: "ecto://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}/${POSTGRES_DB}" + SECRET_KEY_BASE: ${SECRET_KEY_BASE:?set SECRET_KEY_BASE in .env.prod} + networks: + - internal + + relay: + image: docker.io/silberengel/gc-http-relay:${TAG:-latest} + build: + context: . + dockerfile: ./docker/server.Dockerfile + command: ["/app/bin/server"] + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1.00" + memory: 1G + reservations: + cpus: "0.50" + memory: 512M + depends_on: + postgres: + condition: service_healthy + migrator: + condition: service_completed_successfully + ports: + - "127.0.0.1:4000:4000" + environment: + DATABASE_URL: "ecto://${POSTGRES_RUNTIME_USER}:${POSTGRES_RUNTIME_PASSWORD}@${POSTGRES_HOST:-postgres}/${POSTGRES_DB}" + SECRET_KEY_BASE: ${SECRET_KEY_BASE:?set SECRET_KEY_BASE in .env.prod} + PHX_HOST: ${PHX_HOST:?set PHX_HOST in .env.prod (public hostname, no scheme)} + networks: + - internal + +networks: + internal: + +volumes: + pgdata: diff --git a/scripts/deploy_prod.sh b/scripts/deploy_prod.sh new file mode 100755 index 0000000..ba8afd5 --- /dev/null +++ b/scripts/deploy_prod.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Production Docker Compose helper — run ./scripts/deploy_prod.sh --help + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +COMPOSE="${COMPOSE:-docker compose}" +COMPOSE_FILE="${COMPOSE_FILE:-compose.prod.yml}" +ENV_FILE="${ENV_FILE:-.env.prod}" + +APP_IMAGES=(setup relay migrator) + +usage() { + cat <<'EOF' +Production Docker Compose helper (compose.prod.yml). + + ./scripts/deploy_prod.sh build build app images (setup, relay, migrator) + ./scripts/deploy_prod.sh push push those images (run docker login first) + ./scripts/deploy_prod.sh build-push build then push + ./scripts/deploy_prod.sh pull pull images + ./scripts/deploy_prod.sh up start stack (detached) + ./scripts/deploy_prod.sh deploy pull then up -d (typical on server) + ./scripts/deploy_prod.sh down stop stack + ./scripts/deploy_prod.sh ps docker compose ps + ./scripts/deploy_prod.sh logs [svc] follow logs (default: all services) + +Env: ENV_FILE (default .env.prod), COMPOSE_FILE, TAG, COMPOSE (default "docker compose") +EOF + exit "${1:-0}" +} + +require_env_file() { + if [[ ! -f "$ENV_FILE" ]]; then + echo "error: missing env file: $ENV_FILE" >&2 + echo " cp .env.prod.example .env.prod && edit, or set ENV_FILE=..." >&2 + exit 1 + fi +} + +compose() { + require_env_file + $COMPOSE --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@" +} + +cmd="${1:-}" +[[ -z "$cmd" ]] && usage 1 +[[ "$cmd" == "-h" || "$cmd" == "--help" ]] && usage 0 +shift || true + +case "$cmd" in + help) + usage 0 + ;; + build) + compose build "${APP_IMAGES[@]}" + ;; + push) + compose push "${APP_IMAGES[@]}" + ;; + build-push) + compose build "${APP_IMAGES[@]}" + compose push "${APP_IMAGES[@]}" + ;; + pull) + compose pull + ;; + up) + compose up -d + ;; + deploy) + compose pull + compose up -d + ;; + down) + compose down + ;; + ps) + compose ps "$@" + ;; + logs) + compose logs -f "$@" + ;; + *) + echo "error: unknown command: $cmd" >&2 + usage 1 + ;; +esac