Self-hosting configuration

Every environment variable a self-hosted Gitterm reads, whether it is required, its default, and which ones you don't need.

View as markdown

This page lists every environment variable a self-hosted Gitterm deployment reads, for whoever configures the services. Each table says whether a variable is required, its default, and what it does. With Docker Compose you only fill in .env.selfhost (Docker Compose settings). On Railway, or with your own orchestration, set each service directly (Service variables). Variables for the hosted gitterm.dev service are listed under Not needed when self-hosting.

For the deploy steps, see Self-hosting.

Generate secrets

Generate each secret separately, with a different value for each:

Shell
openssl rand -hex 32

This prints 64 hexadecimal characters. ENCRYPTION_MASTER_KEY must be exactly this format. What each secret protects and how to change it is on Security.

Docker Compose settings

docker-compose.selfhost.yml reads these from .env.selfhost and passes them to the right services. Compose stops with required variable ... is missing a value if a variable marked Yes is empty.

Only these names reach the containers. Adding any other variable to .env.selfhost has no effect unless you also add it to the service's environment in the Compose file.

Address and routing

VariableRequiredDefaultWhat it does
PUBLIC_URLYesNoneThe URL users open, for example https://gitterm.example.com. No trailing slash. Compose derives every API, auth, listener and device-login URL from it
PUBLIC_HOSTYesNoneThe host part of PUBLIC_URL, with the port if the URL has one. Workspace URLs and the sign-in cookie use it. With subdomain routing, no port
GITTERM_PORTNo8888Host port the proxy listens on
ROUTING_MODENopathpath or subdomain. See Routing

Images

VariableRequiredDefaultWhat it does
GITTERM_IMAGE_TAGNolatestImage release to run, for example v<version>
GITTERM_IMAGE_PREFIXNoghcr.io/opeoginni/gittermRegistry path. Change it only for your own builds

Database

VariableRequiredDefaultWhat it does
POSTGRES_PASSWORDYesNoneDatabase password. Use a hex value: Compose puts it into the database URL without escaping
POSTGRES_DBNogittermDatabase name
POSTGRES_USERNogittermDatabase user

Secrets and encryption

VariableRequiredDefaultWhat it does
BETTER_AUTH_SECRETYesNoneSigns sign-in sessions
WORKSPACE_JWT_SECRETYesNoneSigns the tokens workspaces use to call the API
INTERNAL_API_KEYYesNoneAuthenticates proxy, listener and worker to server
ENCRYPTION_MASTER_KEYYesNone64 hex characters. Encrypts saved credentials
ENCRYPTION_MASTER_KEY_IDNoprimaryName of the active key. Change it when you rotate the key
ENCRYPTION_MASTER_KEYSOnly during key rotationEmptyJSON object of earlier keys by id, for example {"primary":"<old key>"}
ENCRYPTION_WRITE_VERSIONNov2v3 binds each encrypted value to its record. Fresh installs: v3. Existing installs: see Hardened writes
AUTH_ENCRYPT_OAUTH_TOKENSNofalsetrue encrypts login OAuth tokens. Fresh installs: true

Admin account

VariableRequiredDefaultWhat it does
ADMIN_EMAILNoNoneThe first admin account, created when the server starts. If the email already exists, that user is made an admin. Set both or neither
ADMIN_PASSWORDNoNoneAt least 8 characters. Used only when the account is created: changing it later doesn't change the password

Set both to create the first admin. Without them the admin step is skipped: there is no admin, and nobody can enable a provider.

GitHub

Set up the GitHub App, or a shared token, under Admin → Integrations. There are no environment variables for it. If an earlier version read GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY from the environment, save that App there once; the variables are no longer read.

Workspace pausing

VariableRequiredDefaultWhat it does
ENABLE_IDLE_REAPINGNotruePause workspaces after the idle timeout set in Admin → System
REAP_INTERVAL_MINUTESNo10Minutes between worker passes. Must be above 0 in Compose, where the worker loops in-process

Email

Optional. Without a provider, emails are written to the server log.

VariableRequiredDefaultWhat it does
EMAIL_PROVIDERNoautoauto uses SMTP if SMTP_HOST is set, else Resend if RESEND_API_KEY is set, else logs. Or smtp, resend
EMAIL_FROMNoGitTerm <[email protected]>Sender address
RESEND_API_KEYOnly if EMAIL_PROVIDER=resendNoneResend API key
SMTP_HOSTOnly if EMAIL_PROVIDER=smtpNoneSMTP server
SMTP_PORTNo587SMTP port. 465 uses TLS, 587 uses STARTTLS
SMTP_USEROnly if your SMTP server needs loginNoneSMTP user
SMTP_PASSOnly if your SMTP server needs loginNoneSMTP password

Startup steps

The server image runs three steps before it starts, every time: migrations, the catalog seed (providers, agent types, images, models), and the admin seed. Each waits for the database and takes a lock, so several replicas can start at once. A failed step stops the server from starting.

