4 Commits
Author SHA1 Message Date
Lucas RayanandClaude Opus 5 64c9efe078 #21: keep devDependencies in the build stage regardless of NODE_ENV
The deployment sets NODE_ENV=production, and npm omits devDependencies
when it is set - so "npm ci" in the build stage installed no typescript
and "npm run build" failed with exit 127, command not found. The first
Coolify deployment died there.

The build stage now asks for them explicitly rather than depending on the
environment it happens to run in. Reproduced by building all three images
with NODE_ENV=production before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 14:08:09 -03:00
Lucas RayanandClaude Opus 5 46d421c874 #19: add Trivy scanning and multi-architecture GHCR publishing
CI gains a security job (Trivy fs: vuln, secret and misconfig) and an
image job that builds each of the four services and scans the result. The
secret scanner earns its place here: a DKIM key or Cloudflare token
committed by accident is a full compromise of every domain served.

release.yml publishes on a merge into main and on v* tags. Four services
on two architectures is eight build jobs pushing by digest, then four
publish jobs joining each pair into one manifest list, tagged latest, sha
and semver. github.repository is lowercased first because GHCR rejects an
uppercase path and this repository is lucasrguerra/LambdaMail.

Both scanners failed on first run, so this commit also fixes what they
found rather than shipping a workflow that is red on arrival:

  - All four images ran as root (DS-0002), which section 17 risk R5
    explicitly argues against. They now run unprivileged; protocols keeps
    its eight ports below 1024 through cap_net_bind_service on the binary
    rather than through being root.

  - A HIGH advisory in postcss, reached only through Next. "npm audit fix
    --force" would have resolved it by downgrading Next from 15 to 9, so
    it is pinned with an override instead.

  - The Node images copied the build stage's node_modules wholesale,
    shipping typescript, eslint and vitest into production, and the base
    image's own bundled npm accounted for the rest of the CVEs. Runtime
    dependencies now come from npm ci --omit=dev and npm is removed from
    the final layer.

  - compose-up could never have passed: the proxy network is external
    because Coolify owns it on the deploy host, and nothing creates it in
    CI, so compose refused to start the stack at all.

Verified locally: actionlint clean, all ten action refs confirmed to
exist, Trivy fs and all four image scans exit 0, the full stack reaches
Healthy on every container, and the containers run as uid 10001/1000 with
all eight privileged ports bound and SMTP answering on 25. The arm64
build itself is unverified - this machine has no buildx - and will first
be exercised by release.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 07:58:02 -03:00
Lucas Rayan 7c91182111 #1: fix webmail healthcheck - Next standalone binds IPv4-only, wget resolved localhost to ::1
Verified live via WSL Docker (docker compose up --wait): every other F0
service came up healthy, but web stayed unhealthy. Root cause was two
compounding issues, both invisible to static review:

1. Docker auto-sets HOSTNAME to the container ID; Next's standalone
   server.js honors that env var and binds only to it instead of all
   interfaces. Fixed by explicitly setting ENV HOSTNAME=0.0.0.0 in the
   Dockerfile, after Docker's automatic assignment.
2. Even bound to 0.0.0.0 (IPv4), the healthcheck's `wget http://localhost`
   resolved localhost to ::1 (IPv6) first inside the container, where
   nothing was listening. Fixed by pointing the healthcheck at
   127.0.0.1 explicitly instead of localhost.

Full stack (db, redis, migrate, auth, storage, protocols, web) now
reaches healthy end-to-end under docker compose up --wait.
2026-07-23 06:51:28 -03:00
Lucas Rayan eb09fa0ced #1: implement F0 foundation - monorepo, migrations, Go domain layer, service skeletons, CI
Stands up the LambdaMail monorepo per PLAN.md's F0 milestone: local dev
scaffolding (.env.example, Makefile, .gitignore excluding PLAN.md/docs/
.superpowers/), the full initial Postgres schema as a golang-migrate
migration, the complete Go domain layer for services/protocols (value
objects, entities, domain events - zero external imports beyond
github.com/google/uuid) with unit tests, health-checked service skeletons
for auth/storage (TypeScript) and webmail (Next.js), a local
docker-compose.yaml wiring all services together, a language-enforcement
lint script, and a GitHub Actions CI workflow.

Built via 11 sequential tasks under subagent-driven development, each with
an independent implementer + reviewer cycle. Fixes applied during review:
DomainName.IsPunycode() added to satisfy the promised interface; ioredis
default-import typing under NodeNext; next.js bumped 15.0.3->15.5.21 to
resolve high/critical CVEs; lang-lint.mjs corrected to not flag its own
banned-terms list; web service healthcheck added so compose --wait
actually waits on it; webmail Dockerfile's missing public/ directory:
protocols go.mod version aligned with CI's Go 1.22 pin.

Known follow-ups for later phases (not blocking F0): go-arch-lint isn't
wired into CI yet; live docker compose up and migration apply have not
been exercised in this dev environment (no Docker/psql available here)
and will get their first real run in CI, which does have Docker.
2026-07-23 05:05:46 -03:00