Self-hosting
Deploy Gitterm on your own infrastructure with Docker Compose or Railway, choose a routing mode, enable a provider, and check that each step worked.
This page is for whoever deploys and runs a Gitterm instance, person or agent. It lists what you run and what you must decide first, then gives numbered deploy steps with a check after each one. Every environment variable, with whether it is required, is on Configuration. How credentials are stored and how to rotate secrets is on Security.
A self-hosted deployment has no plans, quotas or billing. Sign-in is always email and password. It serves only the app: your URL opens the dashboard (or sign-in), there is no landing page, pricing or legal pages, and docs links go to gitterm.dev.
What you run
The proxy serves plain HTTP. Put TLS in front of it.
Before you start
On localhost you can sign in and configure everything, but a cloud provider can't reach your machine, so workspaces won't finish setup.
Routing
Choose the routing mode before anyone creates a workspace. Each workspace's address is saved when it is created.
Use subdomain if you control DNS for the domain. For a wildcard certificate, use a reverse proxy that supports the ACME DNS-01 challenge, or Cloudflare. With subdomain, PUBLIC_HOST is a bare host name with no port.
ROUTING_MODE has to be the same on server, proxy and web (NEXT_PUBLIC_ROUTING_MODE). Docker Compose sets all three from one value.
Deploy with Docker Compose
docker-compose.selfhost.yml runs all seven services from the published images. You need Docker with Compose v2, git and openssl on the host.
-
Get the Compose file and the example settings:
Shell git clone https://github.com/OpeOginni/gitterm.git cd gitterm cp .env.selfhost.example .env.selfhost chmod 600 .env.selfhostYou should see
.env.selfhostin the directory, readable only by you. -
Generate the secrets. Run this and copy each line into
.env.selfhost, replacing the empty value with the same name:Shell for name in POSTGRES_PASSWORD BETTER_AUTH_SECRET WORKSPACE_JWT_SECRET INTERNAL_API_KEY ENCRYPTION_MASTER_KEY; do echo "$name=$(openssl rand -hex 32)" doneEach value is 64 hexadecimal characters. Use hex for
POSTGRES_PASSWORDtoo: Compose puts it into the database URL without escaping. Back upENCRYPTION_MASTER_KEYsomewhere separate from the database backups. If you lose it, the saved credentials can't be decrypted. -
Set the address, routing mode and admin account in
.env.selfhost:INI PUBLIC_URL=https://gitterm.example.com PUBLIC_HOST=gitterm.example.com ROUTING_MODE=subdomain ADMIN_EMAIL[email protected] ADMIN_PASSWORD=<at least 8 characters> # Fresh install only: store new secrets in the hardened format from the start ENCRYPTION_WRITE_VERSION=v3 AUTH_ENCRYPT_OAUTH_TOKENS=true<angle brackets>mark values you replace.PUBLIC_URLhas no trailing slash. To try Gitterm on one machine, keep the defaultsPUBLIC_URL=http://localhost:8888,PUBLIC_HOST=localhost:8888andROUTING_MODE=path. Leave the GitHub, email and startup settings empty unless you need them; see Configuration. -
Check that every required value is set:
Shell docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml config --quietYou should see no output. An error such as
required variable POSTGRES_PASSWORD is missing a valuenames the setting to fill in. -
Start the stack:
Shell docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml up -dThe first start can take a few minutes. If an image can't be pulled, Compose builds it from the checkout. Then check the services:
Shell docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml psYou should see all seven services running, and
postgres,redisandservermarkedhealthy. -
Check that the server prepared the database and created the admin:
Shell docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml logs server | grep -E 'entrypoint|seed-admin'You should see
[entrypoint] Running migrations...,Running seed...,Running admin seed...,[seed-admin] Created admin user: <your email>(orAdmin user already existson later starts) and[entrypoint] Starting server.... -
Check the routes through the proxy, from the host:
Shell curl -s http://localhost:8888/health curl -s http://localhost:8888/api/health curl -s http://localhost:8888/listener/healthYou should see
{"status":"UP","mode":"railway"},{"status":"healthy"}andOK. Replace8888if you changedGITTERM_PORT. -
Put TLS in front of the proxy for any address other than
localhost. ForwardPUBLIC_HOST(and*.PUBLIC_HOSTfor subdomain routing) to portGITTERM_PORTon the host. You should see the same three responses when you run thecurlcommands againstPUBLIC_URL.
Continue with First sign-in.
Upgrade
Back up the database first. Then pull the new images and recreate the services:
docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml pull
docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml up -dThe server applies new migrations as it starts. You should see the [entrypoint] lines from step 6 again. To run a specific release, set GITTERM_IMAGE_TAG=v<version>. To run migrations yourself instead, see Startup steps.
Deploy on Railway
The Railway template deploys the same services. Its configuration lives on Railway, not in this repository, so check the variables against Configuration after it deploys.
- Open the Gitterm Railway template and deploy it to your Railway account.
- Fill in the variables Railway asks for, including
ADMIN_EMAILandADMIN_PASSWORD. Generate each secret withopenssl rand -hex 32. You should see every service build and turn healthy in the Railway project. - Check the
servervariables.WORKSPACE_API_URLmust be<your URL>/api/trpcandDEVICE_CODE_VERIFICATION_URImust be<your URL>/device. When unset they point at gitterm.dev, which isn't your deployment. - For subdomain routing, add a wildcard custom domain such as
*.example.comto theproxyservice, and setROUTING_MODE=subdomainonserverandproxyandNEXT_PUBLIC_ROUTING_MODE=subdomainonweb. Redeploywebafter changing anyNEXT_PUBLIC_*value. You should see theproxydomain verified in Railway. - Check that the worker runs as a Railway Cron job, for example on
*/10 * * * *, withREAP_INTERVAL_MINUTESunset or0so each run does one pass and exits. You should see[idle-reaper] Mode: run-oncein its logs. - Open
https://<your domain>/health,/api/healthand/listener/health. You should see the same responses as in step 7 of the Compose deploy.
First sign-in
-
Open
<PUBLIC_URL>/loginand sign in withADMIN_EMAILandADMIN_PASSWORD.You should see the dashboard with an Admin group in the sidebar. If sign-in fails, check the step 6 log lines. The admin seed only creates the account: if the email already exists, it makes it an admin and leaves the password unchanged.
-
Enable a provider. Required before anyone can create a workspace.
-
Set up integrations if you need them. Optional.
-
Create a test workspace from the dashboard on the provider you enabled.
You should see it reach running and open in the browser. If it stays in setup, or reports
No setup status report was received, the provider's machine can't reach<PUBLIC_URL>/api/trpc.
Enable a provider
Every provider except AWS is listed under Admin → Cloud providers after the first start, disabled.
- Open Admin → Cloud providers and select the provider.
- Fill in Credentials and config using the provider's setup guide in the table below.
- Choose Save settings.
- Turn on the Enabled switch. It stays off until the settings are saved.
- If the table says the provider uses a webhook, add it in the provider's dashboard. The provider page shows the exact URL and events under Status webhook.
You should see the provider listed as Configured, without a Disabled label.
For AWS, use Add a region in the AWS section of Admin → Cloud providers. Each region is its own provider.
Webhooks go to the listener through the proxy: <PUBLIC_URL>/listener/trpc/<provider>.handleWebhook.
What each provider offers to workspaces is on Workspaces. Enabled providers also back managed sandboxes for local agents; see Sandbox providers.
Optional admin settings
None of these are needed for a working install.
GitLab and Bitbucket appear under Admin → Integrations but aren't implemented yet.
Point clients at your deployment
The CLI, SDK, OpenCode plugins and bots use the API URL, which is <PUBLIC_URL>/api behind the bundled proxy:
gitterm login --server https://gitterm.example.com/api
gitterm auth statusYou should see your account and server in the auth status output. See the CLI reference.
Verify the install
Run through this list once after deploying. Each item names the step that covers it.
<PUBLIC_URL>/health,/api/healthand/listener/healthrespond (Compose step 7, Railway step 6).- The
serverlog shows migrations, seed and admin seed finishing (Compose step 6). - You can sign in as the admin and see the Admin group (First sign-in).
- At least one provider is Configured and enabled, with its webhook added if required (Enable a provider).
- A test workspace reaches running and opens in the browser.
- The
workerlog shows[idle-reaper] Pass completedat least once. ENCRYPTION_MASTER_KEYis backed up separately from the database (Security).