Files
apix-mvp/scripts/deploy-bluegreen.sh
T
Carsten Rehfeld 82f0ac6007
Deploy to Production / deploy (push) Failing after 10s
ops: add CI/CD pipeline, a/b rolling deploy, Gitea Actions workflow
- .gitea/workflows/deploy.yml — push-to-main triggers rolling deploy
- scripts/deploy-bluegreen.sh — a-stack then b-stack restart; Maven runs
  in Docker (no JDK needed on runner host); Caddy reload at end
- scripts/deploy-all.ps1 — emergency manual deploy from dev machine
- infra/docker-compose.yml — a/b pairs per service; wget health checks;
  Gitea service; Prometheus/Grafana/DB ports restricted to localhost
- infra/Caddyfile — dual upstreams with health-based routing
- infra/Dockerfile.* — one per service
- infra/prometheus.yml + grafana provisioning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 14:01:12 +02:00

90 lines
3.8 KiB
Bash

#!/bin/bash
# Rolling zero-downtime deploy for APIX.
# Triggered by Gitea Actions (act_runner, host process) on push to main.
# Requires: Docker on the runner host. Maven runs inside a container.
#
# Strategy: a/b container pairs per service. Caddy health-routes between them.
# Deploy restarts a-stack then b-stack sequentially — one instance always healthy.
set -euo pipefail
INFRA_DIR="/opt/apix/infra"
ENV_FILE="/opt/apix/.env"
COMPOSE="docker compose -f $INFRA_DIR/docker-compose.yml --env-file $ENV_FILE"
log() { echo "[deploy $(date -u +%H:%M:%S)] $*"; }
wait_healthy() {
local service=$1
local container="infra-${service}-1"
local max=90
local elapsed=0
log "Waiting for $service to become healthy..."
while [ $elapsed -lt $max ]; do
status=$(docker inspect "$container" \
--format '{{.State.Health.Status}}' 2>/dev/null || echo "missing")
if [ "$status" = "healthy" ]; then
log "$service is healthy."
return 0
fi
sleep 5
elapsed=$((elapsed + 5))
done
log "ERROR: $service did not become healthy after ${max}s (last status: $status)"
docker logs "$container" --tail 20 2>&1 || true
return 1
}
# ── 1. Sync infra config to /opt/apix/infra ──────────────────────────────────
log "Syncing infra config..."
cp infra/Caddyfile "$INFRA_DIR/Caddyfile"
cp infra/docker-compose.yml "$INFRA_DIR/docker-compose.yml"
cp infra/Dockerfile.* "$INFRA_DIR/"
[ -f infra/prometheus.yml ] && cp infra/prometheus.yml "$INFRA_DIR/"
# ── 2. Build JARs (Maven runs in Docker — no JDK/Maven required on host) ──────
log "Building JARs..."
# GITHUB_WORKSPACE is set by act_runner; work dir is mounted at the same host path.
BUILD_ROOT="${GITHUB_WORKSPACE:-$(pwd)}"
docker run --rm \
-v "${BUILD_ROOT}:/workspace" \
-v "/home/deploy/gitea-runner/.m2:/root/.m2" \
-w /workspace \
maven:3.9-eclipse-temurin-21 \
mvn clean package -DskipTests -q
# ── 3. Build Docker images ────────────────────────────────────────────────────
log "Building Docker images..."
docker build -f infra/Dockerfile.registry -t apix-registry:latest . -q
docker build -f infra/Dockerfile.portal -t apix-portal:latest . -q
docker build -f infra/Dockerfile.demo -t apix-demo:latest . -q
docker build -f infra/Dockerfile.spider -t apix-spider:latest . -q
# ── 4. Rolling restart: a-stack (registry-a → portal-a + demo-a) ─────────────
log "Rolling restart: a-stack..."
$COMPOSE up -d --no-deps --force-recreate registry-a
wait_healthy "registry-a"
$COMPOSE up -d --no-deps --force-recreate portal-a demo-a
wait_healthy "portal-a"
wait_healthy "demo-a"
# ── 5. Rolling restart: b-stack ───────────────────────────────────────────────
log "Rolling restart: b-stack..."
$COMPOSE up -d --no-deps --force-recreate registry-b
wait_healthy "registry-b"
$COMPOSE up -d --no-deps --force-recreate portal-b demo-b
wait_healthy "portal-b"
wait_healthy "demo-b"
# ── 6. Spider (cron job — restart acceptable) ─────────────────────────────────
log "Restarting spider..."
$COMPOSE up -d --no-deps --force-recreate spider
# ── 7. Reload Caddy ───────────────────────────────────────────────────────────
log "Reloading Caddy..."
docker exec infra-caddy-1 caddy reload --config /etc/caddy/Caddyfile
log "Deploy complete. All services updated with zero downtime."