name: Build, Push and Deploy on: push: branches: [ main, master ] workflow_dispatch: inputs: deploy_to_production: description: 'Deploy to Production?' required: true default: 'no' type: choice options: - 'yes' - 'no' image_tag: description: 'Image tag to deploy (optional, uses latest if empty)' required: false type: string jobs: build-and-push: runs-on: ubuntu-latest outputs: image_tag: ${{ steps.vars.outputs.image_tag }} image_sha: ${{ steps.vars.outputs.image_sha }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set variables id: vars run: | echo "image_tag=$(echo ${{ gitea.sha }} | cut -c1-8)" >> $GITHUB_OUTPUT echo "image_sha=${{ gitea.sha }}" >> $GITHUB_OUTPUT echo "build_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Gitea Registry uses: docker/login-action@v3 with: registry: kode.sadateknologi.site username: ${{ gitea.repository_owner }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Build and push image uses: docker/build-push-action@v5 with: context: . push: true tags: | kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:${{ gitea.sha }} kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:${{ steps.vars.outputs.image_tag }} labels: | org.opencontainers.image.created=${{ steps.vars.outputs.build_time }} org.opencontainers.image.revision=${{ gitea.sha }} org.opencontainers.image.version=${{ steps.vars.outputs.image_tag }} deploy-staging: runs-on: ubuntu-latest needs: build-and-push if: gitea.event_name == 'push' && (gitea.ref == 'refs/heads/main' || gitea.ref == 'refs/heads/master') steps: - name: Deploy to Staging (Local) run: | echo "🚀 Starting staging deployment on local server..." # Pull the latest image echo "Pulling image: kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest" docker pull kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest # Stop and remove existing container echo "Stopping existing container..." docker stop kulakpos_web_staging || true docker rm kulakpos_web_staging || true # Clean up old images (keep last 5) docker image prune -f # Run new container echo "Starting new container..." docker run -d \ --restart unless-stopped \ --health-cmd="curl -f http://localhost:8000 || exit 1" \ --health-interval=30s \ --health-timeout=10s \ --health-retries=3 \ --name kulakpos_web_staging \ --network="host" \ -v /opt/kulakpos/staging/.env:/var/www/html/.env \ -p 8000:8000 \ -e MODE=staging \ -e APP_ENV=staging \ kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest # Check if container is running sleep 5 if docker ps | grep -q kulakpos_web_staging; then echo "✅ Staging deployment successful!" docker logs --tail 20 kulakpos_web_staging else echo "❌ Staging deployment failed! Check logs:" docker logs kulakpos_web_staging || echo "No logs available" exit 1 fi - name: Verify Staging Deployment run: | curl -f http://localhost:8000/health || curl -f http://localhost:8000 || echo 'Health check endpoint not found, but service may be running' echo "✅ Staging deployment verified!" deploy-production: runs-on: ubuntu-latest needs: build-and-push if: gitea.event_name == 'workflow_dispatch' && gitea.event.inputs.deploy_to_production == 'yes' environment: production steps: # ============================================ # STEP 1: Setup SSH Key and Configuration # ============================================ - name: Setup SSH Key and Config run: | echo "🔑 Setting up SSH authentication..." mkdir -p ~/.ssh chmod 700 ~/.ssh # Decode the base64-encoded SSH key echo "${{ secrets.PRODUCTION_SSH_KEY_BASE64 }}" | base64 -d > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa # Add host to known_hosts ssh-keyscan -H ${{ secrets.PRODUCTION_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true # Create SSH config cat > ~/.ssh/config << EOF Host kulakpos_prod HostName ${{ secrets.PRODUCTION_HOST }} User ${{ secrets.PRODUCTION_USER }} IdentityFile ~/.ssh/id_rsa StrictHostKeyChecking accept-new EOF chmod 600 ~/.ssh/config echo "✅ SSH configuration complete" - name: Debug SSH Configuration run: | echo "Current user: $(whoami)" echo "Home directory: $HOME" echo "SSH config location: $HOME/.ssh/config" echo "SSH config content:" cat $HOME/.ssh/config || echo "No SSH config found" echo "" echo "SSH key permissions:" ls -la ~/.ssh/ echo "" echo "Testing SSH connection:" ssh -v kulakpos_prod echo "Connection successful" || echo "Connection failed" - name: Determine image tag for production id: prod_image run: | if [ -n "${{ gitea.event.inputs.image_tag }}" ]; then echo "image_tag=${{ gitea.event.inputs.image_tag }}" >> $GITHUB_OUTPUT else echo "image_tag=latest" >> $GITHUB_OUTPUT fi - name: Deploy to Production Server run: | ssh kulakpos_prod << 'EOF' IMAGE_TAG="${{ steps.prod_image.outputs.image_tag }}" echo "🚀 Starting production deployment with image tag: $IMAGE_TAG" # Show current deployment echo "Current production container:" docker ps --filter name=kulakpos_web_prod # Create backup of current deployment info mkdir -p /opt/kulakpos/backups docker inspect kulakpos_web_prod > /opt/kulakpos/backups/kulakpos_backup_$(date +%Y%m%d_%H%M%S).json || true # Backup .env file if [ -f "/opt/kulakpos/.env" ]; then cp /opt/kulakpos/.env /opt/kulakpos/backups/.env_backup_$(date +%Y%m%d_%H%M%S) fi # Pull the specified image echo "Pulling image: kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG" docker pull kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG # Stop and remove existing container echo "Stopping existing container..." docker stop kulakpos_web_prod || true docker rm kulakpos_web_prod || true # Run new container (using PHP's built-in server on port 8000 like staging) echo "Starting new production container on port 8000..." docker run -d \ --restart unless-stopped \ --health-cmd="curl -f http://localhost:8000 || exit 1" \ --health-interval=30s \ --health-timeout=10s \ --health-start-period=40s \ --health-retries=3 \ --name kulakpos_web_prod \ --network="host" \ -v /opt/kulakpos/.env:/var/www/html/.env \ -e MODE=production \ -e APP_ENV=production \ -e APP_DEBUG=false \ -e APP_URL=https://prod-test.kulakpos.id \ kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG # Wait for container to be ready echo "Waiting for container to be ready..." sleep 10 # Check if container is running if docker ps | grep -q kulakpos_web_prod; then echo "✅ Production deployment successful!" echo "" echo "Container status:" docker ps --filter name=kulakpos_web_prod --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" echo "" echo "Container logs (last 20 lines):" docker logs --tail 20 kulakpos_web_prod # Test the application locally echo "" echo "Testing application locally..." curl -f http://localhost:8000 > /dev/null 2>&1 && echo "✅ Application is responding on port 8000" || echo "⚠️ Application not responding on port 8000" else echo "❌ Production deployment failed! Checking logs:" docker logs kulakpos_web_prod || echo "No logs available" # Attempt rollback to previous version if available echo "" echo "Attempting rollback to previous version..." PREVIOUS_BACKUP=$(ls -t /opt/kulakpos/backups/kulakpos_backup_*.json 2>/dev/null | head -1) if [ -f "$PREVIOUS_BACKUP" ]; then PREVIOUS_IMAGE=$(jq -r '.[0].Config.Image' $PREVIOUS_BACKUP 2>/dev/null || echo "") if [ -n "$PREVIOUS_IMAGE" ] && [ "$PREVIOUS_IMAGE" != "kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG" ]; then echo "Rolling back to: $PREVIOUS_IMAGE" docker run -d \ --restart unless-stopped \ --health-cmd="curl -f http://localhost:8000 || exit 1" \ --health-interval=30s \ --health-timeout=10s \ --health-start-period=40s \ --health-retries=3 \ --name kulakpos_web_prod \ --network="host" \ -v /opt/kulakpos/.env:/var/www/html/.env \ -e MODE=production \ -e APP_ENV=production \ -e APP_DEBUG=false \ $PREVIOUS_IMAGE echo "Rollback completed" fi fi exit 1 fi EOF - name: Verify Production Deployment run: | ssh kulakpos_prod << 'EOF' echo "🔍 Verifying production deployment..." # Check container health if docker ps --filter "name=kulakpos_web_prod" --format "{{.Status}}" | grep -q "Up"; then echo "✅ Container is running" else echo "❌ Container is not running" exit 1 fi # Check if port 8000 is listening if ss -tlnp | grep -q ":8000"; then echo "✅ Application is listening on port 8000" else echo "❌ Application is not listening on port 8000" exit 1 fi # Test application response HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" http://localhost:8000) if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then echo "✅ Application is responding with HTTP $HTTP_CODE" else echo "❌ Application returned HTTP $HTTP_CODE" exit 1 fi # Check if .env is properly mounted docker exec kulakpos_web_prod test -f /var/www/html/.env && echo "✅ .env file is mounted" || echo "⚠️ .env file not found" echo "" echo "🎉 Production deployment verification complete!" echo "Application is running at: https://prod-test.kulakpos.id" EOF - name: Send Deployment Notification if: always() run: | STATUS="${{ job.status }}" if [ "$STATUS" = "success" ]; then echo "✅ Production deployment completed successfully!" echo "🌐 Application is now running at: https://prod-test.kulakpos.id" echo "🐳 Container name: kulakpos_web_prod" echo "🔌 Port: 8000 (internal) -> 80/443 (Nginx proxy)" else echo "❌ Production deployment failed!" echo "Please check the logs above for details" exit 1 fi cleanup: runs-on: ubuntu-latest needs: [deploy-staging, deploy-production] if: always() steps: - name: Cleanup old Docker images on runners run: | docker system prune -f --filter "until=24h"