VariableRequiredDefaultWhat it does
SKIP_MIGRATIONSNofalsetrue skips migrations
SKIP_SEEDNofalsetrue skips the catalog seed
SKIP_ADMIN_SEEDNofalsetrue skips creating the admin account

Skip them only if you run the steps yourself, for example as a release job before you roll out new servers:

Shell
docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml run --rm --no-deps --entrypoint sh server \
  -c 'bun run /app/dist/scripts/migrate.mjs --prod && bun run /app/dist/scripts/seed.mjs --prod && bun run /app/dist/scripts/seed-admin.mjs'

Service variables

Set these when you configure each service yourself, for example on Railway. Docker Compose sets them for you from the settings above; Compose: in a row says from which. Values marked Yes must be set: most fall back to gitterm.dev or localhost defaults that are wrong for your deployment.

Use the same value for a variable on every service that reads it.

server

VariableRequiredDefaultWhat it does
NODE_ENVYesdevelopmentSet production. It enforces the secrets and secure cookies. The published image sets it
DATABASE_URLYesNonePostgreSQL connection URL
REDIS_URLYesredis://localhost:6379Redis connection URL
BASE_URLYesDerived from BASE_DOMAINPublic URL of the dashboard. Compose: PUBLIC_URL
BASE_DOMAINYesgitterm.devPublic host. Workspace URLs and the sign-in cookie domain. Compose: PUBLIC_HOST
CORS_ORIGINYesNoneOrigin trusted for sign-in. Compose: PUBLIC_URL
BETTER_AUTH_URLYesDerived from BASE_DOMAINAuth base URL. Compose: <PUBLIC_URL>/api
API_URLYeshttps://api.<BASE_DOMAIN>Public API URL. GitHub App setup links use it. Compose: <PUBLIC_URL>/api
WORKSPACE_API_URLYeshttps://api.gitterm.dev/trpcAPI URL that workspaces call back to. Must be reachable from the provider's machines. Compose: <PUBLIC_URL>/api/trpc
DEVICE_CODE_VERIFICATION_URIYeshttps://gitterm.dev/devicePage the CLI sends users to for device login. Set <PUBLIC_URL>/device
ROUTING_MODENopathpath or subdomain
DEPLOYMENT_MODENoself-hostedLeave as self-hosted. managed is the hosted service and requires billing
PORTNo8080Listen port
BETTER_AUTH_SECRETYesNoneSee Secrets and encryption
WORKSPACE_JWT_SECRETYesNoneSame
INTERNAL_API_KEYYesNoneSame
ENCRYPTION_MASTER_KEYYesNoneSame. Without it, saving or reading any credential fails
ENCRYPTION_MASTER_KEY_IDNoprimarySame
ENCRYPTION_MASTER_KEYSOnly during key rotationNoneSame
ENCRYPTION_WRITE_VERSIONNov2Same
AUTH_ENCRYPT_OAUTH_TOKENSNofalseSame
ADMIN_EMAIL, ADMIN_PASSWORDYesNoneSee Admin account
ENABLE_IDLE_REAPINGNotrueSame value as on the worker
ENABLE_USAGE_METERINGNofalseRecords usage sessions per workspace. Self-hosted deployments have no quotas, so leave it off unless you want the records
EMAIL_PROVIDER, EMAIL_FROM, RESEND_API_KEY, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSNoSee EmailSee Email. The server's own default for EMAIL_FROM is GitTerm <[email protected]>
EMAIL_REPLY_TONoNoneReply-to address. Not passed by the Compose file
SMTP_SECURENoChosen by porttrue or false to force TLS. Not passed by the Compose file
WORKLOAD_IDENTITY_ISSUER, WORKLOAD_IDENTITY_PRIVATE_KEY, WORKLOAD_IDENTITY_KEY_IDNoKey id gitterm-workload-identity-v1Older Google Cloud setup. Generate the key under Admin → Integrations → Google Cloud instead; the saved one takes precedence
INTERNAL_API_URLNoNoneOlder name, used only when WORKSPACE_API_URL is unset
SKIP_MIGRATIONS, SKIP_SEED, SKIP_ADMIN_SEEDNofalseSee Startup steps. Read by the server image's start script
DB_WAIT_TIMEOUT_MSNo60000How long each startup step waits for the database. Not passed by the Compose file
DB_WAIT_INTERVAL_MSNo2000How often it retries. Not passed by the Compose file

listener

VariableRequiredDefaultWhat it does
NODE_ENVYesdevelopmentSet production. The published image sets it
SERVER_URLYeshttp://localhost:3000Server URL including /api, on the private network. Compose: http://server:8080/api
INTERNAL_API_KEYYesNoneSame value as on the server
WORKSPACE_JWT_SECRETYesNoneSame value as on the server. The listener does not start in production without it
BASE_DOMAINYesgitterm.devBrowser origins allowed to call the listener. Compose: PUBLIC_HOST
CORS_ORIGINNoNoneCompose: PUBLIC_URL
DATABASE_URL, BETTER_AUTH_SECRETYesNoneChecks the dashboard's session for live status updates, and reads the GitHub App saved under Admin → Integrations
ENCRYPTION_MASTER_KEY, ENCRYPTION_MASTER_KEY_ID, ENCRYPTION_MASTER_KEYS, ENCRYPTION_WRITE_VERSIONYes, same as the serverSee serverDecrypts the saved GitHub App webhook secret
PORTNo8080 in the imageListen port

