name: CI/CD Pipeline (Fully Isolated DinD) on: push: branches: [ main, develop ] pull_request: branches: [ main ] env: REGISTRY: ${{ secrets.CI_HOST }}:5000 IMAGE_NAME: ${{ secrets.APP_NAME || 'sharenet' }} jobs: # Job 1: Testing - Uses DinD with multiple containers for comprehensive testing test: name: Run Tests (DinD) runs-on: [self-hosted, dind] if: github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Containerized Testing Environment run: | # Start testing environment using dedicated compose file docker compose -f docker-compose.test.yml up -d # Wait for all services to be ready echo "Waiting for testing environment to be ready..." timeout 120 bash -c 'until docker compose -f docker-compose.test.yml ps | grep -q "healthy" && docker compose -f docker-compose.test.yml ps | grep -q "Up"; do sleep 2; done' # Verify all containers are running docker compose -f docker-compose.test.yml ps - name: Install SQLx CLI in Rust container run: | docker exec ci-cd-test-rust cargo install sqlx-cli --no-default-features --features postgres - name: Validate migration files env: DATABASE_URL: postgres://postgres:postgres@localhost:5432/sharenet_test run: | # Wait for PostgreSQL to be ready echo "Waiting for PostgreSQL to be ready..." timeout 60 bash -c 'until pg_isready -h localhost -p 5432 -U postgres; do sleep 1; done' # Create test database if it doesn't exist docker exec ci-cd-test-rust sqlx database create --database-url "$DATABASE_URL" || true # Run initial migrations to set up the database docker exec ci-cd-test-rust sqlx migrate run --database-url "$DATABASE_URL" || true # Validate migration files docker exec ci-cd-test-rust ./scripts/validate_migrations.sh --verbose - name: Run backend tests working-directory: ./backend env: DATABASE_URL: postgres://postgres:postgres@localhost:5432/sharenet_test run: | # Run tests with increased parallelism for Rust docker exec ci-cd-test-rust cargo test --all --jobs 4 docker exec ci-cd-test-rust cargo clippy --all -- -D warnings docker exec ci-cd-test-rust cargo fmt --all -- --check - name: Install frontend dependencies run: | docker exec ci-cd-test-node npm ci - name: Run frontend tests run: | docker exec ci-cd-test-node npm run lint docker exec ci-cd-test-node npm run type-check docker exec ci-cd-test-node npm run build - name: Cleanup Containerized Environment if: always() run: | # Stop and remove all testing containers docker compose -f docker-compose.test.yml down # Job 2: Building - Uses DinD for isolated image building and pushing build-and-push: name: Build and Push Docker Images (DinD) needs: [test] runs-on: [self-hosted, dind] if: github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup DinD environment run: | # Start DinD container for isolated Docker operations docker run -d \ --name ci-cd-build-dind \ --privileged \ -p 2375:2375 \ -e DOCKER_TLS_CERTDIR="" \ docker:dind # Wait for DinD to be ready echo "Waiting for DinD container to be ready..." timeout 60 bash -c 'until docker exec ci-cd-build-dind docker version; do sleep 2; done' # Configure Docker for Harbor registry (needed for pushing images) docker exec ci-cd-build-dind sh -c 'echo "{\"insecure-registries\": [\"${{ secrets.CI_HOST }}:5000\"]}" > /etc/docker/daemon.json' docker exec ci-cd-build-dind sh -c 'kill -HUP 1' # Wait for Docker daemon to reload sleep 5 # Verify DinD is working docker exec ci-cd-build-dind docker version - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push backend image uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/backend:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64 - name: Build and push frontend image uses: docker/build-push-action@v5 with: context: ./frontend file: ./frontend/Dockerfile push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/frontend:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64 - name: Cleanup DinD environment if: always() run: | # Stop and remove DinD container docker stop ci-cd-build-dind || true docker rm ci-cd-build-dind || true # Job 3: Deployment - Runs directly on production runner (no DinD needed) deploy: name: Deploy to Production needs: build-and-push runs-on: [self-hosted, production] if: github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Create environment file for deployment run: | # Create environment file for this deployment echo "IMAGE_TAG=${{ github.sha }}" > .env echo "REGISTRY=${{ secrets.CI_HOST }}:5000" >> .env echo "IMAGE_NAME=${{ secrets.APP_NAME || 'sharenet' }}" >> .env echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD || 'your_secure_password_here' }}" >> .env echo "POSTGRES_USER=${{ secrets.POSTGRES_USER || 'sharenet' }}" >> .env echo "POSTGRES_DB=${{ secrets.POSTGRES_DB || 'sharenet' }}" >> .env echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER || 'sharenet' }}:${{ secrets.POSTGRES_PASSWORD || 'your_secure_password_here' }}@postgres:5432/${{ secrets.POSTGRES_DB || 'sharenet' }}" >> .env echo "NODE_ENV=production" >> .env echo "RUST_LOG=info" >> .env - name: Make scripts executable run: chmod +x scripts/*.sh - name: Configure Docker for Harbor access run: | # Configure Docker to access Harbor registry on CI Linode echo '{"insecure-registries": ["${{ secrets.CI_HOST }}:5000"]}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker # Wait for Docker to be ready timeout 30 bash -c 'until docker info; do sleep 1; done' - name: Validate migration files run: | echo "Validating migration files before deployment..." ./scripts/validate_migrations.sh --verbose || { echo "ERROR: Migration validation failed. Deployment aborted." exit 1 } - name: Pull and deploy application run: | # Pull latest images from Harbor registry echo "Pulling latest images from Harbor registry..." docker compose -f docker-compose.prod.yml pull # Deploy the application stack echo "Deploying application stack..." docker compose -f docker-compose.prod.yml up -d # Wait for all services to be healthy echo "Waiting for all services to be healthy..." timeout 120 bash -c 'until docker compose -f docker-compose.prod.yml ps | grep -q "healthy" && docker compose -f docker-compose.prod.yml ps | grep -q "Up"; do sleep 2; done' # Verify deployment echo "Verifying deployment..." docker compose -f docker-compose.prod.yml ps