The first image build failed in build-gitea: the --chmod option requires BuildKit Both Dockerfiles declare `# syntax=docker/dockerfile:1` and use `COPY --chmod`, but gcr.io/cloud-builders/docker runs the legacy builder unless DOCKER_BUILDKIT=1 is set. The image ships the buildx plugin, so setting it on the two build steps is all that is needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
4.8 KiB
YAML
133 lines
4.8 KiB
YAML
# Build the Gitea and Caddy images, push them, and roll them out.
|
|
#
|
|
# The rollout works by kicking podman-auto-update on the VM. That is why the
|
|
# quadlets track a floating :prod tag rather than a digest: AutoUpdate=registry
|
|
# compares the local digest against the registry's digest FOR A TAG, so a
|
|
# digest-pinned image would give it nothing to poll.
|
|
#
|
|
# The :$SHORT_SHA and version tags are the audit trail and the rollback targets.
|
|
|
|
substitutions:
|
|
_REGION: us-east1
|
|
_ZONE: us-east1-b
|
|
_DOMAIN: gitea.jasonmross.dev
|
|
_REPO: gitea
|
|
_VM: gitea-vm
|
|
|
|
options:
|
|
# A user-specified service account cannot write to the legacy default log
|
|
# bucket. Without this the very first build fails on storage.objects.create.
|
|
logging: CLOUD_LOGGING_ONLY
|
|
machineType: E2_HIGHCPU_8
|
|
|
|
timeout: 2400s
|
|
|
|
steps:
|
|
- id: read-versions
|
|
name: bash
|
|
script: |
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
mkdir -p /workspace/vars
|
|
tr -d '[:space:]' < image/gitea.version > /workspace/vars/gitea_version
|
|
tr -d '[:space:]' < image/caddy.version > /workspace/vars/caddy_version
|
|
tr -d '[:space:]' < image/coraza.version > /workspace/vars/coraza_version
|
|
echo "gitea=$(cat /workspace/vars/gitea_version) caddy=$(cat /workspace/vars/caddy_version) coraza=$(cat /workspace/vars/coraza_version)"
|
|
|
|
- id: build-gitea
|
|
name: gcr.io/cloud-builders/docker
|
|
# Both Dockerfiles are written for BuildKit (`# syntax=`, COPY --chmod). This
|
|
# builder image defaults to the legacy builder, which rejects --chmod.
|
|
env: [DOCKER_BUILDKIT=1]
|
|
entrypoint: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
set -euo pipefail
|
|
V=$(cat /workspace/vars/gitea_version)
|
|
IMG="${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPO}/gitea"
|
|
docker build \
|
|
--build-arg "GITEA_VERSION=$${V}" \
|
|
--tag "$${IMG}:prod" \
|
|
--tag "$${IMG}:$SHORT_SHA" \
|
|
--tag "$${IMG}:$${V}" \
|
|
image/gitea
|
|
|
|
- id: build-caddy
|
|
name: gcr.io/cloud-builders/docker
|
|
env: [DOCKER_BUILDKIT=1]
|
|
entrypoint: bash
|
|
# xcaddy runs inside the Dockerfile's golang builder stage, so the plain
|
|
# docker builder is all this step needs -- no Go toolchain out here.
|
|
args:
|
|
- -c
|
|
- |
|
|
set -euo pipefail
|
|
V=$(cat /workspace/vars/caddy_version)
|
|
C=$(cat /workspace/vars/coraza_version)
|
|
IMG="${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPO}/caddy"
|
|
# The Dockerfile asserts both the DNS and WAF modules are present, so a
|
|
# dropped --with fails the build rather than shipping an unprotected
|
|
# server. Caddy and coraza-caddy versions are coupled: coraza-caddy
|
|
# pins a minimum Caddy, and a mismatch fails here at `go get`.
|
|
docker build \
|
|
--build-arg "CADDY_VERSION=$${V}" \
|
|
--build-arg "CORAZA_VERSION=$${C}" \
|
|
--tag "$${IMG}:prod" \
|
|
--tag "$${IMG}:$SHORT_SHA" \
|
|
--tag "$${IMG}:$${V}" \
|
|
image/caddy
|
|
|
|
- id: push
|
|
name: gcr.io/cloud-builders/docker
|
|
entrypoint: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
set -euo pipefail
|
|
BASE="${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPO}"
|
|
docker push --all-tags "$${BASE}/gitea"
|
|
docker push --all-tags "$${BASE}/caddy"
|
|
# Record the digests actually published. Pulumi does not pin these, so
|
|
# the build log is where you look to find a rollback target.
|
|
docker image inspect "$${BASE}/gitea:prod" --format '{{index .RepoDigests 0}}'
|
|
docker image inspect "$${BASE}/caddy:prod" --format '{{index .RepoDigests 0}}'
|
|
|
|
- id: rollout
|
|
name: gcr.io/google.com/cloudsdktool/cloud-sdk:slim
|
|
entrypoint: bash
|
|
env:
|
|
# gcloud needs a writable HOME to generate the ephemeral SSH key it pushes
|
|
# through OS Login. The default HOME in this image is not writable.
|
|
- HOME=/workspace
|
|
args:
|
|
- -c
|
|
- |
|
|
set -euo pipefail
|
|
gcloud compute ssh "${_VM}" \
|
|
--zone="${_ZONE}" \
|
|
--tunnel-through-iap \
|
|
--quiet \
|
|
--command 'sudo systemctl start podman-auto-update.service'
|
|
|
|
- id: verify
|
|
name: gcr.io/google.com/cloudsdktool/cloud-sdk:slim
|
|
entrypoint: bash
|
|
# A build that pushes a broken image and reports success is worse than a
|
|
# failed build. Gate on the app actually answering.
|
|
args:
|
|
- -c
|
|
- |
|
|
set -euo pipefail
|
|
for i in $(seq 1 30); do
|
|
if curl -fsS --max-time 10 "https://${_DOMAIN}/api/healthz" >/dev/null; then
|
|
echo "healthz passed after $${i} attempt(s)"
|
|
exit 0
|
|
fi
|
|
echo "waiting for https://${_DOMAIN}/api/healthz ($${i}/30)"
|
|
sleep 10
|
|
done
|
|
echo "healthz never passed -- check 'journalctl -u podman-auto-update' on the VM;" >&2
|
|
echo "podman should have rolled back automatically if the new image failed to start." >&2
|
|
exit 1
|