name: Deploy to Production on: workflow_run: workflows: ["Build Application Image"] types: - completed branches: - main - production workflow_dispatch: inputs: image_tag: description: 'Image tag to deploy' required: true default: 'latest' env: IMAGE_NAME: jd-book-uploader IMAGE_TAG: ${{ inputs.image_tag || 'latest' }} REGISTRY: ${{ secrets.REGISTRY_URL || '' }} jobs: deploy: name: Deploy to Production runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} environment: production steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up SSH uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Configure SSH run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts chmod 600 ~/.ssh/known_hosts - name: Download image artifact if: env.REGISTRY == '' uses: actions/download-artifact@v4 with: name: docker-image workflow: build.yml run-id: ${{ github.event.workflow_run.id }} - name: Prepare deployment files run: | mkdir -p deployment/tmp # Create .env.production cat > deployment/tmp/.env.production << EOF PORT=${{ secrets.PORT || '8080' }} FRONTEND_URL=${{ secrets.FRONTEND_URL }} DB_HOST=${{ secrets.DB_HOST }} DB_PORT=${{ secrets.DB_PORT }} DB_USER=${{ secrets.DB_USER }} DB_PASSWORD=${{ secrets.DB_PASSWORD }} DB_NAME=${{ secrets.DB_NAME }} FIREBASE_PROJECT_ID=${{ secrets.FIREBASE_PROJECT_ID }} FIREBASE_STORAGE_BUCKET=${{ secrets.FIREBASE_STORAGE_BUCKET }} FIREBASE_CREDENTIALS_FILE=${{ secrets.FIREBASE_CREDENTIALS_FILE_PATH || './firebase-credentials.json' }} EOF # Create deployment script cat > deployment/tmp/deploy.sh << 'DEPLOY_SCRIPT' #!/bin/bash set -e IMAGE_NAME="${{ env.IMAGE_NAME }}" IMAGE_TAG="${{ env.IMAGE_TAG }}" CONTAINER_NAME="jd-book-uploader" set -a source .env.production set +a # Stop existing container if podman ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then podman stop "${CONTAINER_NAME}" 2>/dev/null || true podman rm "${CONTAINER_NAME}" 2>/dev/null || true fi # Load image if artifact provided if [ -f image.tar ]; then podman load -i image.tar rm -f image.tar fi # Pull from registry if configured if [ -n "${{ env.REGISTRY }}" ]; then podman pull --tls-verify=false "${{ env.REGISTRY }}/${IMAGE_NAME}:${IMAGE_TAG}" podman tag "${{ env.REGISTRY }}/${IMAGE_NAME}:${IMAGE_TAG}" "${IMAGE_NAME}:${IMAGE_TAG}" fi # Build run command PODMAN_CMD=( podman run -d --name "${CONTAINER_NAME}" --network=host --user root --restart=unless-stopped ) # Add environment variables while IFS='=' read -r key value; do [[ "$key" =~ ^#.*$ ]] && continue [[ -z "$key" ]] && continue value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//") if [ "$key" != "FIREBASE_CREDENTIALS_FILE" ]; then PODMAN_CMD+=(-e "${key}=${value}") fi done < .env.production # Mount Firebase credentials FIREBASE_CREDS="${FIREBASE_CREDENTIALS_FILE}" if [ -f "$FIREBASE_CREDS" ]; then PODMAN_CMD+=(-v "${FIREBASE_CREDS}:/app/firebase-credentials.json:ro,z") PODMAN_CMD+=(-e "FIREBASE_CREDENTIALS_FILE=/app/firebase-credentials.json") fi PODMAN_CMD+=("${IMAGE_NAME}:${IMAGE_TAG}") "${PODMAN_CMD[@]}" sleep 3 if podman ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then echo "✓ Container started" podman logs "${CONTAINER_NAME}" --tail 20 else echo "✗ Container failed" podman logs "${CONTAINER_NAME}" --tail 50 exit 1 fi DEPLOY_SCRIPT chmod +x deployment/tmp/deploy.sh - name: Transfer files run: | # Ensure remote deployment directory exists ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "mkdir -p ${{ secrets.DEPLOY_PATH }}/deployment" # Copy files explicitly — glob (*) skips dotfiles like .env.production scp deployment/tmp/deploy.sh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/deployment/ scp deployment/tmp/.env.production ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/deployment/ if [ -f image.tar ]; then scp image.tar ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/image.tar fi - name: Deploy run: | ssh -T ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << ENDSSH set -e cd ${{ secrets.DEPLOY_PATH }} if [ -f image.tar ]; then podman load -i image.tar rm -f image.tar fi if [ ! -f "${{ secrets.FIREBASE_CREDENTIALS_FILE_PATH || './firebase-credentials.json' }}" ]; then echo "Error: Firebase credentials not found" exit 1 fi if [ -n "${{ env.REGISTRY }}" ]; then echo "${{ secrets.REGISTRY_PASSWORD }}" | podman login "${{ env.REGISTRY }}" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin --tls-verify=false fi cd deployment ./deploy.sh ENDSSH - name: Verify deployment run: | sleep 5 HEALTH_URL="http://${{ secrets.DEPLOY_HOST }}:${{ secrets.PORT || '8080' }}/api/health" for i in {1..10}; do if curl -f -s "$HEALTH_URL" > /dev/null; then echo "✓ Health check passed" curl -s "$HEALTH_URL" exit 0 fi sleep 3 done echo "✗ Health check failed" exit 1