Files
LambdaMail/docs/RELEASING.md
T
Lucas RayanandClaude Sonnet 5.5 5bbbb85128
CI / Lint de idioma (R9) (push) Successful in 5s
CI / Go (protocols) (push) Successful in 2m34s
CI / TypeScript (storage) (push) Successful in 15s
CI / Testes (webmail) (push) Successful in 51s
CI / TypeScript (auth) (push) Successful in 2m39s
CI / Trivy (dependências, segredos e Dockerfiles) (push) Successful in 9s
CI / Imagem (migrations) (push) Successful in 5s
CI / Stack completa (push) Successful in 1m11s
CI / Imagem (auth) (push) Successful in 17s
CI / Imagem (protocols) (push) Successful in 7s
CI / Imagem (rspamd) (push) Successful in 5s
CI / Imagem (storage) (push) Successful in 17s
CI / Imagem (webmail) (push) Successful in 1m52s
release: 1.0.0, a changelog, one .env.example and the docs to match
- CHANGELOG.md and CHANGELOG.pt-BR.md, from the project history.
- .env.example now serves development and production; scripts/init-env.sh
  (make env) creates .env with random secrets. .env.production.example is gone.
- docs/RELEASING.md: the pipeline, how a version is cut and deployed, the runners.
- DEPLOYMENT.md and both READMEs updated for Gitea and the new env flow.
- Service versions set to 1.0.0.
- CI: the proxy network step is idempotent and the stack is torn down always,
  since the runner's Docker daemon now outlives a job.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 11:36:22 -03:00

3.8 KiB

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.

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 runs ci.yml again, builds the six images for linux/amd64 and linux/arm64, publishes them, and on a tag creates the release

Nothing is published unless the full suite passes first.

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 and 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:

    git tag -a v1.2.3 -m "LambdaMail 1.2.3"
    git push gitea v1.2.3
    

The tag triggers release.yml once more. It republishes the images under 1.2.3, 1.2 and 1 (layers are cached, so this is quick) and creates the entry under Releases with the image list and the commit log since the previous tag. A tag with a hyphen (v1.3.0-rc.1) is marked as a pre-release.

Versions follow Semantic Versioning: 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 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.