release: 1.0.0, a changelog, one .env.example and the docs to match
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

- 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>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-30 11:36:22 -03:00
1 parent f500534ac6
commit 5bbbb85128
17 files changed
+524 -182

No files matched your search

+23 -10
View File
@@ -70,17 +70,28 @@ Cloudflare token, or you can add them by hand later.
## Step 2 — Create the application in Coolify
1. **New Resource → Docker Compose**, pointed at this repository.
1. **New Resource → Docker Compose**, pointed at this repository. It lives on
Gitea: choose **Private Repository (with Deploy Key)** and use
`git@git.lucasrguerra.dev.br:lucasrguerra/LambdaMail.git` with a key that is
registered in Gitea. An application that was created from a GitHub source
keeps cloning from GitHub until its source is changed in the Coolify UI; the
API cannot change it.
2. Coolify will read `docker-compose.yaml`. Leave the compose file alone; all
configuration is environment.
3. Under **Environment Variables**, paste the contents of
`.env.production.example` and replace every value marked `CHANGE`.
3. Create the environment on your own machine, then paste it into
**Environment Variables**:
Generate each secret separately:
```bash
scripts/init-env.sh --production # writes .env with random secrets
```
```bash
openssl rand -base64 36
```
The script lists every value that is still at a development default
(`MAIL_DOMAIN`, `PRIMARY_MAIL_HOST`, `WEBMAIL_HOST`, `PUBLIC_IPV4`,
`IMAGE_TAG`); set those in `.env`, then paste. Every variable is explained
inline in [`.env.example`](../.env.example).
`POSTGRES_PASSWORD` and `REDIS_PASSWORD` are generated as hex on purpose: they
go inside connection URIs, and the `/` and `+` of base64 break them.
`LAMBDAMAIL_MASTER_KEY` deserves particular care. It encrypts every DKIM
private key, TOTP secret and provider token in the database. **Store a copy
@@ -176,8 +187,9 @@ have propagated and the IP has some sending history.
## Upgrading
Images are published to the Gitea container registry for `linux/amd64` and `linux/arm64` on every merge
to `main`.
Images are published to the Gitea container registry for `linux/amd64` and
`linux/arm64` on every merge to `main`, and again under the version number when
a `v*` tag is pushed (see [RELEASING.md](RELEASING.md)).
```bash
docker compose pull
@@ -189,7 +201,8 @@ Deploys pull the images CI published rather than building on the server;
source instead.
Migrations run automatically on start, before the services that depend on them.
**Pin `IMAGE_TAG` to a `sha-` tag rather than leaving it on `latest`.** Not
**Pin `IMAGE_TAG` to a release (`1.0.0`) or a `sha-` tag rather than leaving it
on `latest`.** Not
only for rollback: `docker compose up` does not re-pull a tag it already has
locally, so a deployment left on `latest` will happily keep running the image
it pulled the first time while reporting success. An immutable tag changes on
+88
View File
@@ -0,0 +1,88 @@
# 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 | 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](../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. 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](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.