Deploy the editor
A deployment has two images. Build both from the root of the repository: pnpm needs the workspace files.
| Image | Dockerfile | What it runs |
|---|---|---|
app | packages/api/Dockerfile | The API on PORT. It also serves the built page, and this site at /docs. The page and the API have one origin. |
migrator | packages/db/Dockerfile | dist/migrate.mjs on DATABASE_URL. It applies the migrations that are not applied yet, then stops. |
docker build -f packages/api/Dockerfile -t git8n .
docker build -f packages/db/Dockerfile -t git8n-migrator .- The
appimage runs as the usernode, listens on port 8001, and has aHEALTHCHECKon/health. - The
migratorimage 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:
| Application | Dockerfile path | Docker context path | Watch paths |
|---|---|---|---|
app | packages/api/Dockerfile | . | none: every push deploys it |
migrator | packages/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
- Run the
migratorto its end. It stops with the code 0 when the database is up to date, and it is safe to run again. - 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
| Variable | Required | What it is | Example |
|---|---|---|---|
DATABASE_URL | yes | The PostgreSQL server. | postgresql://i18n:secret@i18n-postgres:5432/i18n |
DATABASE_TLS | no | true to connect with TLS when the URL does not say so. The default is false. | true |
BETTER_AUTH_URL | yes | The 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_SECRET | yes | At 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_ID | yes | The client ID of the GitHub App, for the sign-in. | Iv23li… |
GITHUB_CLIENT_SECRET | yes | The client secret of the GitHub App. | a secret from the App settings |
I18N_GITHUB_APP_ID | yes | The ID of the GitHub App, for the repositories. | 1234567 |
I18N_GITHUB_PRIVATE_KEY | yes | The private key of the GitHub App, in PEM format. Write the newlines as they are, or as \n. | -----BEGIN RSA PRIVATE KEY-----\n… |
ADMIN_EMAILS | yes | The addresses of the admins, separated by commas. GitHub must say that each address is verified. | alice@example.com,bob@example.com |
COMMIT_EMAIL_DOMAIN | no | The domain of the address a commit gives its author. The default is users.noreply.invalid. | users.noreply.example.com |
SMTP_URL | no | The 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_FROM | no | The sender of the mails. The default is git8n <no-reply@<host of BETTER_AUTH_URL>>. | git8n <no-reply@example.com> |
REDIRECT_HOSTS | no | The 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_SLUG | no | The name of the GitHub App in its address, github.com/apps/<slug>. The page links to its installation page. | git8n |
GITHUB_WEBHOOK_SECRET | no | The 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 |
PORT | no | The port of the API. The image sets 8001. | 8001 |
WEB_ROOT | no | The 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
| Variable | Required | What it is | Example |
|---|---|---|---|
DATABASE_URL | yes | The same server as the app. For TLS, add ?sslmode=require to the URL. | postgresql://i18n:secret@i18n-postgres:5432/i18n |
Check a deployment
GET /healthanswers200and{"status":"ok"}.GET /answers the page.GET /docs/answers this site, andGET /docs/llms.txtlists its pages as Markdown.GET /i18n/meanswers401without a session.- « Se connecter avec GitHub » opens GitHub, then comes back to the editor.