Skip to content

build: official container image with release and manual preview channels - #960

Merged
paulrobertlloyd merged 4 commits into
mainfrom
feat/docker-image
Oct 12, 2026
Merged

paulrobertlloyd merged 4 commits into
mainfrom
feat/docker-image

Conversation

@rmdes

@rmdes rmdes commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Why

Deploying Indiekit today means building your own image from an example config. A published runtime image makes it a config file plus docker compose up, and gives starter themes something to point at. This adds one. Happy to convert this into a proposal issue first if you would rather discuss the shape before reviewing the files.

Yours to veto

Three decisions in here are yours, and each is a small change:

  • Registry. This uses ghcr.io/getindiekit/indiekit, which needs no secrets. example-config already uses the Docker Hub-shaped name getindiekit/indiekit; switching is two lines.
  • Nightly channel. A daily multi-arch build costs your Actions minutes and leaves a main-<sha> version in the registry per run. It is one file; I can drop it, make it weekly, or add a retention step.
  • Preview channel. Manual only, severable on its own.

What

  • Dockerfile and .dockerignore: node:24-alpine (the engine floor is >=24.17), the whole workspace installed with npm ci --omit=dev --ignore-scripts, so every official plugin resolves by name from a mounted indiekit.config.js. Ignoring install scripts is safe here: the root postinstall is husky; bcrypt@6 resolves its prebuild through node-gyp-build at require time; sharp, esbuild and lightningcss resolve through optional platform packages; the lockfile carries the musl and arm64 variants of all of them. Runs as the unprivileged node user, working directory /app, entrypoint node packages/indiekit/bin/cli.js serve rather than npx, so the container can run as any --user. 128 MB.
  • .github/workflows/docker-image.yml: one reusable job that builds linux/amd64 and linux/arm64 and pushes with OCI labels, using the repository token with packages: write. Guarded to this repository, so forks never publish.
  • docker-release.yml: on release: published (and manual dispatch with a tag). I checked how releases happen here: lerna publish creates the tag and the GitHub release is created by hand afterwards, the last four all by you about a quarter of an hour after each tag, so the release event does fire. A tag pushed by the publish job's own token could not trigger a workflow, which is why the tag push is not the trigger. The channel is derived from the version string rather than the release's prerelease flag, because the beta releases here are not flagged as prereleases. Tags: <version> plus beta while the version is a prerelease; latest, <major>, <major>.<minor> once a stable release lands, with no workflow change.
  • docker-nightly.yml: daily from main, tags nightly and main-<sha>, skipped when the registry already has that sha.
  • docker-preview.yml: manual, any ref, a named channel such as preview-v1 or rc, for milestone testing without cutting a release.
  • docker-check.yml: on pull requests touching the Dockerfile, builds the amd64 image without pushing and starts the CLI as an arbitrary user, so the Dockerfile cannot rot between releases.
  • docs/docker.md, linked from the sidebar and from Get started.

No JavaScript changes, so build.yml is unaffected. Every file is root-level, so Lerna's version bumping sees nothing. create-indiekit's generated Dockerfile and compose file are untouched and out of scope; a follow-up could point its compose at the published image and drop the generated Dockerfile.

Proof

Built locally from this branch and run with MongoDB, a mounted indiekit.config.js (the Eleventy preset and the file-system store, six post types) and a mounted content/ directory, as the host user. Signed in through the browser and created a note from the Posts UI. The store received content/notes/c7c13.md, owned by the host user, and an Eleventy build of the mounted site rendered it at /notes/c7c13/ and in its feeds.

Two things the proof taught, both in the docs: the container must run as the host user or a mounted store is read-only to it, and PUBLICATION_URL must resolve from inside the container because the server fetches its own URL during sign-in.

Only the amd64 image has been started. The arm64 image builds under QEMU but has not been run; the lockfile carries the arm64 and musl native variants, so it should work, but that is not proven.

Limits

  • Third-party plugins are not in the image. Build on top of it to add them.
  • Multi-arch builds under QEMU are slow; the Actions cache is enabled.
  • The root dependencies include lerna and husky, so they ride along in the image. Moving them to devDependencies would shave the image; not touched here because it changes your development setup.

