Deploying Nexus¶
Nexus is a Go binary packaged as a container image and run as a Scaleway
serverless container (nexus-<env>). One HTTP/1.1 server on port 8080 serves
every ConnectRPC surface and /healthz.
Environment variables¶
Set on the container by infra/terraform/modules/environment/main.tf.
| Variable | Description |
|---|---|
NEXUS_DATABASE_DSN |
PostgreSQL DSN for the environment's database on the shared RDB (sslmode=require). Secret. |
NEXUS_LOG_LEVEL / NEXUS_LOG_FORMAT |
debug/info/warn/error; the container uses json. |
NEXUS_LISTEN_ADDR |
Bind address. Default :8080 (PORT also honoured). |
NEXUS_AUTHZ_ADDR |
Authz base URL including scheme, e.g. https://dev.authz.digilan-edge.com. Required: nexus refuses to start without it. |
NEXUS_AUTHZ_API_KEY |
Bearer token for authz; same value as authz's AUTHZ_API_KEY. Secret. |
NEXUS_ZITADEL_ISSUER |
OIDC issuer; the JWKS is loaded from <issuer>/oauth/v2/keys. |
NEXUS_ZITADEL_PROJECT_ID |
The environment's Zitadel project id; checked against token aud. |
NEXUS_ZITADEL_URL |
Zitadel Management API base URL. |
NEXUS_ZITADEL_SERVICE_TOKEN |
PAT of the nexus-service machine user. Secret. |
NEXUS_REALTIME_URL |
Realtime Worker base URL. Required. |
NEXUS_INTERNAL_API_KEY |
Shared secret with the realtime Worker (X-Internal-Key). Required. Secret. |
NEXUS_OTA_S3_ENDPOINT, NEXUS_OTA_S3_REGION, NEXUS_OTA_S3_BUCKET |
The environment's release bucket. |
NEXUS_OTA_S3_ACCESS_KEY, NEXUS_OTA_S3_SECRET_KEY |
Read-only object-storage identity used to presign downloads. Secret. |
NEXUS_RELEASE_PUBLISHER_KEY |
X-Release-Key that CI presents to ReleasePublisherService. Secret. |
NEXUS_RELAY_ZONE_SUFFIX |
Zone for per-gateway names. Set only when relay_zone_suffix is set; empty disables per-gateway names and certificates. |
NEXUS_CLOUDFLARE_ZONE_ID, NEXUS_CLOUDFLARE_API_TOKEN |
Zone and token for per-gateway records and ACME dns-01. Token is secret. |
NEXUS_ACME_DIRECTORY_URL, NEXUS_ACME_ACCOUNT_KEY |
ACME directory (unset means Let's Encrypt staging, with a warning) and account key. Key is secret. |
NEXUS_RELAY_TUNNEL_KEY |
Secret from which per-assignment relay tunnel tokens are derived. Secret. |
NEXUS_MRWIFI_URL, NEXUS_MRWIFI_API_KEY |
Monsieur Wifi API used by enrollment, when configured. |
NEXUS_REDIS_ADDR |
Optional Redis cache. |
NEXUS_DEV_AUTH |
true disables token validation. Local development only. |
Terraform also sets NEXUS_JWT_PRIVATE_KEY; the current nexus does not read it.
What happens when something is missing:
- Without
NEXUS_AUTHZ_ADDR(and withoutNEXUS_DEV_AUTH=true) nexus exits. - Without
NEXUS_REALTIME_URLorNEXUS_INTERNAL_API_KEYnexus exits. - If the JWKS cannot be loaded at startup nexus exits.
- Without the OTA variables it logs
OTA object store not configuredand offers no agent updates. - Without the relay/ACME variables it logs that per-gateway names and certificate issuance are disabled.
Running locally¶
cd platform/nexus
docker compose up -d # Postgres :5432, Redis :6379
make dev # air hot-reload on :4001
See Local dev stack for the full stack.
Releasing¶
Only a tag runs the pipeline; merging to main deploys nothing.
On every tag CI builds and pushes three images,
rg.fr-par.scw.cloud/digilan/nexus:dev, :stg and :prd, then:
| Job | Container | When |
|---|---|---|
deploy_dev |
nexus-dev |
automatically |
deploy_stg |
nexus-stg |
manual |
deploy_prd |
nexus-prod |
manual |
A deploy job resolves the container by name and asks Scaleway to redeploy it,
which pulls the tag the container references. The prod container references
:prd (not :prod); nexus_image in environments/prod/terraform.tfvars
must match.
Authz (platform/authz) follows the same pattern, except that all three of its
deploy jobs, including dev, are manual.
Migrations¶
SQL migrations live in internal/store/pgstore/migrations/. Nexus applies them
on startup, before the HTTP server binds, then seeds the roles into authz. There
is no separate migration binary.
Background work¶
Pruning (device logs, metrics, expired tickets, abandoned relays) runs on an
hourly ticker inside the process. With min_scale = 0 it runs only while an
instance is up.