Skip to content

Deploy the editor ​

A deployment has two images. Build both from the root of the repository: pnpm needs the workspace files.

ImageDockerfileWhat it runs
apppackages/api/DockerfileThe API on PORT. It also serves the built page, and this site at /docs. The page and the API have one origin.
migratorpackages/db/Dockerfiledist/migrate.mjs on DATABASE_URL. It applies the migrations that are not applied yet, then stops.
sh
docker build -f packages/api/Dockerfile -t git8n .
docker build -f packages/db/Dockerfile -t git8n-migrator .
  • The app image runs as the user node, listens on port 8001, and has a HEALTHCHECK on /health.
  • The migrator image holds one bundle and the SQL files, on a distroless Node image, as a user that is not root. It has no shell, no pnpm and no drizzle-kit.

The migrator keeps the journal of drizzle-kit: the table drizzle.__drizzle_migrations, with the when of meta/_journal.json and the same hash of each file. A database that drizzle-kit migrate updated gets only the migrations that came after.

Dokploy ​

Two applications from the same repository and branch, with the build type Dockerfile and no build stage:

ApplicationDockerfile pathDocker context pathWatch paths
apppackages/api/Dockerfile.none: every push deploys it
migratorpackages/db/Dockerfile.packages/db/**, pnpm-lock.yaml

The migrator has no domain, and its restart policy is « none »: it stops after it applies the migrations.

Order of a deployment ​

  1. Run the migrator to its end. It stops with the code 0 when the database is up to date, and it is safe to run again.
  2. Then start or restart the app.

Run the migrator first. A new version of the app can read a column that only its migration adds.

Behind a proxy ​

Put the app behind a reverse proxy that terminates TLS and sets X-Forwarded-For to the address of the client, as Traefik does. The sign-in limits the number of requests for each client address, and it reads the address from that header. Without the header, all the clients share one limit.

Variables of the app ​

VariableRequiredWhat it isExample
DATABASE_URLyesThe PostgreSQL server.postgresql://i18n:secret@i18n-postgres:5432/i18n
DATABASE_TLSnotrue to connect with TLS when the URL does not say so. The default is false.true
BETTER_AUTH_URLyesThe address of the editor, as the browser reads it. GitHub sends the browser back to it after the sign-in.https://git8n.sakuga.dev
BETTER_AUTH_SECRETyesAt least 32 characters. It signs the sessions and encrypts the GitHub tokens. The app refuses to start without it.the output of openssl rand -hex 32
GITHUB_CLIENT_IDyesThe client ID of the GitHub App, for the sign-in.Iv23li…
GITHUB_CLIENT_SECRETyesThe client secret of the GitHub App.a secret from the App settings
I18N_GITHUB_APP_IDyesThe ID of the GitHub App, for the repositories.1234567
I18N_GITHUB_PRIVATE_KEYyesThe private key of the GitHub App, in PEM format. Write the newlines as they are, or as \n.-----BEGIN RSA PRIVATE KEY-----\n…
ADMIN_EMAILSyesThe addresses of the admins, separated by commas. GitHub must say that each address is verified.alice@example.com,bob@example.com
COMMIT_EMAIL_DOMAINnoThe domain of the address a commit gives its author. The default is users.noreply.invalid.users.noreply.example.com
SMTP_URLnoThe SMTP server that sends the magic links to invited people (DB-645). Without it, no mail leaves, and an invited person without GitHub cannot sign in.smtps://user:password@smtp.example.com:465
MAIL_FROMnoThe sender of the mails. The default is git8n <no-reply@<host of BETTER_AUTH_URL>>.git8n <no-reply@example.com>
REDIRECT_HOSTSnoThe old hosts of the editor, separated by commas. A GET to one of them answers 301 to the same path on BETTER_AUTH_URL; the other methods, such as a webhook, are served there.i18n.example.com
GITHUB_APP_SLUGnoThe name of the GitHub App in its address, github.com/apps/<slug>. The page links to its installation page.git8n
GITHUB_WEBHOOK_SECRETnoThe secret of the webhook of the GitHub App (DB-651). Without it, the API takes no event, and a change made on GitHub shows after five minutes.32 random bytes in hex
PORTnoThe port of the API. The image sets 8001.8001
WEB_ROOTnoThe folder of the built page. The image sets /app/web./app/web

A Scaleway server blocks the outbound SMTP ports 465 and 587: the connection waits and no mail leaves. Scaleway TEM also listens on 2465 and 2587, so write smtps://<project id>:<secret key>@smtp.tem.scaleway.com:2465.

The GitHub App says how to create the App and get its four values. The callback URL of the App must be BETTER_AUTH_URL followed by /api/auth/callback/github.

Variables of the migrator ​

VariableRequiredWhat it isExample
DATABASE_URLyesThe same server as the app. For TLS, add ?sslmode=require to the URL.postgresql://i18n:secret@i18n-postgres:5432/i18n

Check a deployment ​

  • GET /health answers 200 and {"status":"ok"}.
  • GET / answers the page.
  • GET /docs/ answers this site, and GET /docs/llms.txt lists its pages as Markdown.
  • GET /i18n/me answers 401 without a session.
  • « Se connecter avec GitHub » opens GitHub, then comes back to the editor.