name: Deploy to Prod # Intentional promotion only: push origin :prod to ship whatever image # dev already built and published for that commit. This workflow never runs # `docker compose build` - it only pulls a pre-existing SHA-tagged image from # the registry, so prod always runs byte-identical artifacts to what dev # already validated. See docker-compose.deploy.yml and CLAUDE.md. on: push: branches: [prod] jobs: deploy: name: Deploy & Smoke Test (prod) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install SSH client run: which ssh || (apt-get update -qq && apt-get install -y -qq openssh-client) - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H ${{ secrets.PROD_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - name: Deploy (pull pre-built image, no rebuild) run: | ssh -i ~/.ssh/deploy_key \ -o StrictHostKeyChecking=no \ -o ConnectTimeout=10 \ ${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }} \ 'set -e cd /opt/lisilou-portfolio git fetch origin git reset --hard origin/prod SHA=$(git rev-parse --short HEAD) echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.jerodrigged.com -u "${{ secrets.REGISTRY_USER }}" --password-stdin export IMAGE_TAG=$SHA docker compose -f docker-compose.yml -f docker-compose.deploy.yml pull docker compose -f docker-compose.yml -f docker-compose.deploy.yml up -d echo "Deployed $SHA (pulled, not built)"' - name: Wait for health check run: | echo "Waiting for services..." for i in $(seq 1 24); do if curl -sf --max-time 4 "http://${{ secrets.PROD_HOST }}:8080/health" > /dev/null 2>&1; then echo "Services are up (attempt $i)" exit 0 fi echo " Attempt $i/24 — sleeping 5s" sleep 5 done echo "Health check timed out after 2 minutes" exit 1 - name: Smoke tests run: bash scripts/smoke-test.sh "http://${{ secrets.PROD_HOST }}:8080" - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/deploy_key