Publish the image the docs have been telling people to pull

README has said `docker run ... ghcr.io/coffey-labs/ihasmail:latest` since the
Docker instructions were written, and the docs site repeats it in four places.
Nothing ever pushed that image. `docker pull` answers `denied`, because the
package does not exist: .github/workflows held ci.yml and nothing else, and
there is no reference to ghcr.io, docker/build-push or docker push anywhere in
this repo. The instructions have been wrong the whole time.

Adds the workflow that makes them true. It fires on a published release, and
by hand for a ref -- the same dispatch trigger ci.yml carries, and the only
way to build an image for the tags that predate this file.

Two architectures on native runners rather than one build under QEMU.
Emulated arm64 runs `npm ci` and the Vite build through instruction
translation, which takes tens of minutes and sometimes exhausts memory;
ubuntu-24.04-arm is free for public repositories and does it at native speed.
The cost is pushing by digest and joining the two into one manifest at the
end, which is what the third job does.

`latest` moves only for a real release. A prerelease that moved it would hand
every `:latest` deployment an unfinished build, and a dispatch run has to ask
for it deliberately.

Also documents the images in README: which tags exist, that the dated tag is
the one to pin, and that building it yourself is still fully supported --
`docker compose up --build` is unchanged and the image is a convenience, not a
new requirement.

Worth knowing before the first run: GHCR creates a new package **private**,
even for a public repository, so an anonymous pull will still be refused until
the visibility is changed by hand. That is written at the top of the workflow,
because it is the failure that looks like success.
This commit is contained in:
2026-09-02 23:22:21 -07:00
parent 93856de143
commit 1734ed0439
2 changed files with 203 additions and 0 deletions
+26
View File
@@ -94,6 +94,32 @@ Full instructions, TLS, and every environment variable:
[Installing](https://docs.ihasmail.org/install/) ·
[Configuring](https://docs.ihasmail.org/configure/).
### Container images
Published to GHCR on every release, for `linux/amd64` and `linux/arm64`:
```bash
docker pull ghcr.io/coffey-labs/ihasmail:latest
```
| Tag | What it is |
| --- | --- |
| `latest` | The newest release. Prereleases never move it |
| `2026.9.2-pr243` | One specific build — the [version](#version-numbers) with `+` written as `-`, because a Docker tag may not contain `+` |
Pin the dated tag in anything you care about. `latest` is a moving target by
definition, and rolling back to a named tag is a `docker run` rather than a
rebuild.
Building it yourself stays fully supported and is what `docker compose up
--build` above does — the image is a convenience, not a new requirement. If you
build by hand, pass the version in, because `.dockerignore` excludes `.git` and
the build cannot work out what it is:
```bash
docker build --build-arg IHASMAIL_VERSION="$(node scripts/version.mjs)" -t ihasmail:local .
```
### Running immutably
The server writes to exactly one path, the optional `SESSION_FILE`. Clear it