Skip to content

refactor(api): update Dockerfile to improve build process and output … #31

refactor(api): update Dockerfile to improve build process and output …

refactor(api): update Dockerfile to improve build process and output … #31

Workflow file for this run

# Docker-based deployment to VPS via GitHub Container Registry
#
# Required GitHub Secrets:
# - SSH_HOST: VPS IP address or hostname
# - SSH_USER: SSH username
# - SSH_KEY: SSH private key for VPS access
# - ENV_DOCKER: Full contents of the production .env file
# - GCP_SA_KEY: JSON contents of a Google Cloud service account key with the
# "reCAPTCHA Enterprise Agent" role. Mounted into the api container as
# /run/secrets/gcp-credentials.json (GOOGLE_APPLICATION_CREDENTIALS).
#
# Uses the built-in GITHUB_TOKEN for GHCR auth — no PAT needed.
# The GHCR package must be set to "public" in the repo's Packages settings so
# the VPS can `docker pull` without credentials.
#
# Required GitHub Variables (Settings → Secrets and variables → Actions → Variables tab):
# - NEXT_PUBLIC_API_URL: Public backend API URL (e.g. https://ogstack.dev)
# - NEXT_PUBLIC_DEMO_PROJECT_ID: Public project ID used by the playground demo
# - NEXT_PUBLIC_DOCS_URL: Public docs URL (e.g. https://docs.ogstack.dev)
# - NEXT_PUBLIC_RECAPTCHA_SITE_KEY: reCAPTCHA Enterprise site key (public, baked into web bundle)
name: Build and Deploy
on:
push:
branches: [ prod ]
workflow_dispatch:
env:
REGISTRY: ghcr.io
APP_DIR: ~/deploy/ogstack
jobs:
build:
name: Build ${{ matrix.image }}
runs-on: ubuntu-latest
concurrency:
group: build-${{ matrix.image }}
cancel-in-progress: true
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- image: api
file: ./apps/api/Dockerfile
- image: web
file: ./apps/web/Dockerfile
- image: docs
file: ./apps/docs/Dockerfile
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set lowercase repository name
id: repo
run: echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push ${{ matrix.image }}
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.file }}
push: true
tags: ${{ env.REGISTRY }}/${{ steps.repo.outputs.name }}/${{matrix.image}}:latest
build-args: |
NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }}
NEXT_PUBLIC_DEMO_PROJECT_ID=${{ vars.NEXT_PUBLIC_DEMO_PROJECT_ID }}
NEXT_PUBLIC_DOCS_URL=${{ vars.NEXT_PUBLIC_DOCS_URL }}
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=${{ vars.NEXT_PUBLIC_RECAPTCHA_SITE_KEY }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{steps.repo.outputs.name}}/${{ matrix.image }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{steps.repo.outputs.name}}/${{ matrix.image }}:buildcache,mode=max
deploy:
name: Deploy to VPS
runs-on: ubuntu-latest
needs: build
concurrency:
group: deploy-production
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Write .env from ENV_DOCKER secret
run: |
mkdir -p deploy
printf '%s' "${{ secrets.ENV_DOCKER }}" > deploy/.env
- name: Write GCP service account credentials
run: |
printf '%s' '${{ secrets.GCP_SA_KEY }}' > deploy/gcp-credentials.json
chmod 600 deploy/gcp-credentials.json
- name: Copy docker-compose and secrets to VPS
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
source: "deploy/docker-compose.yml,deploy/.env,deploy/gcp-credentials.json"
target: ${{ env.APP_DIR }}
strip_components: 1
- name: Deploy with Docker Compose
uses: appleboy/ssh-action@v1
env:
GITHUB_REPOSITORY: ${{ github.repository }}
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
envs: GITHUB_REPOSITORY
script: |
cd ${{ env.APP_DIR }}
# GHCR requires lowercase image names.
export GITHUB_REPOSITORY=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]')
# Pull new images while old containers are still running
docker compose pull
# Recreate containers with new images (stops old, starts new)
docker compose up -d --force-recreate --remove-orphans
# Clean up old images
docker image prune -f
echo "=== Running Containers ==="
docker compose ps --format "table {{.Name}}\t{{.Status}}"
- name: Health check
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
echo "Waiting for services to start..."
sleep 15
echo "=== API Health Check ==="
curl -sf http://localhost:5000/health || echo "API not responding yet"
echo "=== Container Status ==="
docker compose -f ${{ env.APP_DIR }}/docker-compose.yml ps --format "table {{.Name}}\t{{.Status}}"
echo "=== Recent Logs ==="
docker compose -f ${{ env.APP_DIR }}/docker-compose.yml logs --tail=10 2>/dev/null || true