name: Deploy to Production # Manual production deploy — pulls pre-built images from the registry on: workflow_dispatch: inputs: image_tag: description: "Docker image tag to deploy (commit SHA or version tag, e.g. v1.0.0)" required: true default: "latest" type: string env: BACKEND_IMAGE: git.koppkb.com/kopkb/my-kopkb-be FRONTEND_IMAGE: git.koppkb.com/kopkb/my-kopkb-fe DEPLOY_DIR: /home/arrahn/Project jobs: deploy: runs-on: host steps: - name: Checkout repository uses: actions/checkout@v4 with: ref: ${{ inputs.image_tag }} - name: Login to Docker registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | \ docker login git.koppkb.com \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin - name: Verify images exist in registry run: | set -e IMAGE_TAG="${{ inputs.image_tag }}" for IMAGE in "${BACKEND_IMAGE}:${IMAGE_TAG}" "${FRONTEND_IMAGE}:${IMAGE_TAG}"; do echo "Checking if image exists: ${IMAGE}" if ! docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then echo "ERROR: Image ${IMAGE} does not exist in registry!" exit 1 fi echo "✓ ${IMAGE} found" done - name: Pull Docker images run: | set -e IMAGE_TAG="${{ inputs.image_tag }}" docker pull "${BACKEND_IMAGE}:${IMAGE_TAG}" docker pull "${FRONTEND_IMAGE}:${IMAGE_TAG}" echo "✓ Images pulled successfully" - name: Sync compose file and deploy run: | set -e IMAGE_TAG="${{ inputs.image_tag }}" mkdir -p "${DEPLOY_DIR}" cp be/docker/production/docker-compose.production.yml "${DEPLOY_DIR}/docker-compose.yml" # nginx config is mounted from repo checkout path on each deploy NGINX_CONF="$(pwd)/be/docker/production/nginx/nginx.conf" if [ ! -f "${NGINX_CONF}" ]; then echo "ERROR: nginx config not found at ${NGINX_CONF}" exit 1 fi cd "${DEPLOY_DIR}" if [ ! -f .env ]; then echo "ERROR: ${DEPLOY_DIR}/.env is missing on the server" exit 1 fi export IMAGE_TAG export NGINX_CONF docker compose -f docker-compose.yml pull backend frontend docker compose -f docker-compose.yml up -d --remove-orphans echo "✓ Deployed IMAGE_TAG=${IMAGE_TAG}" - name: Verify deployment run: | set -e cd "${DEPLOY_DIR}" echo "Waiting for services to start..." sleep 10 for SERVICE in backend frontend; do STATUS=$(docker compose -f docker-compose.yml ps --status running --format '{{.Name}}' "$SERVICE" 2>/dev/null || true) if [ -z "$STATUS" ]; then echo "ERROR: ${SERVICE} is not running" docker compose -f docker-compose.yml ps docker compose -f docker-compose.yml logs --tail=50 "$SERVICE" || true exit 1 fi echo "✓ ${SERVICE} is running (${STATUS})" done BE_CONTAINER=$(docker compose -f docker-compose.yml ps -q backend) if docker exec "$BE_CONTAINER" test -f /var/www/.env; then echo "✓ Backend .env file exists" else echo "WARNING: Backend .env file not found" fi API_PORT=$(grep -E '^API_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true) FE_PORT=$(grep -E '^FE_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true) API_PORT=${API_PORT:-8080} FE_PORT=${FE_PORT:-8081} check_health() { NAME=$1 URL=$2 for i in 1 2 3 4 5 6; do if curl -sf "$URL" > /dev/null; then echo "✓ ${NAME} health check passed (${URL})" return 0 fi echo "Waiting for ${NAME}... attempt ${i}/6" sleep 5 done echo "ERROR: ${NAME} health check failed (${URL})" docker compose -f docker-compose.yml logs --tail=50 "${NAME}" || true return 1 } check_health "backend" "http://127.0.0.1:${API_PORT}/health" check_health "frontend" "http://127.0.0.1:${FE_PORT}/health"