worker

VariableRequiredDefaultWhat it does
SERVER_URLYeshttp://localhost:3000Same as on the listener
INTERNAL_API_KEYYesNoneSame value as on the server
ENABLE_IDLE_REAPINGNotrueSame value as on the server
REAP_INTERVAL_MINUTESNo00 runs one pass and exits, for a cron scheduler such as Railway Cron. Above 0 loops forever with that many minutes between passes. Compose sets 10
DEPLOYMENT_MODENoself-hostedLeave as self-hosted
NODE_ENVNodevelopmentproduction in the published image

The worker calls the server's internal API and reads no credentials, so it doesn't need the database or encryption settings.

web

The published web image replaces these when the container starts. If you build web yourself, for example with Railway's builder, they are fixed at build time: rebuild after you change one.

VariableRequiredDefaultWhat it does
NEXT_PUBLIC_SERVER_URLYesNoneAPI URL. Compose: <PUBLIC_URL>/api
NEXT_PUBLIC_AUTH_URLYesNoneAuth URL without /api. Compose: PUBLIC_URL
NEXT_PUBLIC_LISTENER_URLYesNoneListener URL. Also shown as the webhook base on provider pages. Compose: <PUBLIC_URL>/listener
NEXT_PUBLIC_BASE_DOMAINYesgitterm.devPublic host, for workspace URLs. Compose: PUBLIC_HOST
NEXT_PUBLIC_ROUTING_MODENopathMust match the server's ROUTING_MODE
NEXT_PUBLIC_DEPLOYMENT_MODENoself-hostedLeave as self-hosted
PORT, HOSTNAMENo8080, 0.0.0.0Listen address, set in the image

proxy

VariableRequiredDefaultWhat it does
ROUTING_MODEYesNonepath or subdomain. With neither, workspace routes aren't served
BASE_DOMAINYesNonePublic host, used to match <port>-<workspace>.<BASE_DOMAIN>. Compose: PUBLIC_HOST
WEB_URLYesNonehost:port of web on the private network, for example web:8080
SERVER_URLYesNonehost:port of server, for example server:8080. No /api, unlike the listener and worker
LISTENER_URLYesNonehost:port of listener, for example listener:8080
INTERNAL_API_KEYYesNoneSame value as on the server. Sent when the proxy asks the server who may open a workspace

Not needed when self-hosting

These belong to the hosted service at gitterm.dev, or to development. Leave them unset, or at the value shown.

VariableWhereWhy you don't need it
POLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET, POLAR_ENVIRONMENT, POLAR_STARTER_PRODUCT_ID, POLAR_PRO_PRODUCT_ID, POLAR_GROWTH_PRODUCT_ID, POLAR_OVERAGE_PRICE_IDSserverBilling. Self-hosted deployments have no plans
ENABLE_BILLINGserverBilling. Leave unset
NEXT_PUBLIC_ENABLE_BILLINGwebBilling pages. Leave false
BILLING_PRODUCT_EVENT_RETENTION_DAYSserverBilling analytics
ENABLE_EMAIL_AUTH, ENABLE_GITHUB_AUTH, GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRETserverSign-in methods for the hosted service. Self-hosted sign-in is always email and password
NEXT_PUBLIC_ENABLE_EMAIL_AUTH, NEXT_PUBLIC_ENABLE_GITHUB_AUTHwebSame
POSTHOG_ERROR_TRACKING_ENABLED, POSTHOG_PROJECT_KEY, POSTHOG_HOSTserverError reporting. Off unless POSTHOG_ERROR_TRACKING_ENABLED=true
NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_HOST, POSTHOG_API_KEY, POSTHOG_PROJECT_ID, POSTHOG_API_HOST, POSTHOG_RELEASE_VERSIONweb (build)Browser analytics and source-map upload. The published image is built without them
NEXT_PUBLIC_WEB3FORMS_ACCESS_KEYwebContact form on the gitterm.dev self-host page
DISCORD_TOKEN, DISCORD_DM_CHANNEL_IDserverNotifications to the hosted service's owner. Not the Discord bot
ENCRYPTION_ALLOW_INSECURE_DEV_KEYserverDevelopment only. Ignored when NODE_ENV=production
DEBUG_PROXY_RESOLVEserverExtra logging for workspace routing
DB_MIGRATIONS_DIRserverSet by the server image

DEPLOYMENT_MODE and NEXT_PUBLIC_DEPLOYMENT_MODE must stay self-hosted. managed turns on billing and fails to start without the Polar settings.