name: Build and Publish Docker Images on: push: tags: - "v*" - "release-*" branches: - main - production workflow_dispatch: inputs: version: description: "Version tag (e.g., 1.0.0)" required: true type: string env: REGISTRY: ghcr.io PROJECT_NAME: edh-stats jobs: build: name: Build and Push Docker Images runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver-options: image=moby/buildkit:latest - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_TOKEN || secrets.GITHUB_TOKEN }} - name: Extract version id: version run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then VERSION=${{ github.event.inputs.version }} elif [[ "${{ github.ref }}" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then VERSION=${GITHUB_REF#refs/tags/v} elif [[ "${{ github.ref }}" =~ ^refs/tags/release-(.+)$ ]]; then VERSION=${BASH_REMATCH[1]} elif [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/production" ]]; then VERSION=${{ github.ref_name }}-${{ github.sha }} else VERSION=latest fi echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT echo "BACKEND_IMAGE=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}-backend:${VERSION}" >> $GITHUB_OUTPUT echo "FRONTEND_IMAGE=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}-frontend:${VERSION}" >> $GITHUB_OUTPUT - name: Build and push backend image uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile target: production platforms: linux/amd64 push: true tags: | ${{ steps.version.outputs.BACKEND_IMAGE }} ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}-backend:latest - name: Build and push frontend image uses: docker/build-push-action@v5 with: context: ./frontend file: ./frontend/Dockerfile.svelte platforms: linux/amd64 push: true tags: | ${{ steps.version.outputs.FRONTEND_IMAGE }} ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}-frontend:latest - name: Generate deployment config id: config run: | cat > docker-compose.prod.deployed.yml << 'EOF' version: '3.8' services: postgres: image: postgres:16-alpine environment: - POSTGRES_USER=${DB_USER:-postgres} - POSTGRES_PASSWORD=${DB_PASSWORD:-edh_password} - POSTGRES_DB=${DB_NAME:-edh_stats} ports: - '${DB_PORT:-5432}:5432' volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: - CMD-SHELL - 'PGPASSWORD=${DB_PASSWORD:-edh_password} pg_isready -U ${DB_USER:-postgres} -h localhost' interval: 10s timeout: 5s retries: 5 networks: - edh-stats-network restart: unless-stopped db-migrate: image: ${{ steps.version.outputs.BACKEND_IMAGE }} depends_on: postgres: condition: service_healthy environment: - NODE_ENV=production - DB_HOST=${DB_HOST:-postgres} - DB_PORT=${DB_PORT:-5432} - DB_NAME=${DB_NAME:-edh_stats} - DB_USER=${DB_USER:-postgres} - DB_PASSWORD=${DB_PASSWORD:-edh_password} - DB_SEED=${DB_SEED:-false} command: node src/database/migrate.js migrate networks: - edh-stats-network restart: 'no' backend: image: ${{ steps.version.outputs.BACKEND_IMAGE }} depends_on: db-migrate: condition: service_completed_successfully environment: - NODE_ENV=production - DB_HOST=${DB_HOST:-postgres} - DB_PORT=${DB_PORT:-5432} - DB_NAME=${DB_NAME:-edh_stats} - DB_USER=${DB_USER:-postgres} - DB_PASSWORD=${DB_PASSWORD:-edh_password} - JWT_SECRET=${JWT_SECRET} - CORS_ORIGIN=${CORS_ORIGIN:-https://yourdomain.com} - LOG_LEVEL=${LOG_LEVEL:-warn} - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-false} restart: unless-stopped healthcheck: test: - CMD - wget - --no-verbose - --tries=1 - --spider - http://localhost:3000/api/health interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - edh-stats-network stop_grace_period: 30s frontend: image: ${{ steps.version.outputs.FRONTEND_IMAGE }} ports: - '80:80' - '443:443' depends_on: - backend restart: unless-stopped networks: - edh-stats-network volumes: postgres_data: driver: local networks: edh-stats-network: driver: bridge EOF - name: Upload deployment config uses: actions/upload-artifact@v4 with: name: deployment-config path: docker-compose.prod.deployed.yml - name: Create Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v1 with: files: docker-compose.prod.deployed.yml generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Clean up old backend images uses: actions/delete-package-versions@v5 continue-on-error: true with: owner: ${{ github.repository_owner }} package-name: ${{ env.PROJECT_NAME }}-backend package-type: container min-versions-to-keep: 10 delete-only-untagged-versions: true - name: Clean up old backend images uses: actions/delete-package-versions@v5 continue-on-error: true with: owner: ${{ github.repository_owner }} package-name: ${{ env.PROJECT_NAME }}-frontend package-type: container min-versions-to-keep: 10 delete-only-untagged-versions: true - name: Post deployment info run: | echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** ${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Backend Image:** ${{ steps.version.outputs.BACKEND_IMAGE }}" >> $GITHUB_STEP_SUMMARY echo "**Frontend Image:** ${{ steps.version.outputs.FRONTEND_IMAGE }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Pull Commands:**" >> $GITHUB_STEP_SUMMARY echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY echo "docker pull ${{ steps.version.outputs.BACKEND_IMAGE }}" >> $GITHUB_STEP_SUMMARY echo "docker pull ${{ steps.version.outputs.FRONTEND_IMAGE }}" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Cleanup:** Old images (keeping last 10 versions)" >> $GITHUB_STEP_SUMMARY