Release hashes services/, migrations/ and config/; when images for that hash already exist it skips CI and the build and only applies the new tags. Tagging a release no longer recompiles the webmail. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
102 lines
4.6 KiB
Markdown
102 lines
4.6 KiB
Markdown
# Releasing LambdaMail
|
|
|
|
How a version gets from a commit to a server, and what runs where. The history
|
|
of what changed is in the [CHANGELOG](../CHANGELOG.md).
|
|
|
|
## The pipeline
|
|
|
|
Everything runs on **Gitea Actions** (`.gitea/workflows/`), on runners hosted on
|
|
the Oracle server.
|
|
|
|
| Workflow | Runs on | Does |
|
|
| :--- | :--- | :--- |
|
|
| `ci.yml` | every push and pull request | language lint, Go and TypeScript suites against a real Postgres, webmail tests, a Trivy scan of the repository and of each image, and the whole stack brought up with compose |
|
|
| `release.yml` | a push to `main`, a `v*` tag, or by hand | builds the six images for `linux/amd64` and `linux/arm64` after `ci.yml` passes, publishes them, and on a tag creates the release |
|
|
|
|
Nothing is published unless the full suite passes first.
|
|
|
|
### Images are reused, not rebuilt
|
|
|
|
The images are built from `services/`, `migrations/` and `config/` and nothing
|
|
else. `release.yml` hashes those three trees (the `inputs-<hash>` tag) and asks
|
|
the registry whether images for that hash already exist. If they do, it skips
|
|
the CI run and the build and only points the new tags (`latest`, `sha-<commit>`,
|
|
the version) at the existing manifests, which takes about a minute.
|
|
|
|
So these never recompile anything: a documentation or workflow change merged
|
|
to `main`, and a version tag pushed on a commit whose images were just
|
|
published. A change under the three trees rebuilds everything, as it should.
|
|
|
|
Images go to the Gitea registry, one package per service:
|
|
|
|
```
|
|
git.lucasrguerra.dev.br/lucasrguerra/lambdamail/{protocols,auth,storage,webmail,migrations,rspamd}
|
|
```
|
|
|
|
Tags on every image: `latest`, `sha-<full commit>`, and on a version tag also
|
|
`1.2.3`, `1.2` and `1`. The packages are public, so a server pulls them without
|
|
logging in.
|
|
|
|
## Cutting a release
|
|
|
|
1. Update [CHANGELOG.md](../CHANGELOG.md) and [CHANGELOG.pt-BR.md](../CHANGELOG.pt-BR.md):
|
|
move what is under *Unreleased* to a new `## [X.Y.Z] - date` section.
|
|
2. Set `"version"` in `services/{auth,storage,webmail}/package.json` and their
|
|
lockfiles to the same number.
|
|
3. Merge to `main` and wait for the `Release` run of that merge to go green.
|
|
4. Tag the merge commit and push the tag:
|
|
|
|
```bash
|
|
git tag -a v1.2.3 -m "LambdaMail 1.2.3"
|
|
git push gitea v1.2.3
|
|
```
|
|
|
|
The tag triggers `release.yml` once more. Because the commit was already
|
|
released from `main`, the images are reused: it adds the tags `1.2.3`, `1.2` and
|
|
`1` to them without building, and creates the entry under **Releases** with the
|
|
image list and the commit log since the previous tag. Tagging a commit whose
|
|
`main` run has not finished (or never ran) builds it from scratch. A tag with a hyphen (`v1.3.0-rc.1`) is marked as a pre-release.
|
|
|
|
Versions follow [Semantic Versioning](https://semver.org/): a change to a
|
|
migration, a required environment variable, or a published port is at least a
|
|
minor release, and anything that needs manual steps on upgrade is called out in
|
|
the changelog.
|
|
|
|
## Deploying a version
|
|
|
|
Coolify pulls; it does not build. See the *Upgrading* section of
|
|
[DEPLOYMENT.md](DEPLOYMENT.md) for the two-step deploy (set `IMAGE_TAG`, then
|
|
deploy). In short:
|
|
|
|
1. `IMAGE_TAG` = the release (`1.2.3`) or `sha-<commit>`. Never `latest`.
|
|
2. Deploy. Migrations run before the services that need them.
|
|
3. Check `GET /health` on `protocols` and sign in to the webmail.
|
|
|
|
To roll back, set `IMAGE_TAG` back to the previous release and deploy again.
|
|
Migrations are not reverted: write them so the previous version still runs
|
|
against the new schema.
|
|
|
|
## Runners
|
|
|
|
Two `act_runner` instances run on the Oracle server (Ampere ARM, 4 cores), each
|
|
with its own Docker-in-Docker daemon, so two jobs never share a network or a
|
|
port. The jobs run unprivileged.
|
|
|
|
- `linux/arm64` images build natively; `linux/amd64` images build under QEMU
|
|
emulation, which is slower. Layers are cached in the registry.
|
|
- The Docker daemon of each runner keeps its layer cache between runs, and the
|
|
toolcache volume keeps downloaded toolchains.
|
|
- The workspace of a job is not visible to the Docker daemon, so workflows copy
|
|
files into containers with `docker cp` instead of mounting them with `-v`.
|
|
- Windows and macOS jobs cannot run here.
|
|
|
|
## Why some actions are not used
|
|
|
|
- `aquasecurity/trivy-action` clones Trivy from the workflow's own server,
|
|
which is Gitea; Trivy runs from its official image instead.
|
|
- `actions/upload-artifact` v4+ and the `gha` build cache are GitHub services;
|
|
per-architecture images are told apart by tag, and the cache lives in the
|
|
registry.
|
|
- Workflows read `GITHUB_OUTPUT` and `GITHUB_STEP_SUMMARY` under those names:
|
|
the `GITEA_*` equivalents are not set by the runner.
|