name: CI/CD on: push: branches: [master] pull_request: branches: [master] env: BACKEND_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/trekr-backend FRONTEND_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/trekr-frontend jobs: # ── CI: build both images on every push / PR ────────────────────────────── build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build backend image (no push) uses: docker/build-push-action@v5 with: context: ./backend push: false cache-from: type=gha,scope=backend cache-to: type=gha,scope=backend,mode=max - name: Build frontend image (no push) uses: docker/build-push-action@v5 with: context: ./frontend push: false cache-from: type=gha,scope=frontend cache-to: type=gha,scope=frontend,mode=max # ── CD part 1: push images to DockerHub (master only) ───────────────────── push: name: Push to DockerHub runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/master' && github.event_name == 'push' outputs: image_tag: ${{ github.sha }} steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to DockerHub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push backend uses: docker/build-push-action@v5 with: context: ./backend push: true tags: | ${{ env.BACKEND_IMAGE }}:latest ${{ env.BACKEND_IMAGE }}:${{ github.sha }} cache-from: type=gha,scope=backend cache-to: type=gha,scope=backend,mode=max - name: Build and push frontend uses: docker/build-push-action@v5 with: context: ./frontend push: true tags: | ${{ env.FRONTEND_IMAGE }}:latest ${{ env.FRONTEND_IMAGE }}:${{ github.sha }} cache-from: type=gha,scope=frontend cache-to: type=gha,scope=frontend,mode=max # ── CD part 2: deploy to k3s cluster ────────────────────────────────────── deploy: name: Deploy to Kubernetes runs-on: ubuntu-latest needs: push steps: - uses: actions/checkout@v4 - name: Set up kubectl uses: azure/setup-kubectl@v3 with: version: latest - name: Configure kubeconfig run: | echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > $HOME/kubeconfig.yaml chmod 600 $HOME/kubeconfig.yaml echo "KUBECONFIG=$HOME/kubeconfig.yaml" >> $GITHUB_ENV - name: Apply namespace run: kubectl apply -f k8s/namespace.yaml - name: Create / update app secrets run: | kubectl create secret generic trekr-secrets \ --namespace=trekr \ --from-literal=db-password=${{ secrets.DB_PASSWORD }} \ --from-literal=jwt-secret=${{ secrets.JWT_SECRET }} \ --save-config \ --dry-run=client -o yaml | kubectl apply -f - - name: Create DB init ConfigMap from DDL run: | kubectl create configmap trekr-db-init \ --namespace=trekr \ --from-file=01-ddl.sql=db-scripts/ddl.sql \ --save-config \ --dry-run=client -o yaml | kubectl apply -f - - name: Apply Postgres manifests run: kubectl apply -f k8s/postgres.yaml - name: Wait for Postgres to be ready run: kubectl rollout status statefulset/postgres -n trekr --timeout=120s - name: Apply backend manifests run: kubectl apply -f k8s/backend.yaml - name: Apply frontend manifests run: kubectl apply -f k8s/frontend.yaml - name: Roll out new images run: | kubectl set image deployment/backend \ backend=${{ env.BACKEND_IMAGE }}:${{ github.sha }} \ -n trekr kubectl set image deployment/frontend \ frontend=${{ env.FRONTEND_IMAGE }}:${{ github.sha }} \ -n trekr - name: Wait for rollout to complete run: | kubectl rollout status deployment/backend -n trekr --timeout=180s kubectl rollout status deployment/frontend -n trekr --timeout=60s - name: Print app URL run: | NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}' 2>/dev/null || \ kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}') echo "App available at: http://${NODE_IP}:30080"