keep notes of things to build #32
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Deploy to Production | |
on: | |
push: | |
branches: | |
- main | |
workflow_dispatch: | |
env: | |
REGISTRY: ghcr.io | |
IMAGE_NAME: ${{ github.repository }} | |
jobs: | |
build-and-push: | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
packages: write | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Set up Docker Buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Log in to GitHub Container Registry | |
uses: docker/login-action@v3 | |
with: | |
registry: ${{ env.REGISTRY }} | |
username: ${{ github.actor }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build and push Docker image | |
uses: docker/build-push-action@v5 | |
with: | |
context: . | |
push: true | |
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
cache-from: type=gha | |
cache-to: type=gha,mode=max | |
deploy: | |
needs: build-and-push | |
runs-on: ubuntu-latest | |
environment: prod | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Check required secrets | |
run: | | |
if [ -z "${{ secrets.HETZNER_HOST }}" ]; then | |
echo "Error: HETZNER_HOST secret is not set" | |
exit 1 | |
fi | |
if [ -z "${{ secrets.HETZNER_USERNAME }}" ]; then | |
echo "Error: HETZNER_USERNAME secret is not set" | |
exit 1 | |
fi | |
if [ -z "${{ secrets.HETZNER_SSH_KEY }}" ]; then | |
echo "Error: HETZNER_SSH_KEY secret is not set" | |
exit 1 | |
fi | |
- name: Deploy to Hetzner | |
uses: appleboy/ssh-action@master | |
with: | |
host: ${{ secrets.HETZNER_HOST }} | |
username: ${{ secrets.HETZNER_USERNAME }} | |
key: ${{ secrets.HETZNER_SSH_KEY }} | |
script: | | |
# Navigate to app directory | |
cd ~/app | |
# Pull latest changes from git | |
if [ -d .git ]; then | |
git pull | |
else | |
git clone https://github.com/${{ github.repository }}.git . | |
fi | |
# Export environment variables for docker-compose | |
export REGISTRY="${{ env.REGISTRY }}" | |
export IMAGE_NAME="${{ env.IMAGE_NAME }}" | |
# Login to GitHub Container Registry | |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin | |
# Pull the latest image | |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
# Stop and remove existing containers | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml down | |
# Start new containers with production configuration | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d | |
# Clean up unused images | |
docker image prune -f | |
# Function to check container health | |
check_container_health() { | |
local container_name=$1 | |
local status=$(docker inspect --format='{{.State.Status}}' $container_name) | |
echo "Container $container_name status: $status" | |
[ "$status" = "running" ] | |
return $? | |
} | |
# Function to check service availability | |
check_service() { | |
local attempt=$1 | |
local max_attempts=$2 | |
echo "Attempt $attempt/$max_attempts - Checking service health..." | |
# Check container health first | |
if ! check_container_health "app-app-1"; then | |
echo "App container not running" | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=50 app | |
return 1 | |
fi | |
if ! check_container_health "app-nginx-1"; then | |
echo "Nginx container not running" | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=50 nginx | |
return 1 | |
fi | |
# Check HTTP response using Docker network | |
# Use curl inside the nginx container to check the app | |
local http_code=$(docker exec app-nginx-1 curl -s -o /dev/null -w "%{http_code}" http://app:4321) | |
if [ "$http_code" != "200" ] && [ "$http_code" != "301" ] && [ "$http_code" != "308" ]; then | |
echo "HTTP check failed with status $http_code" | |
return 1 | |
fi | |
echo "✅ Basic health check passed" | |
return 0 | |
} | |
# Initial delay to let containers start | |
echo "Waiting 45 seconds for containers to fully start..." | |
sleep 45 | |
# Verify deployment with improved checks | |
echo "Starting deployment verification..." | |
max_attempts=4 | |
attempt=1 | |
while [ $attempt -le $max_attempts ]; do | |
if check_service $attempt $max_attempts; then | |
echo "✅ Deployment verification successful!" | |
exit 0 | |
fi | |
# If we're at attempt 2, dump all container logs | |
if [ $attempt -eq 2 ]; then | |
echo "🔍 Mid-verification status check:" | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps | |
echo "📋 Container logs:" | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=100 | |
fi | |
echo "⏳ Waiting 30 seconds before next attempt..." | |
sleep 30 | |
attempt=$((attempt + 1)) | |
done | |
echo "❌ Deployment verification failed after $max_attempts attempts" | |
echo "📊 Final system status:" | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps | |
echo "📋 Final logs:" | |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs | |
exit 1 |