What I would most like a second opinion on

The tag policy, and whether you want the nightly at all.

@rmdes rmdes self-assigned this Sep 6, 2026
@rmdes rmdes added question Further information is requested onboarding Set-up and first run github_actions Pull requests that update GitHub Actions code labels Sep 6, 2026
@rmdes rmdes added this to the v1.0 milestone Sep 6, 2026
@rmdes
rmdes marked this pull request as ready for review September 6, 2026 16:25
@rmdes

rmdes commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

This is in preparation to a new eleventy theme and a new indiekit-quickstart repo that would depend on these images, as for the 3 starters example, they will allow us to provide a repo from which indiekit can be bootstrap in few steps.

At first it will bootstrap indiekit as it is today, with mongodb if edit/deleted etc..
but in the future we could have sqlite running the show (as other db drivers) and let the user choose right from the indiekit-quickstart how he/she wants to boot the stack

@rmdes

rmdes commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

@paulrobertlloyd

Copy link
Copy Markdown
Collaborator

I think nightly isn’t needed right now; perhaps release and manually generated preview releases is enough for now.

Also, I forget why lerna and husky are dependencies… I have vague recollections of this being needed for some reason.

@rmdes

rmdes commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Dropping the nightly. That leaves release (on release: published) and the manual preview, and docker-nightly.yml goes with it — one file plus its row in the docs table.

On lerna and husky: they were yours, both on 2022-03-25, c5c1e3d4 "ci: make lerna a dependency" and 91757e2f "ci: make husky a dependency".

They started in devDependencies (1bf21d91, e0356738) and moved that day — the same day as 44e484de "ci: make lerna play nice with workspaces", which set useWorkspaces: true. So it looks like fallout from the npm-workspaces migration rather than a runtime need.

As far as I can tell the reason no longer holds. postinstall is husky, and publish.yml installs with npm ci --ignore-scripts, so it never runs there; build.yml and deploy.yml run a plain npm ci, which installs devDependencies anyway. lerna is only invoked as npx lerna publish in that same job, where a dev dependency would resolve. I haven't tried moving them back, so treat that as a reading of the files rather than a tested claim.

Out of scope for this PR either way — the image uses --ignore-scripts, so the only cost is the two packages riding along. Happy to open a separate PR if you want them moved.

@rmdes rmdes changed the title build: official container image with release, nightly and preview channels build: official container image with release and manual preview channels Oct 12, 2026
@paulrobertlloyd

paulrobertlloyd commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

I’ve moved all the dependencies to devDependencies (and removed the sharp override which was only needed if running tests locally on Node 24; have updated .nvmrc to use Node 26).

rmdes added 4 commits October 12, 2026 23:22
Release and manually published previews cover the need; a daily multi-arch
build spends Actions minutes and leaves a `main-<sha>` version in the registry
per run for nobody in particular.

`nightly` also stops being a reserved channel name in the preview workflow,
since no workflow claims it any more.
@paulrobertlloyd
paulrobertlloyd merged commit 66ed24d into main Oct 12, 2026
3 checks passed
@paulrobertlloyd
paulrobertlloyd deleted the feat/docker-image branch October 12, 2026 21:33
@rmdes

rmdes commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for merging! No image has been published yet, since docker-release only runs on a published release and beta.31 came out before the merge. Could you start the workflow by hand for v1.0.0-beta.31, or would you rather wait for beta.32? Once the indiekit package exists, it'll need setting to public in its package settings (org admin) for docker pull ghcr.io/getindiekit/indiekit:beta to work for everyone; the quickstart is waiting on that tag.

@rmdes

rmdes commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

I wanted to test the workflow, so I ran docker-preview by hand from main to confirm it works: it did (run 38438316241), and pushed preview-31 and preview-31-7563895. So the indiekit package exists now; it just needs switching to public in its package settings. (docker-release can't build v1.0.0-beta.31 itself, as that tag predates the Dockerfile, so release images start with beta.32.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code onboarding Set-up and first run question Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants