# Generated by GitHub Copilot # # docker.yml — Build and push Docker image to Gitea Container Registry # # Triggers after CI passes on main (workflow_run) to prevent this heavyweight job # from competing with the Maven+test job on the single-capacity Pi runner. # # Produces images tagged with the short SHA and 'latest'. # Immediately deploys to the Pi production stack via docker.sock. # # Required Gitea repository secrets (Settings → Actions → Secrets): # REGISTRY_USERNAME — Gitea username (andrisenins) # REGISTRY_PASSWORD — Gitea access token (package:write + repo:write scope) name: Docker on: # Only run after CI succeeds — prevents OOM kills on the Pi from parallel jobs. workflow_run: workflows: ["CI"] types: [completed] branches: [main] workflow_dispatch: # Cancel any in-progress docker build for the same branch so stale BuildKit # containers from the previous run are cleaned up before the new build starts. concurrency: group: docker-${{ gitea.event.workflow_run.head_branch || gitea.ref }} cancel-in-progress: true env: REGISTRY: git.amlab.dev jobs: build-api: name: Build & push API image runs-on: pi-runner # For workflow_run events: only build if CI succeeded. # For workflow_dispatch (manual): always run. if: > github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' steps: - name: Checkout uses: actions/checkout@v4 with: # In a workflow_run context, checkout the commit that triggered CI. ref: ${{ gitea.event.workflow_run.head_sha || gitea.sha }} - name: Log in to Gitea Container Registry # Login BEFORE the Maven build while the Pi CPU is idle. # When login is attempted after a long Maven build the host Docker daemon # exhausts its 30s HTTP timeout on every attempt (confirmed in MYPHOTOS # task logs — same issue applies here). # NOTE: use 'if' not '&&' — act_runner uses 'bash -e', so 'cmd && break' # exits the entire script on the first non-zero cmd result. env: REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} run: | for i in 1 2 3 4 5 6 7 8 9 10; do if echo "$REGISTRY_PASSWORD" | docker login --username "$REGISTRY_USERNAME" --password-stdin "$REGISTRY"; then break fi if [ "$i" -eq 10 ]; then echo "ERROR: All 10 docker login attempts failed" exit 1 fi echo "Login attempt $i failed, retrying in 30s..." sleep 30 done # Build JAR natively (inside the job container, outside BuildKit/QEMU). # Without this, Maven runs under QEMU cross-emulation during the Docker build # RUN step, causing TLS connections to Maven Central to break after ~20 min. - name: Set up Java 21 LTS uses: actions/setup-java@v4 with: distribution: temurin java-version: '21' cache: maven # actions/setup-java installs the JDK but not Maven. # pi-runner has no pre-installed mvn — download the same version used # in backend/Dockerfile to keep the build environment consistent. - name: Install Maven 3.9.9 run: | curl -fsSL https://archive.apache.org/dist/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz \ | tar -xz -C /opt echo "/opt/apache-maven-3.9.9/bin" >> $GITHUB_PATH - name: Build JAR (native — avoids QEMU Maven download) working-directory: backend run: mvn -q -ntp clean package -DskipTests - name: Build and push API image # Use plain docker build+push (no buildx, no JS action) to avoid # docker/build-push-action@v6 crashing silently in act_runner v0.5.0. # DOCKER_BUILDKIT=0 uses legacy builder — no gRPC DeadlineExceeded on RPi. env: DOCKER_BUILDKIT: "0" SHA: ${{ gitea.event.workflow_run.head_sha || gitea.sha }} OWNER: ${{ gitea.repository_owner }} run: | SHORT_SHA="${SHA:0:7}" IMAGE="${REGISTRY}/${OWNER}/calorie-counter-api" docker build \ --target ci-image \ --build-arg JAR_FILE=target/calorie-counter-backend-1.0.0-SNAPSHOT.jar \ -t "${IMAGE}:${SHORT_SHA}" \ -t "${IMAGE}:latest" \ backend/ docker push "${IMAGE}:${SHORT_SHA}" docker push "${IMAGE}:latest" deploy: name: Deploy to Pi — pull & restart stack runs-on: pi-runner needs: [build-api] if: > github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' steps: - name: Pull and restart production stack # Run docker compose against the Pi HOST's homelab directory. # Volume paths in a `docker run` issued via the mounted socket are # resolved against the Docker DAEMON's filesystem (the Pi host), not # the job container's filesystem — standard DinD-via-socket behaviour. # The .env file in that directory supplies all production secrets so # no secrets need to be passed into this job. env: REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} run: | docker run --rm \ -v /home/andris/homelab/calorie-counter:/project \ -v /var/run/docker.sock:/var/run/docker.sock \ -e REGISTRY_PASSWORD \ -e REGISTRY_USERNAME \ -e "REGISTRY=${REGISTRY}" \ -w /project \ docker:27.5.1-cli \ sh -c ' echo "$REGISTRY_PASSWORD" | docker login \ --username "$REGISTRY_USERNAME" \ --password-stdin "$REGISTRY" && docker compose pull && docker compose up -d && echo "=== Stack status ===" && docker compose ps ' # ── Build failure notification ────────────────────────────────────────────── notify-failure: name: Notify — build failure runs-on: ubuntu-latest needs: [build-api, deploy] # Only run when something failed; skip on success or cancellation if: failure() steps: - name: Post commit comment on failure env: REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} OWNER: ${{ gitea.repository_owner }} SHA: ${{ gitea.event.workflow_run.head_sha || gitea.sha }} RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }} run: | BODY="❌ **Docker build failed** for \`${SHA:0:7}\`\n\nSee the failed run: ${RUN_URL}" curl -sf -X POST \ "https://git.amlab.dev/api/v1/repos/${OWNER}/calorie-counter/commits/${SHA}/comments" \ -H "Authorization: token ${REGISTRY_PASSWORD}" \ -H "Content-Type: application/json" \ -d "{\"body\": \"${BODY}\"}" \ && echo "Failure comment posted" \ || echo "Warning: could not post comment (non-fatal)"