Build the base and CI images from a workflow instead of by hand #148

Open
opened 2026-08-15 00:56:31 +02:00 by myrmidex · 1 comment
Owner

Problem

docker/build/Dockerfile.base is published as
forge.lvl0.xyz/lvl0/fedi-feed-router-base:latest and consumed by the
production build (Dockerfile:2). Nothing in the repository builds it:

  • no workflow references Dockerfile.base or fedi-feed-router-base
  • no documentation describes how it is built or when it was last pushed
  • build.yml builds only the application image, and assumes the base already
    exists

So the base image is built and pushed by hand. Editing Dockerfile.base changes
nothing until someone remembers to rebuild, and there is no record of what is
currently in the published :latest.

#147 makes this worse by adding a second image (fedi-feed-router-ci) with the
same property, which CI then depends on to run at all.

Proposed

A workflow that builds and pushes both images, triggered on changes under
docker/build/** and manually via workflow_dispatch, following the registry
login and tagging approach already used in build.yml.

Both images get :latest and a commit-sha tag, so a specific build can be
identified and rolled back to.

Open question: tagging strategy

Consumers currently pin :latest, which means a change to a base image
silently alters every consumer's environment on their next run. That is the
behaviour that makes a hand-built image risky in the first place.

Options:

  • Keep :latest for simplicity, and accept that base changes take effect
    immediately and invisibly.
  • Have consumers pin the sha tag, making base updates an explicit commit in the
    consuming file. Safer and more traceable, but every base change then needs a
    follow-up commit in Dockerfile and ci.yml.

Worth deciding deliberately rather than inheriting :latest by default.

Acceptance criteria

  • A workflow builds and pushes both fedi-feed-router-base and
    fedi-feed-router-ci
  • It runs on changes under docker/build/** and can be dispatched manually
  • Both images are tagged :latest and with the commit sha
  • Tagging strategy for consumers decided and recorded here
  • The build and update path is documented in .claude/PLATFORM.md, since it
    is currently undocumented anywhere
  • #147 introduces Dockerfile.ci and makes CI depend on the published image
## Problem `docker/build/Dockerfile.base` is published as `forge.lvl0.xyz/lvl0/fedi-feed-router-base:latest` and consumed by the production build (`Dockerfile:2`). Nothing in the repository builds it: - no workflow references `Dockerfile.base` or `fedi-feed-router-base` - no documentation describes how it is built or when it was last pushed - `build.yml` builds only the application image, and assumes the base already exists So the base image is built and pushed by hand. Editing `Dockerfile.base` changes nothing until someone remembers to rebuild, and there is no record of what is currently in the published `:latest`. #147 makes this worse by adding a second image (`fedi-feed-router-ci`) with the same property, which CI then depends on to run at all. ## Proposed A workflow that builds and pushes both images, triggered on changes under `docker/build/**` and manually via `workflow_dispatch`, following the registry login and tagging approach already used in `build.yml`. Both images get `:latest` and a commit-sha tag, so a specific build can be identified and rolled back to. ## Open question: tagging strategy Consumers currently pin `:latest`, which means a change to a base image silently alters every consumer's environment on their next run. That is the behaviour that makes a hand-built image risky in the first place. Options: - Keep `:latest` for simplicity, and accept that base changes take effect immediately and invisibly. - Have consumers pin the sha tag, making base updates an explicit commit in the consuming file. Safer and more traceable, but every base change then needs a follow-up commit in `Dockerfile` and `ci.yml`. Worth deciding deliberately rather than inheriting `:latest` by default. ## Acceptance criteria - [ ] A workflow builds and pushes both `fedi-feed-router-base` and `fedi-feed-router-ci` - [ ] It runs on changes under `docker/build/**` and can be dispatched manually - [ ] Both images are tagged `:latest` and with the commit sha - [ ] Tagging strategy for consumers decided and recorded here - [ ] The build and update path is documented in `.claude/PLATFORM.md`, since it is currently undocumented anywhere ## Related - #147 introduces `Dockerfile.ci` and makes CI depend on the published image
myrmidex added this to the v1.5.0 milestone 2026-08-15 00:56:31 +02:00
myrmidex added the
devops
label 2026-08-15 00:56:31 +02:00
Author
Owner

The :latest problem is not theoretical

Hit during #147. Recording it here because it answers this ticket's open
tagging question with evidence rather than preference.

The CI image was rebuilt to add gd and pushed to
fedi-feed-router-ci:latest. Verified afterwards that the image was correct:

  • docker run ... php -m | grep -i gd on the local image returned gd
  • docker manifest inspect on the registry copy returned config digest
    sha256:ab8ecd4f..., matching the local image id exactly

So the registry held the right image. The runner kept using an older one
anyway: two consecutive CI runs failed with ext-gd ... missing from your system, and the loaded-extension list in the error output showed only
opcache, pcntl and sodium. Re-running the job did not help.

The runner caches mutable tags and does not re-pull them.

Resolution taken in #147: consumers pin an explicit version tag. The CI
image is now published as fedi-feed-router-ci:php8.3-1 and ci.yml pins that
tag; :latest and the commit sha are still published alongside it. The
revision suffix is bumped whenever the image contents change.

Still open for this ticket: Dockerfile:2 pins
fedi-feed-router-base:latest for the production build. That was deliberately
left alone in #147 to keep the production build path out of a CI change, but it
has exactly the same exposure — a base image rebuild silently changes what
production is built from, with no way to roll back to a known-good base. Worth
deciding whether production pins a version tag too.

## The `:latest` problem is not theoretical Hit during #147. Recording it here because it answers this ticket's open tagging question with evidence rather than preference. The CI image was rebuilt to add `gd` and pushed to `fedi-feed-router-ci:latest`. Verified afterwards that the image was correct: - `docker run ... php -m | grep -i gd` on the local image returned `gd` - `docker manifest inspect` on the registry copy returned config digest `sha256:ab8ecd4f...`, matching the local image id exactly So the registry held the right image. The runner kept using an older one anyway: two consecutive CI runs failed with `ext-gd ... missing from your system`, and the loaded-extension list in the error output showed only opcache, pcntl and sodium. Re-running the job did not help. The runner caches mutable tags and does not re-pull them. **Resolution taken in #147**: consumers pin an explicit version tag. The CI image is now published as `fedi-feed-router-ci:php8.3-1` and `ci.yml` pins that tag; `:latest` and the commit sha are still published alongside it. The revision suffix is bumped whenever the image contents change. **Still open for this ticket**: `Dockerfile:2` pins `fedi-feed-router-base:latest` for the production build. That was deliberately left alone in #147 to keep the production build path out of a CI change, but it has exactly the same exposure — a base image rebuild silently changes what production is built from, with no way to roll back to a known-good base. Worth deciding whether production pins a version tag too.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lvl0/fedi-feed-router#148
No description provided.