diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index a9e716d..b8ddc3b 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -1,4 +1,4 @@ -name: Build and Deploy +name: Build and Push on: push: @@ -22,9 +22,55 @@ jobs: - name: Checkout code uses: https://github.com/actions/checkout@v4 - - name: Install Docker CLI + - name: Install Docker CLI and buildx run: | - apt-get update && apt-get install -y docker.io + # Dodge apt entirely — the runner image's bookworm repos have been + # failing GPG verification with "At least one invalid signature". + # The static Docker client binary talks to the host's daemon socket + # that act_runner mounts into the job container. buildx is needed + # separately because the Dockerfile uses `--mount=type=cache`, + # which is a BuildKit-only feature. + curl -fsSL -o /tmp/docker.tgz \ + https://download.docker.com/linux/static/stable/x86_64/docker-27.5.1.tgz + tar xzf /tmp/docker.tgz -C /tmp + install -m 0755 /tmp/docker/docker /usr/local/bin/docker + rm -rf /tmp/docker /tmp/docker.tgz + + mkdir -p /usr/libexec/docker/cli-plugins + curl -fsSL -o /usr/libexec/docker/cli-plugins/docker-buildx \ + https://github.com/docker/buildx/releases/download/v0.19.3/buildx-v0.19.3.linux-amd64 + chmod +x /usr/libexec/docker/cli-plugins/docker-buildx + + docker --version + docker buildx version + + - name: Wait for Docker daemon + run: | + # act_runner mounts the host docker socket into the job, but + # the daemon side sometimes takes a moment to answer. Poll + # `docker info` until it responds so the login/build steps + # below don't race the socket and fail with "unable to + # connect to docker". + sock="${DOCKER_HOST:-unix:///var/run/docker.sock}" + echo "Using DOCKER_HOST=${sock}" + + for i in $(seq 1 30); do + if docker info >/dev/null 2>&1; then + echo "Docker daemon reachable after ${i}s" + exit 0 + fi + echo "Waiting for Docker daemon... ($i/30)" + sleep 1 + done + + echo "Docker daemon never became reachable — diagnostics:" >&2 + echo "--- ls -l /var/run/docker.sock ---" >&2 + ls -l /var/run/docker.sock 2>&1 || true + echo "--- env | grep -i docker ---" >&2 + env | grep -i docker 2>&1 || true + echo "--- docker info (verbose) ---" >&2 + docker info 2>&1 || true + exit 1 - name: Generate image tag id: tag @@ -35,19 +81,54 @@ jobs: echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Log in to container registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ secrets.REGISTRY_URL }} -u ${{ secrets.REGISTRY_USER }} --password-stdin - - - name: Set up Docker Buildx - uses: https://github.com/docker/setup-buildx-action@v3 + run: | + # Exponential backoff (2s, 4s, 8s) around docker login to + # ride out transient registry blips without failing the + # whole workflow. + attempt=1 + max_attempts=3 + while : ; do + if echo "${{ secrets.REGISTRY_PASSWORD }}" | \ + docker login "${{ secrets.REGISTRY_URL }}" \ + -u "${{ secrets.REGISTRY_USER }}" \ + --password-stdin; then + exit 0 + fi + if [ "$attempt" -ge "$max_attempts" ]; then + echo "docker login failed after $attempt attempts" >&2 + exit 1 + fi + delay=$((2 ** attempt)) + echo "docker login attempt $attempt failed; retrying in ${delay}s" + sleep "$delay" + attempt=$((attempt + 1)) + done - name: Build and push Docker image - uses: https://github.com/docker/build-push-action@v5 - with: - context: . - push: true - platforms: linux/amd64 - provenance: false - sbom: false - tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main + run: | + IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + TAG="${{ steps.tag.outputs.tag }}" + BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + attempt=1 + max_attempts=3 + while : ; do + if docker buildx build \ + --platform linux/amd64 \ + --provenance=false \ + --sbom=false \ + --build-arg BUILD_TIMESTAMP="${BUILD_TIMESTAMP}" \ + -t "${IMAGE}:${TAG}" -t "${IMAGE}:latest" \ + --push \ + .; then + exit 0 + fi + if [ "$attempt" -ge "$max_attempts" ]; then + echo "docker buildx build failed after $attempt attempts" >&2 + exit 1 + fi + delay=$((2 ** attempt)) + echo "docker buildx build attempt $attempt failed; retrying in ${delay}s" + sleep "$delay" + attempt=$((attempt + 1)) + done diff --git a/Dockerfile b/Dockerfile index 2a04830..8b36e87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1.6 # Build arguments ARG ELIXIR_VERSION=1.19.5 ARG OTP_VERSION=28.3 @@ -44,6 +45,11 @@ RUN mix release # Runtime stage FROM ${RUNNER_IMAGE} +# Build timestamp baked in by CI (--build-arg BUILD_TIMESTAMP=...). +# Only consumed in this final stage, so it never busts the earlier +# compile cache. +ARG BUILD_TIMESTAMP=unknown + ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ @@ -61,5 +67,8 @@ RUN chown -R elixir:root /app COPY --from=builder --chown=elixir:root /app/_build/prod/rel/aprsme ./ +RUN echo "${BUILD_TIMESTAMP}" > /app/BUILD_TIMESTAMP \ + && chown elixir:root /app/BUILD_TIMESTAMP + USER elixir CMD ["/app/bin/server"] \ No newline at end of file