name: SPIKE — registry build and push # THROWAWAY. Delete this file when #237 is answered, whichever way it goes. # # It exists to answer three questions that cannot be answered by reading # anything in this repository, before a design is built on assumptions about # them. #233 was designed on an assumption about the build context and broke # every Portainer deploy; this is the correction to that habit. # # 1. Can a job on this runner run `docker build` at all? The other workflows # use `services:`, which proves the runner can *start* containers, not # that a job can build images. That needs a Docker socket or buildx. # 2. Can it push to this Gitea's container registry? The packages API answers # and lists nothing, so the registry has never been used here and # "enabled" is unconfirmed. # 3. How long does the real image take to build here? Not a nice-to-know: # backend-integration.yml is manual because a job once held this runner # for 3h12m, and any design that builds on every merge to main lands on # the same runner. # # workflow_dispatch only. It cannot fire on push or pull request, so merging it # changes nothing until someone presses the button. on: workflow_dispatch: env: REGISTRY: gitea.bermudalamb.synology.me # Gitea's container registry namespaces by owner: //. IMAGE: gitea.bermudalamb.synology.me/bermudalamb/redefined-designs jobs: spike: runs-on: ubuntu-latest # The same reasoning as backend-integration.yml: not a performance budget, # a stop so that a hang costs minutes rather than hours of a runner that # everything else queues behind. timeout-minutes: 25 steps: - name: Checkout uses: actions/checkout@v4 # First, and never failing, so that a failure in any later step is still # informative rather than just "it did not work". - name: Question 1 — what can this runner do? continue-on-error: true run: | echo "--- docker client/daemon ---" docker version || echo "NO DOCKER CLI OR DAEMON" echo "--- buildx ---" docker buildx version || echo "NO BUILDX" echo "--- socket ---" ls -l /var/run/docker.sock || echo "NO DOCKER SOCKET" echo "--- disk ---" df -h / | tail -1 # Iteration 2. The first run answered Question 1 with "no": the job # container has no `docker` binary and login died with exit 127. What it # also showed is that /var/run/docker.sock IS mounted, so the daemon is # reachable and only the client is missing — which is a gap this step can # close without changing how the runner itself is configured. # # The static binary rather than apt: it is one download with no package # index to refresh and no repository to add, and only the CLI is wanted. # The daemon already exists on the other side of that socket. # # Not continue-on-error. If this fails there is nothing left to measure, # and a clear failure here is more useful than three skipped steps. - name: Install the docker CLI, since the runner has none run: | DOCKER_VERSION=27.3.1 # Resolved rather than assumed. The NAS's architecture has never been # confirmed anywhere in this repository, and hardcoding x86_64 would # waste a whole run on a machine that everything else queues behind. case "$(uname -m)" in x86_64) DOCKER_ARCH=x86_64 ;; aarch64|arm64) DOCKER_ARCH=aarch64 ;; *) echo "unsupported architecture $(uname -m) — no static docker build for it"; exit 1 ;; esac echo "runner architecture: $(uname -m) -> downloading $DOCKER_ARCH" curl -fsSL "https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz" -o /tmp/docker.tgz tar -xzf /tmp/docker.tgz -C /tmp install -m 0755 /tmp/docker/docker /usr/local/bin/docker docker version echo "--- can it reach the daemon through the socket? ---" docker info --format 'server {{.ServerVersion}}, {{.Driver}}, {{.Architecture}}' # Reported as a boolean, never printed. If this says NO, the login below # will fail and the fix is to add the secret, not to change the workflow. - name: Is a token available? continue-on-error: true run: | if [ -n "${{ secrets.GITEA_TOKEN }}" ]; then echo "GITEA_TOKEN is present" else echo "GITEA_TOKEN is EMPTY — add a repo secret with package write scope" fi - name: Question 2a — log in to the registry run: | echo "${{ secrets.GITEA_TOKEN }}" \ | docker login "$REGISTRY" -u "${{ github.actor }}" --password-stdin # A trivial image first, deliberately. It separates "can this runner build # and push anything" from "does our real Dockerfile work here" — if the # real build fails later, this having passed says the plumbing is fine and # the problem is ours. - name: Question 2b — build and push a trivial image run: | mkdir -p /tmp/spike printf 'FROM alpine:3.20\nRUN echo spike > /spike.txt\n' > /tmp/spike/Dockerfile docker build -t "$IMAGE:spike-trivial" /tmp/spike docker push "$IMAGE:spike-trivial" echo "pushed $IMAGE:spike-trivial" # The real thing, timed. The tag records the commit so that the pulled # image can be checked against what was built — which is the whole point # of the design this spike is testing. - name: Question 3 — build the real image and time it run: | SHORT_SHA="$(git rev-parse --short HEAD)" echo "building $IMAGE:spike-$SHORT_SHA" START=$(date +%s) docker build -t "$IMAGE:spike-$SHORT_SHA" . END=$(date +%s) echo "REAL BUILD TOOK $((END - START)) SECONDS" docker push "$IMAGE:spike-$SHORT_SHA" echo "pushed $IMAGE:spike-$SHORT_SHA" # Proves the commit can actually be baked in, which is the reason for the # whole exercise. In a Gitea Actions checkout .git is present, unlike in # Portainer's build context — that difference is exactly what #235 was. - name: Does the built image know its own commit? continue-on-error: true run: | SHORT_SHA="$(git rev-parse --short HEAD)" echo "git says: $SHORT_SHA" echo "image says:" docker run --rm "$IMAGE:spike-$SHORT_SHA" cat dist/buildInfo.json || echo "no stamp" - name: What to do next if: always() run: | echo "If the pushes succeeded, try pulling from Portainer:" echo " 1. Portainer > Registries > Add registry > Custom" echo " URL: $REGISTRY (username + a token with package read)" echo " 2. docker pull $IMAGE:spike-trivial" echo "Then delete this workflow and the spike-* packages. See #237."