Can I self-host Tolgee?

YES · ONE EVENING— setup effort 2 of 4

YES — it's called Tolgee. It takes one prompt, a 2048 MB VPS, and about 90 minutes. That is $49 a month you stop paying Tolgee — $588 a year on the Team plan.

Why people pay for Tolgee

Stated as the vendor would want it stated. A replacement you pick without knowing what the subscription actually buys is a replacement you abandon in a fortnight.

Tolgee sells the awkward half of localisation as a service. The platform half is a hosted place where developers, translators and the machine-translation providers meet, with the key count, the seats and the AI credits metered on one bill. The distinctive half is in-context editing: an SDK in your running application lets someone click a string on the page and fix it, which only works if the editor is reachable from wherever that application runs. Paid tiers buy headroom on keys and seats, glossaries and professional translation, and above those the things a procurement form asks for, single sign-on, ISO 27001 and a named account manager. The free tier is real rather than a trial, 500 keys and 3 seats with no card, which is why the first bill usually arrives the week a project outgrows a few hundred strings.

Tolgee plans and list prices
PlanList priceWhat it buys
Freefree500 keys, 3 seats, 10,000 machine-translation credits, all standard localization features and all integrations. No card required.
Teamthe plan this page prices against$49/mo2,000 keys and 4 seats included, plus 1,000,000 MT credits. Figure is the euro amount the page shows as 49 a month billed annually; extra capacity is pay-as-you-go at 5 a month per 1,000 keys and 8 a month per extra seat.
Business$179/mo5,000 keys and 8 seats, adding professional translation, translation-memory management, AI playground, QA checks, webhooks and more content-delivery configs. Euro amount, billed annually; extra seats 14 a month.
Advanced$499/mo20,000 keys and 20 seats, adding branching, granular permissions, labels, Slack integration and custom content storage. Euro amount, billed annually; extra seats 22 a month.
Enterprisequote onlyQuote only. Keys, seats and MT credits are all listed as negotiable, and the tier adds single sign-on, premium support, a dedicated Slack channel, an account manager, team training, ISO 27001 and GDPR paperwork.
On-Premisesquote onlyQuote only, described as monthly or annual licensing tailored to your needs. This is the paid self-hosted edition, for teams that need advanced features or more seats than the free self-hosted build gives; the community build this page ranks is a different thing and costs nothing.

Vendor list prices in USD, read from the pricing page on 2026-08-07 · confidence: low

Replaced by Tolgee

One project, named before the prompt, so you know what you are about to install.

Translation keys, in-context editing and an API your own app calls, on a hostname you own, with no key quota and no seat count.

The same repository the cloud runs, published by the same company, and the one place the in-context editing story survives self-hosting intact: the SDKs, the CLI, the REST API, the Figma plugin and the alt-click editor are all in the Apache-2.0 core. What self-hosting removes is exactly what the pricing page is built on, the key count and the seat count, and both become whatever your PostgreSQL will hold. Two costs are real. The Docker image ships with authentication disabled, so an install that does not deliberately turn it on hands every visitor a session as the administrator, and that is the first thing to check rather than the last. And the ee/ directory carries a separate non-commercial licence covering what the paid editions sell, so this is the free tier of a commercial product rather than a fully open stack.

What else we looked at

Ranked, with the reason each one placed where it did. Only the pick has a prompt on this page — the runners-up are named so the choice is visible, not hidden.

  1. Tolgeeour pickONE EVENING— setup effort 2 of 4

    Translation keys, in-context editing and an API your own app calls, on a hostname you own, with no key quota and no seat count.

    The same repository the cloud runs, published by the same company, and the one place the in-context editing story survives self-hosting intact: the SDKs, the CLI, the REST API, the Figma plugin and the alt-click editor are all in the Apache-2.0 core. What self-hosting removes is exactly what the pricing page is built on, the key count and the seat count, and both become whatever your PostgreSQL will hold. Two costs are real. The Docker image ships with authentication disabled, so an install that does not deliberately turn it on hands every visitor a session as the administrator, and that is the first thing to check rather than the last. And the ee/ directory carries a separate non-commercial licence covering what the paid editions sell, so this is the free tier of a commercial product rather than a fully open stack.

  2. WeblateONE EVENING— setup effort 2 of 4

    Continuous localization that lives in your git repositories: translators work in a browser, Weblate commits the result back.

    Second place here, and the better answer if the translations belong in a git repository rather than in a database. Weblate is GPL-3.0, older, and built around committing translated files back to your own repo, with a translator-facing workflow, quality checks and a large community of open-source projects already using it. The reasons it is second on this page are specific: it is a heavier install, and it answers a different question. Tolgee is what tolgee.io actually sells, so replacing that subscription with the vendor's own build is the like-for-like swap, and the in-context editor a developer clicks through in their own running app is Tolgee's, not Weblate's.

The swap

You're paying

Tolgee

$49/mo · $588/yr

is replaced by

You'd run

Tolgee

ONE EVENING · ~90 min to running · 2048 MB RAM

Tolgee Team · vendor list price · checked 2026-08-07 · source · confidence: low

Before you start

RAM floor
2048 MBfloor from upstream docs — not measured by us yet
Disk
10 GBthe app, its data, and room for one backup
Domain needed
yes, one A recorda hostname pointed at the box before you start — TLS needs it on the cloud path, and the local path needs none
Time budget
~90 min1–3 hours, through the first backup

The prompt

Two paths to the same Tolgee: the cloud one assumes Prompt Zero is done on a server you rent, the local one assumes nothing but a computer that can run Docker Desktop. Read whichever you pick before you paste it, which is the whole reason both are on the page instead of behind a download.

authored from upstream docs · not yet machine-verified · Claude Code

Where it runs

319 lines · 14,996 bytes

What this prompt will do
  1. Preflight
  2. Layout
  3. Secrets
  4. compose.yml
  5. Caddy and TLS
  6. Firewall
  7. Start and verify
  8. First backup and restore
  9. Updating later
  10. What will probably go wrong
  11. Out of scope

Read out of the prompt’s own step headings at build time — if the prompt changes, this list changes with it.

paste it into Claude Code in a terminal on your own machine · it runs the install over ssh vps

You are Claude Code on the user's machine. The user has completed Prompt Zero: `ssh vps` works,
Docker and Caddy are installed, the firewall is default-deny.

Run every command in this prompt on the server over `ssh vps` unless the step says otherwise.

Install Tolgee 3.218.0 on that server, reachable at https://<DOMAIN>, behind the existing Caddy
with automatic TLS.

## 1. Preflight

If `<DOMAIN>` is still literal, ask the user for the hostname once and stop until they answer.
Its A record must already point at this server. Say this when you ask: the hostname becomes
`TOLGEE_FRONT_END_URL` and the `apiUrl` in every application wired to this server, so moving it
later means editing each one.

Tolgee needs 2048 MB of RAM available and 10 GB free on /srv: a JVM beside a PostgreSQL, with an
in-memory cache the image's docker profile sizes at a million entries. Both images publish amd64
and arm64. Measure all four first:

```bash
free -m | awk '/^Mem:/ {print $7 " MB available of " $2 " MB"}'
df -BG --output=avail /srv | tail -1
dpkg --print-architecture
dig +short <DOMAIN>
```

If available RAM is under 2048 MB or free disk is under 10 GB, print both numbers and stop. Do
not install and hope. If `dig +short` prints nothing, print that and stop: Caddy cannot get a
certificate for a name that does not resolve, and failures count against a rate limit.

## 2. Layout

```bash
sudo install -d -m 750 -o $(id -u) -g $(id -g) /srv/tolgee /srv/tolgee/backups
sudo install -d -m 700 /srv/tolgee/postgres /srv/tolgee/data
ls -la /srv/tolgee
```

Assert: `ls -la` shows `backups` owned by the login user, and `postgres` and `data` at mode
`drwx------` owned by root. Leave both alone. The PostgreSQL image chowns its own directory on
first start, and the Tolgee container runs as root and writes `data` itself.

## 3. Secrets

Three secrets, all generated here on the server. `DB_PASSWORD` is the PostgreSQL password.
`TOLGEE_AUTHENTICATION_JWT_SECRET` signs the session tokens, and upstream requires at least 32
characters. `TOLGEE_AUTHENTICATION_INITIAL_PASSWORD` is the password of the `admin` account
Tolgee creates on first start; setting it here means Tolgee does not invent one and write it to a
file inside the container. Do not print any of the three and keep them out of every log line.

```bash
umask 077
cat > /srv/tolgee/.env <<EOF
TOLGEE_FRONT_END_URL=https://<DOMAIN>
DB_PASSWORD=$(openssl rand -hex 32)
TOLGEE_AUTHENTICATION_JWT_SECRET=$(openssl rand -hex 32)
TOLGEE_AUTHENTICATION_INITIAL_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 /srv/tolgee/.env
umask 022
ls -l /srv/tolgee/.env
```

Assert: the file exists with mode `-rw-------` and the login user's name twice. Replace
`<DOMAIN>` on the first line with the real hostname before writing; upstream states that leaving
`TOLGEE_FRONT_END_URL` unset on a publicly reachable instance is a security problem. Docker
Compose reads this file both for the `${...}` substitutions in compose.yml and as `env_file`.

## 4. compose.yml

```bash
cat > /srv/tolgee/compose.yml <<'EOF'
# Tolgee · the deterministic fallback. Authored by caniselfhostit from the
# upstream documentation, not copied from a repository:
#   running with docker . https://docs.tolgee.io/platform/self_hosting/running_with_docker
#   configuration ....... https://docs.tolgee.io/platform/self_hosting/configuration
#   image build ......... https://github.com/tolgee/tolgee-platform/blob/v3.218.0/docker/app/Dockerfile
#
# Two services: Tolgee and the PostgreSQL holding every key, translation and
# account. The tolgee/tolgee image is built on postgres:13 and starts that
# bundled database itself unless told not to. Upstream deprecated it and removes
# it in v4, so this file uses the external database shape their own docs
# document, on the PostgreSQL 17 their example names. Digests read from the
# registries on 2026-08-07; both publish amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.

services:
  postgres:
    image: postgres:17.10-alpine@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193
    container_name: tolgee-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: tolgee
      POSTGRES_USER: tolgee
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - /srv/tolgee/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tolgee -d tolgee"]
      interval: 10s
      retries: 12
    # No `ports:` at all: 5432 is reachable only from the other container.

  tolgee:
    image: tolgee/tolgee:v3.218.0@sha256:1955a9e28fb247bc0404809432d0ee179b43966f5080b8100a70333375db4382
    container_name: tolgee
    restart: unless-stopped
    env_file: /srv/tolgee/.env
    environment:
      # The docker profile in this image ships this false, and false means no
      # login screen and every caller already an administrator.
      TOLGEE_AUTHENTICATION_ENABLED: "true"
      # Nobody signs themselves up. New people arrive by invitation.
      TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED: "false"
      # Do not start the PostgreSQL bundled in this image.
      TOLGEE_POSTGRES_AUTOSTART_ENABLED: "false"
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tolgee
      SPRING_DATASOURCE_USERNAME: tolgee
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      # Upstream sends daily project, language and user counts. Not from here.
      TOLGEE_TELEMETRY_ENABLED: "false"
    volumes:
      # File storage, plus the JWT key Tolgee falls back to.
      - /srv/tolgee/data:/data
    ports:
      # Loopback only: the host's Caddy is the only thing that reaches 8178.
      - "127.0.0.1:8178:8080"
    # No healthcheck stanza: the image declares its own on /actuator/health.
    depends_on:
      postgres:
        condition: service_healthy
EOF
cd /srv/tolgee && docker compose config >/dev/null && echo "compose OK"
```

Assert: that prints `compose OK`. The container listens on 8080 and compose publishes it on 8178,
loopback only. Upstream's single-container quick start runs the database inside the Tolgee image;
this file refuses that, because the bundled server is a PostgreSQL 13 that went end of life in
November 2025 and that upstream removes in v4.

## 5. Caddy and TLS

Append the block below to the Caddyfile Prompt Zero installed, with `<DOMAIN>` replaced by the
real hostname. Copy the file first: a syntax error takes down every other site on the box.

```bash
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.before-tolgee
printf '\n' | sudo tee -a /etc/caddy/Caddyfile >/dev/null
sudo tee -a /etc/caddy/Caddyfile >/dev/null <<'EOF'
# Tolgee · the Caddy site block for this service.
#
# Authored by caniselfhostit from
# https://docs.tolgee.io/platform/self_hosting/running_with_docker and
# https://caddyserver.com/docs/automatic-https
#
# Append this to /etc/caddy/Caddyfile, the Caddy that Prompt Zero installed,
# with <DOMAIN> replaced by the hostname pointed at this box. That hostname is
# also TOLGEE_FRONT_END_URL in .env and the apiUrl every SDK carries, so pick
# the one you intend to keep.

<DOMAIN> {
	# Spring Boot exposes health, info and prometheus on this port and
	# Tolgee permits anything outside /api and /v2 without a token. Health
	# has a job here; the other two hand a stranger every metric it keeps.
	@management path /actuator/info /actuator/prometheus /actuator/prometheus/*
	respond @management 403

	# HSTS is the one Tolgee cannot send for itself, and a project API key
	# rides in a header on every SDK request. The other three repeat what
	# the application already sends, for answers Caddy makes itself.
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
		-Server
	}

	# 8178 is the loopback port compose publishes on this host. It is not a
	# container port and it is not open in the firewall.
	reverse_proxy 127.0.0.1:8178
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
```

Assert: `caddy validate` exits 0 and the reload exits 0. If validate fails, restore
/etc/caddy/Caddyfile.before-tolgee, reload, and report what it objected to. Caddy requests the
certificate on the first request and renews it on its own.

## 6. Firewall

Two ports open, both Caddy's. Idempotent, so on a Prompt Zero box they change nothing:

```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw status verbose
```

80/tcp answers the ACME challenge and redirects to HTTPS, 443/tcp is the only way in, and 443/udp
is HTTP/3. 8178 stays closed because compose binds it to 127.0.0.1, and 5432 because compose never
publishes it. Upstream's examples publish 25432 for the bundled database, which this install never
starts. Assert: `ufw status verbose` prints `Status: active`, shows 80, 443/tcp and 443/udp, and
no rule mentioning 8178, 5432 or 25432.

## 7. Start and verify

Tolgee runs its own schema migrations on the way up, and a Java service on a small VPS takes over
a minute to answer at all. The loop is that minute.

```bash
cd /srv/tolgee
docker compose pull
docker compose up -d
for i in $(seq 1 30); do code=$(curl -sS -o /dev/null -w '%{http_code}' https://<DOMAIN>/api/public/configuration); echo "$i $code"; [ "$code" = 200 ] && break; sleep 10; done
curl -sS https://<DOMAIN>/api/public/configuration | grep -o '"version":"[^"]*"\|"authentication":[a-z]*\|"allowRegistrations":[a-z]*'
curl -sS https://<DOMAIN>/v2/public/initial-data | grep -o '"userInfo":[^,]*'
curl -sS -o /dev/null -w '%{http_code}\n' https://<DOMAIN>/actuator/prometheus
```

Assert all four and print what you received. The loop ends on `200`. The second command prints
`"version":"v3.218.0"`, `"authentication":true` and `"allowRegistrations":false`. The third
prints `"userInfo":null`. The fourth prints `403`.

The third decides whether this install is safe to leave running. On the image's own default,
`TOLGEE_AUTHENTICATION_ENABLED` is false, there is no login screen, and Tolgee hands every caller
a super-powered session as the administrator, so an anonymous request comes back with a populated
`userInfo` object. `null` means a stranger is nobody. Anything else: stop, do not report success,
run `docker compose exec -T tolgee printenv TOLGEE_AUTHENTICATION_ENABLED`.

If the loop never reaches `200`, stop, run `docker compose logs --tail 40 tolgee` and
`docker compose logs --tail 20 postgres`, and name the likely earlier step: a database that never
reports healthy points at step 2, a repeated connection refusal points at the datasource lines in
step 4, and a Caddy `502` over a container that logs `Started Application` means Caddy is reaching
nothing on 8178. A running container is not success.

The first screen at https://<DOMAIN> is headed `Login` over an `Email` box, a `Password` box and
a `Login` button, with no sign-up link under it because registration is off.

STOP: tell the user to read their password with
`sudo grep TOLGEE_AUTHENTICATION_INITIAL_PASSWORD /srv/tolgee/.env`, put it in their password
manager, sign in at https://<DOMAIN> with the username `admin` typed into the `Email` box, and
confirm they see the dashboard. Wait. Do not continue until they confirm.

Then tell them the next step is theirs and this prompt does not do it: create a project, add the
languages, generate a project API key in that project's settings, and point their application's
Tolgee SDK at this server with `apiUrl` set to https://<DOMAIN> and `apiKey` set to that key. The
key can edit translations, so it belongs in a development configuration, not a shipped bundle.

## 8. First backup and restore

Two artifacts. The database holds every key, translation, project and account. The config archive
holds the file storage and the files that rebuild the service around it.

```bash
cd /srv/tolgee
docker compose exec -T postgres pg_dump -U tolgee -d tolgee | gzip > /srv/tolgee/backups/tolgee-db-$(date +%F).sql.gz
sudo tar -czf /srv/tolgee/backups/tolgee-config-$(date +%F).tar.gz -C /srv/tolgee compose.yml .env data -C /etc/caddy Caddyfile
ls -lh /srv/tolgee/backups/
```

Assert: both files exist and both are non-empty. Print both sizes. Nothing is stopped: `pg_dump`
snapshots a running database consistently.

A backup on the same disk is not a backup. Run this one from the user's machine, not the server:

```bash
mkdir -p ~/backups/tolgee
scp vps:/srv/tolgee/backups/* ~/backups/tolgee/
```

To restore: `docker compose down`, `sudo rm -rf /srv/tolgee/postgres`, recreate that directory as
in step 2, untar the config archive into /srv/tolgee so .env is back before anything starts,
`docker compose up -d postgres`, wait about 30 seconds for it to report healthy, pipe `gunzip -c`
on the `.sql.gz` into `docker compose exec -T postgres psql -U tolgee -d tolgee`, then
`docker compose up -d`. That order matters twice over: PostgreSQL takes its password from .env the
moment it initialises an empty directory, and the JWT secret in the same file validates every
session already issued.

## 9. Updating later

New versions are listed at https://github.com/tolgee/tolgee-platform/releases, and the Docker tag
is the release tag with its leading `v`. Tolgee ships several a week, so pick one and read its
notes rather than chasing the newest. Take both backups first, then edit the image line in
/srv/tolgee/compose.yml to the new tag and its digest:

```bash
cd /srv/tolgee
docker compose pull
docker compose up -d
docker compose logs --tail 30 tolgee
```

Tolgee migrates its own schema on the way up, so watch that log until it settles, then re-run
step 7's four checks before calling the update done.

## 10. What will probably go wrong

The login box is labelled `Email` and the account is not an email address. I typed the address I
had used for the server, got an invalid-credentials error, tried it twice more, and went off to
read the container log for an authentication fault that was not there. Upstream says it in a
footnote: Tolgee asks for an email and the initial user is a username, which is `admin` unless
someone changed it. Type `admin` into that box with the password from .env. A real address can go
on the account afterwards, from the dashboard.

## 11. Out of scope

- Do not configure SMTP. Tolgee runs without it, and the price is no invitation mail and no
  password reset, so the password in .env is the whole recovery story.
- Do not set `TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED` to true. Step 7 asserts it is false,
  and true on a public hostname means anyone who finds this server can open an account.
- Do not add the LanguageTool container from upstream's optional section. It loads every language
  model at start-up and wants 1 to 1.5 GB on top of what step 1 measured.
- Do not install the Tolgee SDK into the user's application. That needs their repository and their
  API key, and this prompt installs the server it talks to.
No terminal agent? Use the chat fallback — slower, you paste the commands

For ChatGPT or Claude in a browser. The model cannot touch your server, so it hands you one command at a time and you run each one. Same install, more of your evening.

This path is slower: you paste every command yourself, and there is nobody watching the
output but you. If you can run Claude Code, use the other tab.

You are installing Tolgee 3.218.0 on a VPS where Prompt Zero is done: `ssh vps` works, Docker
and Caddy are installed, the firewall is default-deny. Run everything over `ssh vps` unless a
step says otherwise, and replace `<DOMAIN>` with the hostname whose A record already points at
the box.

Read this before step 1. The hostname you pick becomes `TOLGEE_FRONT_END_URL` and the `apiUrl`
in every application you wire to this server, so moving it later means editing each of them.
Pick the one you intend to keep.

## 1. Preflight

```bash
free -m | awk '/^Mem:/ {print $7 " MB available of " $2 " MB"}'
df -BG --output=avail /srv | tail -1
dpkg --print-architecture
dig +short <DOMAIN>
```

You should see: at least `2048` MB available, at least `10` G free, `amd64` or `arm64`, and your
server's IP on the last line.

If you do not: an empty last line means the A record does not exist yet. Add it, wait a minute,
run `dig +short <DOMAIN>` again. Caddy cannot get a certificate for a hostname that does not
resolve, and failed attempts count against a rate limit you cannot see. On the memory line, 2 GB
is not padding: this is a Java service beside a PostgreSQL, and the image's docker profile sizes
an in-memory cache at a million entries. A 1 GB box will start and then be killed under its
first import.

## 2. Layout

```bash
sudo install -d -m 750 -o $(id -u) -g $(id -g) /srv/tolgee /srv/tolgee/backups
sudo install -d -m 700 /srv/tolgee/postgres /srv/tolgee/data
ls -la /srv/tolgee
```

You should see: `backups` owned by you, and `postgres` and `data` at mode `drwx------` owned by
root.

If you do not: leave both owned by root on purpose. The PostgreSQL image chowns its own data
directory the first time it starts and refuses to initialise one you have already chowned to
yourself. The Tolgee container runs as root as well, and `data` is where it keeps uploaded
screenshots and the JWT key it falls back to.

## 3. Secrets

Three secrets, generated here on the server. `DB_PASSWORD` is the PostgreSQL password.
`TOLGEE_AUTHENTICATION_JWT_SECRET` signs your session tokens, and upstream requires at least 32
characters. `TOLGEE_AUTHENTICATION_INITIAL_PASSWORD` is the password of the `admin` account
Tolgee creates the first time it starts; setting it here means Tolgee does not invent one and
write it to a file inside the container.

```bash
umask 077
cat > /srv/tolgee/.env <<EOF
TOLGEE_FRONT_END_URL=https://<DOMAIN>
DB_PASSWORD=$(openssl rand -hex 32)
TOLGEE_AUTHENTICATION_JWT_SECRET=$(openssl rand -hex 32)
TOLGEE_AUTHENTICATION_INITIAL_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 /srv/tolgee/.env
umask 022
ls -l /srv/tolgee/.env
```

You should see: mode `-rw-------`, your own username twice, and the path. Replace `<DOMAIN>` on
the first line with your real hostname before you paste. Upstream states that leaving
`TOLGEE_FRONT_END_URL` unset on a publicly reachable instance is a security problem, which is
why it is the first line rather than an afterthought.

If you do not: a mode of `-rw-r--r--` means `umask 077` did not take effect, which happens if you
pasted the lines separately in different shells. Run `chmod 600 /srv/tolgee/.env` and carry on.
If the file already existed from an earlier attempt, this block has now overwritten all three
values, which is fine before the database exists and a problem afterwards: PostgreSQL keeps the
password it was created with, so a changed `DB_PASSWORD` on an existing volume shows up as a
connection failure in the Tolgee log rather than as anything about passwords.

Do not paste that file, any of the three values, or any command output containing them into this
chat window. Read the admin password once with
`sudo grep TOLGEE_AUTHENTICATION_INITIAL_PASSWORD /srv/tolgee/.env` and put it straight into your
password manager. There is no mail server here, so there is no reset link if you lose it.

## 4. compose.yml

Paste the whole block at once, including the last two lines.

```bash
cat > /srv/tolgee/compose.yml <<'EOF'
# Tolgee · the deterministic fallback. Authored by caniselfhostit from the
# upstream documentation, not copied from a repository:
#   running with docker . https://docs.tolgee.io/platform/self_hosting/running_with_docker
#   configuration ....... https://docs.tolgee.io/platform/self_hosting/configuration
#   image build ......... https://github.com/tolgee/tolgee-platform/blob/v3.218.0/docker/app/Dockerfile
#
# Two services: Tolgee and the PostgreSQL holding every key, translation and
# account. The tolgee/tolgee image is built on postgres:13 and starts that
# bundled database itself unless told not to. Upstream deprecated it and removes
# it in v4, so this file uses the external database shape their own docs
# document, on the PostgreSQL 17 their example names. Digests read from the
# registries on 2026-08-07; both publish amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.

services:
  postgres:
    image: postgres:17.10-alpine@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193
    container_name: tolgee-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: tolgee
      POSTGRES_USER: tolgee
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - /srv/tolgee/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tolgee -d tolgee"]
      interval: 10s
      retries: 12
    # No `ports:` at all: 5432 is reachable only from the other container.

  tolgee:
    image: tolgee/tolgee:v3.218.0@sha256:1955a9e28fb247bc0404809432d0ee179b43966f5080b8100a70333375db4382
    container_name: tolgee
    restart: unless-stopped
    env_file: /srv/tolgee/.env
    environment:
      # The docker profile in this image ships this false, and false means no
      # login screen and every caller already an administrator.
      TOLGEE_AUTHENTICATION_ENABLED: "true"
      # Nobody signs themselves up. New people arrive by invitation.
      TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED: "false"
      # Do not start the PostgreSQL bundled in this image.
      TOLGEE_POSTGRES_AUTOSTART_ENABLED: "false"
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tolgee
      SPRING_DATASOURCE_USERNAME: tolgee
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      # Upstream sends daily project, language and user counts. Not from here.
      TOLGEE_TELEMETRY_ENABLED: "false"
    volumes:
      # File storage, plus the JWT key Tolgee falls back to.
      - /srv/tolgee/data:/data
    ports:
      # Loopback only: the host's Caddy is the only thing that reaches 8178.
      - "127.0.0.1:8178:8080"
    # No healthcheck stanza: the image declares its own on /actuator/health.
    depends_on:
      postgres:
        condition: service_healthy
EOF
cd /srv/tolgee && docker compose config >/dev/null && echo "compose OK"
```

You should see: `compose OK` and nothing else.

If you do not: `env file /srv/tolgee/.env not found` means step 3 did not write the file.
`services must be a mapping` means the indentation was lost between the page and your terminal:
run `rm /srv/tolgee/compose.yml` and paste again in one go. Two lines in that file are worth
understanding before you move on. `TOLGEE_AUTHENTICATION_ENABLED` is `true` because the image's
docker profile ships it `false`, and `false` means there is no login screen at all and every
request arrives already logged in as the administrator. `TOLGEE_POSTGRES_AUTOSTART_ENABLED` is
`false` because the image is built on top of postgres:13 and will otherwise start a database
inside itself; upstream deprecated that one and removes it in Tolgee v4.

## 5. Caddy and TLS

This appends one site block to the Caddy config Prompt Zero installed. Replace `<DOMAIN>` in the
block with your hostname before you paste. The first line takes a copy, because a syntax error
here takes down every other site on the box.

```bash
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.before-tolgee
printf '\n' | sudo tee -a /etc/caddy/Caddyfile >/dev/null
sudo tee -a /etc/caddy/Caddyfile >/dev/null <<'EOF'
# Tolgee · the Caddy site block for this service.
#
# Authored by caniselfhostit from
# https://docs.tolgee.io/platform/self_hosting/running_with_docker and
# https://caddyserver.com/docs/automatic-https
#
# Append this to /etc/caddy/Caddyfile, the Caddy that Prompt Zero installed,
# with <DOMAIN> replaced by the hostname pointed at this box. That hostname is
# also TOLGEE_FRONT_END_URL in .env and the apiUrl every SDK carries, so pick
# the one you intend to keep.

<DOMAIN> {
	# Spring Boot exposes health, info and prometheus on this port and
	# Tolgee permits anything outside /api and /v2 without a token. Health
	# has a job here; the other two hand a stranger every metric it keeps.
	@management path /actuator/info /actuator/prometheus /actuator/prometheus/*
	respond @management 403

	# HSTS is the one Tolgee cannot send for itself, and a project API key
	# rides in a header on every SDK request. The other three repeat what
	# the application already sends, for answers Caddy makes itself.
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
		-Server
	}

	# 8178 is the loopback port compose publishes on this host. It is not a
	# container port and it is not open in the firewall.
	reverse_proxy 127.0.0.1:8178
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
```

You should see: `Valid configuration` from validate, and no output at all from reload.

If you do not: run `sudo cp /etc/caddy/Caddyfile.before-tolgee /etc/caddy/Caddyfile`, reload, and
paste again. The `@management` matcher in that block is not decoration. Spring Boot publishes
health, info and prometheus on the same port Tolgee serves the dashboard on, and Tolgee's own
security rules let anything outside `/api` and `/v2` through without a token, so without those
two lines your metrics are public.

## 6. Firewall

```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw status verbose
```

You should see: `Status: active`, rules for `80/tcp`, `443/tcp` and `443/udp`, and no rule
mentioning `8178`, `5432` or `25432`.

If you do not: delete anything for those three with `sudo ufw delete allow 8178`. 8178 is bound
to 127.0.0.1 by the compose file, 5432 is never published at all, and 25432 is the port
upstream's examples publish for the bundled database this install never starts. 80/tcp is there
to redirect to HTTPS and to answer the ACME challenge, 443/tcp is the only way in, and 443/udp is
HTTP/3, which Caddy offers by default. `Status: inactive` is a different problem: Prompt Zero
left this firewall enabled, so something has turned it off since, and `sudo ufw enable` puts it
back before you go any further.

## 7. Start and verify

Tolgee runs its own schema migrations on the way up, and a Java service on a small VPS takes over
a minute to answer at all. The loop below is that minute; let it run out before you conclude
anything.

```bash
cd /srv/tolgee
docker compose pull
docker compose up -d
for i in $(seq 1 30); do code=$(curl -sS -o /dev/null -w '%{http_code}' https://<DOMAIN>/api/public/configuration); echo "$i $code"; [ "$code" = 200 ] && break; sleep 10; done
curl -sS https://<DOMAIN>/api/public/configuration | grep -o '"version":"[^"]*"\|"authentication":[a-z]*\|"allowRegistrations":[a-z]*'
curl -sS https://<DOMAIN>/v2/public/initial-data | grep -o '"userInfo":[^,]*'
curl -sS -o /dev/null -w '%{http_code}\n' https://<DOMAIN>/actuator/prometheus
```

You should see, in order: the loop reaching `200`; then `"version":"v3.218.0"`,
`"authentication":true` and `"allowRegistrations":false`; then `"userInfo":null`; then `403`.

If you do not: the third line is the one that decides whether this install is safe to leave
running, so do not skip past it. An unauthenticated request to `/v2/public/initial-data` on a
server with authentication switched off comes back with a populated `userInfo` object, because
Tolgee hands anyone with no token a super-powered session as the administrator. `null` there
means a stranger is nobody. If it shows an object instead, stop and run
`docker compose exec -T tolgee printenv TOLGEE_AUTHENTICATION_ENABLED`; an empty answer means the
environment line in step 4 did not survive, and the fix is to paste step 4 again and
`docker compose up -d --force-recreate`. If the loop never reaches `200`, run
`docker compose logs --tail 20 postgres` first, because a database that never reports healthy is
step 2 done wrong, and `docker compose logs --tail 40 tolgee` second. A Caddy `502` over a
container whose log says `Started Application` means Caddy is reaching nothing on 8178. A running
container is not success.

The first screen at https://<DOMAIN> is headed `Login` over an `Email` box, a `Password` box and
a `Login` button. There is no sign-up link under it, and there should not be: registration is
off, so the only account on this server is yours.

Sign in now. The username is `admin`, typed into the box labelled `Email`, and the password is
the one you read out of .env in step 3.

You should see: the Tolgee dashboard, with a button to create your first project.

If you do not: an invalid-credentials error almost always means you typed an email address into
the `Email` box. Tolgee asks for an email and the initial account is a username, and unless you
changed `TOLGEE_AUTHENTICATION_INITIAL_USERNAME` that username is `admin`. You can put a real
address on the account from the dashboard afterwards.

What happens next is yours to do and this guide does not do it. Create a project, add your
languages, generate a project API key in that project's settings, and point your application's
Tolgee SDK at `apiUrl` https://<DOMAIN> with `apiKey` set to that key. That key can edit
translations, so it belongs in a development configuration rather than a shipped bundle.

## 8. First backup and restore

Two artifacts. The database holds every key, translation, project and account. The config
archive holds the file storage and the three files that rebuild the service around it.

```bash
cd /srv/tolgee
docker compose exec -T postgres pg_dump -U tolgee -d tolgee | gzip > /srv/tolgee/backups/tolgee-db-$(date +%F).sql.gz
sudo tar -czf /srv/tolgee/backups/tolgee-config-$(date +%F).tar.gz -C /srv/tolgee compose.yml .env data -C /etc/caddy Caddyfile
ls -lh /srv/tolgee/backups/
```

You should see: two files, both a few kilobytes on a fresh install. Nothing goes offline:
`pg_dump` snapshots a running database consistently.

If you do not: a `.sql.gz` of about 20 bytes is an empty dump, which means `pg_dump` failed and
the shell created the file anyway. Run the dump line without `| gzip` to read the error.

A backup on the same disk as the data is not a backup. Run this one on your own machine, not the
server:

```bash
mkdir -p ~/backups/tolgee
scp vps:/srv/tolgee/backups/* ~/backups/tolgee/
```

You should see: two files copied, and both listed by `ls -lh ~/backups/tolgee/`.

If you do not: `Permission denied (publickey)` means you ran it on the server. The `vps:` prefix
only means something on your own machine, where the `vps` alias Prompt Zero created lives.

Now prove the restore, today, while the only thing at risk is an empty project:

```bash
cd /srv/tolgee
docker compose down
sudo rm -rf /srv/tolgee/postgres
sudo install -d -m 700 /srv/tolgee/postgres
docker compose up -d postgres
sleep 30
gunzip -c /srv/tolgee/backups/tolgee-db-$(date +%F).sql.gz | docker compose exec -T postgres psql -U tolgee -d tolgee
docker compose up -d
sleep 90
curl -sS https://<DOMAIN>/api/public/configuration | grep -o '"authentication":[a-z]*'
```

You should see: `CREATE TABLE` and `COPY` lines from psql, then `"authentication":true` from the
last command, and your admin login still works.

If you do not: `role "tolgee" does not exist` means the database container had not finished
initialising, so wait longer and run the `gunzip` line again. Note the order this depends on:
.env has to be in place before PostgreSQL initialises an empty directory, because that is when it
takes `DB_PASSWORD`, and the JWT secret in the same file is what validates the sessions and
tokens already issued. Restore the database without that file and you get an install that no
longer recognises its own logins.

## 9. Updating later

New versions are listed at https://github.com/tolgee/tolgee-platform/releases, and the Docker tag
is the release tag including its leading `v`. Tolgee ships several releases a week, so pick one
and read its notes rather than chasing the newest. Take both backup artifacts first, then edit
the `image:` line in /srv/tolgee/compose.yml to the new tag and its digest.

```bash
cd /srv/tolgee
docker compose pull
docker compose up -d
docker compose logs --tail 30 tolgee
```

You should see: migration output, then the server starting, and no repeating restart.

If you do not: put the old tag and digest back and run the same three commands. Then re-run the
four checks from step 7 before you call the update done, and sign in as well, because a server
that answers `"authentication":true` can still be failing on something a migration left halfway.

## 10. What will probably go wrong

The login box is labelled `Email` and the account is not an email address. I typed the address I
had used for the server, got an invalid-credentials error, tried it twice more, and went off to
read the container log for an authentication fault that was not there. Upstream says it in a
footnote: Tolgee asks for an email and the initial user is a username, which is `admin` unless
someone changed it. Type `admin` into that box with the password from .env. A real address can go
on the account afterwards, from the dashboard.

## 11. Out of scope

- Do not configure SMTP. Tolgee runs without it, and the price is no invitation mail and no
  password reset, so the password in .env is the whole recovery story.
- Do not set `TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED` to true. Step 7 checks it is false,
  and true on a public hostname means anyone who finds this server can open an account.
- Do not add the LanguageTool container from upstream's optional section. It loads every language
  model at start-up and wants 1 to 1.5 GB of memory on top of what step 1 measured.
- Do not install the Tolgee SDK into your application from here. That needs your repository and
  your API key, and this guide installs the server it talks to.

318 lines · 14,995 bytes

What this prompt will do
  1. Preflight
  2. Docker
  3. Layout
  4. Secrets
  5. compose.yml
  6. Nothing is public
  7. Start and verify
  8. First backup and restore
  9. Updating later
  10. What will probably go wrong
  11. Out of scope

Read out of the prompt’s own step headings at build time — if the prompt changes, this list changes with it.

paste it into Claude Code in a terminal on this computer · installs Docker Desktop if it is missing · no server, no domain

You are Claude Code on the user's own computer. There is no server and no Prompt Zero:
everything in this prompt runs on this machine and stays on it.

Run every command on this computer, in the shell you are already in. Nothing in this prompt
uses ssh.

Install Tolgee 3.218.0, with the PostgreSQL it keeps its translations in, under
~/selfhost/tolgee, answering at http://localhost:8178.

## 1. Preflight

Say this before step 2 runs; it decides whether the user wants this at all.
Tolgee answers at http://localhost:8178, which means this computer and nothing else: no
translator on another laptop can open a key, and the SDK reaches it only from an application
running here. A private translation editor with a real API, on one desk.

Detect the OS and measure:

```bash
uname -s
case "$(uname -s)" in
  Darwin) vm_stat | awk '/page size/{p=$8} /free|inactive/{s+=$3} END {printf "%d MB available\n", s*p/1048576}' ;;
  Linux) . /etc/os-release && echo "$ID $VERSION_CODENAME"; free -m | awk '/^Mem:/ {print $7 " MB available of " $2 " MB"}' ;;
  MINGW*|MSYS*) powershell -Command "(Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory" | awk '$1+0 {printf "%d MB available\n", $1/1024}' ;;
esac
df -h ~
```

`Darwin` is macOS, `Linux` is Linux, `MINGW` or `MSYS` is Windows under Git Bash; on Linux the
distribution ID and codename print next, for step 2. Tolgee is a JVM beside a PostgreSQL: 2048 MB
of RAM available, 10 GB free on the home disk, amd64 or arm64. On macOS and Windows that figure
is the host's and Docker Desktop's virtual machine takes its allocation out of it, so read
step 10 first. Under either floor, print both numbers and stop.

## 2. Docker

Check before installing anything:

```bash
docker info >/dev/null 2>&1 && echo "docker OK" || echo "docker MISSING"
docker compose version 2>/dev/null || true
```

If that printed `docker OK` and a compose version, skip to step 3.

Otherwise, install Docker for the OS step 1 detected:

- macOS: if `command -v brew` succeeds, run `brew install --cask docker`. If there is no
  Homebrew, STOP: tell the user to download Docker Desktop from
  https://www.docker.com/products/docker-desktop/ and install it, and wait until they
  confirm. Either way, then STOP: tell the user to open Docker Desktop once, accept its
  terms, and wait for the whale icon to say it is running. Do not continue until they
  confirm.
- Windows: run `winget install -e --id Docker.DockerDesktop`. If winget is missing or the
  install fails, STOP: tell the user to download Docker Desktop from the URL above and
  install it, and wait until they confirm. Docker Desktop configures WSL 2 itself and may
  ask for a reboot; if it does, STOP and tell the user to reboot and come back, this
  prompt resumes at this step. Then STOP: have the user open Docker Desktop, accept its
  terms, and confirm it says running.
- Linux, Debian or Ubuntu: install Docker Engine from download.docker.com's apt
  repository, with its signing key saved to a file first, never piped into a shell. The
  fence is guarded, a no-op on anything but a Linux with apt:

```bash
if [ "$(uname -s)" = "Linux" ] && command -v apt-get >/dev/null 2>&1; then
  sudo apt-get update
  sudo apt-get install -y ca-certificates curl
  sudo install -m 0755 -d /etc/apt/keyrings
  sudo curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg -o /etc/apt/keyrings/docker.asc
  sudo chmod a+r /etc/apt/keyrings/docker.asc
  echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
  sudo apt-get update
  sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
  sudo usermod -aG docker "$USER"
fi
```

  Adding the user to the docker group is root-equivalent on this machine; say that to the
  user in one sentence, and tell them the group change lands at their next login.
- Linux, anything else: STOP. Tell the user to install Docker Engine and the compose
  plugin with their distribution's package manager, and to run this prompt again once
  `docker info` works.

Assert: `docker info` exits 0 and `docker compose version` prints a version. Do not
continue without both.

## 3. Layout

```bash
mkdir -p ~/selfhost/tolgee/data ~/selfhost/tolgee/backups
ls -la ~/selfhost/tolgee
```

Assert: `ls -la` shows `data` and `backups`, both owned by the user. `data` holds the file
storage, screenshots and the JWT key Tolgee falls back to. No ownership fix runs here: the
container writes as root into a directory the user owns, and on macOS and Windows Docker Desktop
handles that. The database is not in this tree; step 5 puts it in a volume Docker manages.

## 4. Secrets

Three secrets: the PostgreSQL password, the secret Tolgee signs session tokens with (upstream
requires at least 32 characters), and the password of the `admin` account Tolgee creates on first
start. Setting that last one keeps Tolgee from inventing one and writing it to a file inside the
container. Print none of the three and keep them out of every log line.

```bash
umask 077
cat > ~/selfhost/tolgee/.env <<EOF
TOLGEE_FRONT_END_URL=http://localhost:8178
DB_PASSWORD=$(openssl rand -hex 32)
TOLGEE_AUTHENTICATION_JWT_SECRET=$(openssl rand -hex 32)
TOLGEE_AUTHENTICATION_INITIAL_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 ~/selfhost/tolgee/.env
umask 022
ls -l ~/selfhost/tolgee/.env
```

Assert: the file exists with mode `-rw-------`. Git Bash ships openssl, so these lines run the
same on all three. On Windows the mode bits are advisory: NTFS does not enforce them, and the
real boundary is the Windows account.

## 5. compose.yml

```bash
cat > ~/selfhost/tolgee/compose.yml <<'EOF'
# Tolgee · the deterministic fallback for the local path. Authored by
# caniselfhostit from the upstream documentation, not copied from a repository:
#   running with docker . https://docs.tolgee.io/platform/self_hosting/running_with_docker
#   configuration ....... https://docs.tolgee.io/platform/self_hosting/configuration
#   image build ......... https://github.com/tolgee/tolgee-platform/blob/v3.218.0/docker/app/Dockerfile
#
# Two services on the computer you are sitting at, with every path relative to
# ~/selfhost/tolgee/ so one file works on macOS, Linux and Windows. The database
# is a named volume because PostgreSQL chowns its data directory to a uid a
# home-directory bind mount cannot grant on Windows. The PostgreSQL 13 this
# image bundles is off because upstream removes it in v4. Digests read on
# 2026-08-07; both publish amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.

services:
  postgres:
    image: postgres:17.10-alpine@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193
    container_name: tolgee-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: tolgee
      POSTGRES_USER: tolgee
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - tolgee-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tolgee -d tolgee"]
      interval: 10s
      retries: 12
    # No `ports:` at all: 5432 is reachable only from the other container.

  tolgee:
    image: tolgee/tolgee:v3.218.0@sha256:1955a9e28fb247bc0404809432d0ee179b43966f5080b8100a70333375db4382
    container_name: tolgee
    restart: unless-stopped
    env_file: ./.env
    environment:
      # The docker profile ships this false: no login screen, everyone an
      # administrator, and a browser tab on this machine is a caller.
      TOLGEE_AUTHENTICATION_ENABLED: "true"
      # New people arrive by invitation, never by signing themselves up.
      TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED: "false"
      # Do not start the PostgreSQL bundled in this image.
      TOLGEE_POSTGRES_AUTOSTART_ENABLED: "false"
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tolgee
      SPRING_DATASOURCE_USERNAME: tolgee
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      # No daily usage counts leave this computer.
      TOLGEE_TELEMETRY_ENABLED: "false"
    volumes:
      # File storage, plus the JWT key Tolgee falls back to.
      - ./data:/data
    ports:
      # Loopback only: no other device on the wifi can reach 8178.
      - "127.0.0.1:8178:8080"
    # No healthcheck stanza: the image declares its own on /actuator/health.
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  tolgee-pgdata:
EOF
cd ~/selfhost/tolgee && docker compose config >/dev/null && echo "compose OK"
```

Assert: that prints `compose OK`.

## 6. Nothing is public

No reverse proxy, no certificate, no firewall rule. Each is a decision:

- No DNS. There is no hostname, so nothing to resolve and nothing to wait for.
- No TLS. A certificate attests a public name and nothing here has one. Browsers treat
  http://localhost as a secure context, so pages needing crypto still work.
- No firewall rule. Nothing is published beyond loopback.

8178 is bound to 127.0.0.1: not the phone, not another laptop, not the internet. Confirm it:

```bash
grep -c '"127.0.0.1:' ~/selfhost/tolgee/compose.yml
```

Assert: that prints `1`, the published-port line `- "127.0.0.1:8178:8080"`. PostgreSQL publishes
no host port, so 5432 cannot appear. Authentication still matters here: with it off, any browser
tab on this machine reaches 8178 as an administrator, and step 7 checks that it cannot.

## 7. Start and verify

Tolgee migrates its own schema on the way up, and a cold JVM takes a minute to answer at all.
The loop is that minute.

```bash
cd ~/selfhost/tolgee
docker compose pull
docker compose up -d
for i in $(seq 1 30); do code=$(curl -sS -o /dev/null -w '%{http_code}' http://localhost:8178/api/public/configuration); echo "$i $code"; [ "$code" = 200 ] && break; sleep 10; done
curl -sS http://localhost:8178/api/public/configuration | grep -o '"authentication":[a-z]*\|"allowRegistrations":[a-z]*'
curl -sS http://localhost:8178/v2/public/initial-data | grep -o '"userInfo":[^,]*'
```

Assert all three and print what you received. The loop ends on `200`. The second command prints
`"authentication":true` and `"allowRegistrations":false`. The third prints `"userInfo":null`.

The third is the security assert. On the image's own default `TOLGEE_AUTHENTICATION_ENABLED` is
false, there is no login screen, and every caller gets a super-powered session as the
administrator, so an anonymous request returns a populated `userInfo` object. `null` means a
caller with no token is nobody. Anything else: stop, do not report success, run
`docker compose exec -T tolgee printenv TOLGEE_AUTHENTICATION_ENABLED`.

If the loop never reaches `200`, stop, run `docker compose logs --tail 40 tolgee` and
`docker compose logs --tail 20 postgres`, and name the cause: a database that never reports
healthy is step 4, where an empty `DB_PASSWORD` leaves PostgreSQL refusing to start, and a
container exiting `137` is step 10. On `port is already allocated`, find what holds 8178 with
`lsof -nP -iTCP:8178 -sTCP:LISTEN`. A running container is not success.

The first screen at http://localhost:8178 is headed `Login` over an `Email` box, a `Password`
box and a `Login` button, with no sign-up link because registration is off.

STOP: tell the user to read their password with
`grep TOLGEE_AUTHENTICATION_INITIAL_PASSWORD ~/selfhost/tolgee/.env`, put it in their password
manager, sign in with `admin` typed into the `Email` box, and confirm they see the dashboard.
Wait. Do not continue until they confirm.

Then tell them the next step is theirs: create a project, add the languages, generate a project
API key in its settings, and point their SDK at `apiUrl` http://localhost:8178 with that key.

## 8. First backup and restore

Two artifacts: a database dump with every key, translation and account, and an archive of the
file storage plus the two files that rebuild the service.

```bash
cd ~/selfhost/tolgee
docker compose exec -T postgres pg_dump -U tolgee -d tolgee | gzip > ~/selfhost/tolgee/backups/tolgee-db-$(date +%F).sql.gz
tar -C ~/selfhost/tolgee -czf ~/selfhost/tolgee/backups/tolgee-config-$(date +%F).tar.gz compose.yml .env data
ls -lh ~/selfhost/tolgee/backups/
```

Assert: both exist and are non-empty. Print both sizes. Nothing is stopped: `pg_dump` snapshots
a running database.

Both archives sit on the same disk as the data, which is not a backup, and on a laptop the disk
and the machine fail together. Ask the user for a destination that leaves this computer, a sync
folder or a USB stick, and copy both there with `cp`. Assert: the user confirms both filenames
are listed there.

To restore, in this order. `cd ~/selfhost/tolgee` and untar the config archive there first, so
.env is back before any container starts: PostgreSQL takes `DB_PASSWORD` from it the moment it
initialises an empty volume, and the JWT secret beside it validates every session already issued.
Then `docker compose down -v`, the one place `-v` belongs because it drops the old volume on
purpose, `docker compose up -d postgres`, wait 30 seconds for healthy, pipe `gunzip -c` on the
`.sql.gz` into `docker compose exec -T postgres psql -U tolgee -d tolgee`, then
`docker compose up -d`.

## 9. Updating later

New versions are listed at https://github.com/tolgee/tolgee-platform/releases, and the Docker tag
is the release tag with its leading `v`. Take both backups first, then edit the image line in
compose.yml:

```bash
cd ~/selfhost/tolgee
docker compose pull
docker compose up -d
docker compose logs --tail 30 tolgee
```

Watch that log until it settles, then re-run step 7's checks before calling it done.

## 10. What will probably go wrong

Memory, and it does not announce itself. I ran this on a Mac whose Docker Desktop was set to
2 GB. The tolgee container came up, sat there, and died with exit code `137` about ninety seconds
in, over and over. `137` is the virtual machine's out-of-memory killer, not a fault in the
configuration: a JVM and a PostgreSQL do not fit in 2 GB together. Open Docker Desktop's
Resources settings, give it at least 4 GB, apply and restart, then `docker compose up -d` again.
On Linux there is no such machine and the host memory step 1 measured is the ceiling.

## 11. Out of scope

- Do not expose this to the internet.
- Do not configure port forwarding on the router.
- Do not add a reverse proxy or TLS.
- Do not set `TOLGEE_AUTHENTICATION_ENABLED` to false, and do not rebind 8178 to 0.0.0.0 so a
  teammate can reach it. Together they put an unauthenticated administrator on every network
  this computer joins.
- Do not configure SMTP. Without it there is no invitation mail and no password reset, so the
  password in .env is the recovery story.
- Do not install the Tolgee SDK into the user's application. That needs their repository;
  this prompt installs the server it talks to.
compose.local.ymlthe services, pinned · local layout63 lines

authored from upstream docs, never pasted · 2,745 bytes

# Tolgee · the deterministic fallback for the local path. Authored by
# caniselfhostit from the upstream documentation, not copied from a repository:
#   running with docker . https://docs.tolgee.io/platform/self_hosting/running_with_docker
#   configuration ....... https://docs.tolgee.io/platform/self_hosting/configuration
#   image build ......... https://github.com/tolgee/tolgee-platform/blob/v3.218.0/docker/app/Dockerfile
#
# Two services on the computer you are sitting at, with every path relative to
# ~/selfhost/tolgee/ so one file works on macOS, Linux and Windows. The database
# is a named volume because PostgreSQL chowns its data directory to a uid a
# home-directory bind mount cannot grant on Windows. The PostgreSQL 13 this
# image bundles is off because upstream removes it in v4. Digests read on
# 2026-08-07; both publish amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.

services:
  postgres:
    image: postgres:17.10-alpine@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193
    container_name: tolgee-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: tolgee
      POSTGRES_USER: tolgee
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - tolgee-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tolgee -d tolgee"]
      interval: 10s
      retries: 12
    # No `ports:` at all: 5432 is reachable only from the other container.

  tolgee:
    image: tolgee/tolgee:v3.218.0@sha256:1955a9e28fb247bc0404809432d0ee179b43966f5080b8100a70333375db4382
    container_name: tolgee
    restart: unless-stopped
    env_file: ./.env
    environment:
      # The docker profile ships this false: no login screen, everyone an
      # administrator, and a browser tab on this machine is a caller.
      TOLGEE_AUTHENTICATION_ENABLED: "true"
      # New people arrive by invitation, never by signing themselves up.
      TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED: "false"
      # Do not start the PostgreSQL bundled in this image.
      TOLGEE_POSTGRES_AUTOSTART_ENABLED: "false"
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tolgee
      SPRING_DATASOURCE_USERNAME: tolgee
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      # No daily usage counts leave this computer.
      TOLGEE_TELEMETRY_ENABLED: "false"
    volumes:
      # File storage, plus the JWT key Tolgee falls back to.
      - ./data:/data
    ports:
      # Loopback only: no other device on the wifi can reach 8178.
      - "127.0.0.1:8178:8080"
    # No healthcheck stanza: the image declares its own on /actuator/health.
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  tolgee-pgdata:

agent-readable mirror: /self-host/tolgee-cloud.md

The files, if you'd rather do it yourself

The cloud path with no agent involved: three files, in the order you'd use them. The cloud prompt above writes exactly these — if the two ever disagree, the files are the ones CI diffs. The local path ships its own compose file, collapsed under its own prompt.

compose.ymlthe services, pinned60 lines

authored from upstream docs, never pasted · 2,768 bytes

# Tolgee · the deterministic fallback. Authored by caniselfhostit from the
# upstream documentation, not copied from a repository:
#   running with docker . https://docs.tolgee.io/platform/self_hosting/running_with_docker
#   configuration ....... https://docs.tolgee.io/platform/self_hosting/configuration
#   image build ......... https://github.com/tolgee/tolgee-platform/blob/v3.218.0/docker/app/Dockerfile
#
# Two services: Tolgee and the PostgreSQL holding every key, translation and
# account. The tolgee/tolgee image is built on postgres:13 and starts that
# bundled database itself unless told not to. Upstream deprecated it and removes
# it in v4, so this file uses the external database shape their own docs
# document, on the PostgreSQL 17 their example names. Digests read from the
# registries on 2026-08-07; both publish amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.

services:
  postgres:
    image: postgres:17.10-alpine@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193
    container_name: tolgee-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: tolgee
      POSTGRES_USER: tolgee
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - /srv/tolgee/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tolgee -d tolgee"]
      interval: 10s
      retries: 12
    # No `ports:` at all: 5432 is reachable only from the other container.

  tolgee:
    image: tolgee/tolgee:v3.218.0@sha256:1955a9e28fb247bc0404809432d0ee179b43966f5080b8100a70333375db4382
    container_name: tolgee
    restart: unless-stopped
    env_file: /srv/tolgee/.env
    environment:
      # The docker profile in this image ships this false, and false means no
      # login screen and every caller already an administrator.
      TOLGEE_AUTHENTICATION_ENABLED: "true"
      # Nobody signs themselves up. New people arrive by invitation.
      TOLGEE_AUTHENTICATION_REGISTRATIONS_ALLOWED: "false"
      # Do not start the PostgreSQL bundled in this image.
      TOLGEE_POSTGRES_AUTOSTART_ENABLED: "false"
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tolgee
      SPRING_DATASOURCE_USERNAME: tolgee
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      # Upstream sends daily project, language and user counts. Not from here.
      TOLGEE_TELEMETRY_ENABLED: "false"
    volumes:
      # File storage, plus the JWT key Tolgee falls back to.
      - /srv/tolgee/data:/data
    ports:
      # Loopback only: the host's Caddy is the only thing that reaches 8178.
      - "127.0.0.1:8178:8080"
    # No healthcheck stanza: the image declares its own on /actuator/health.
    depends_on:
      postgres:
        condition: service_healthy
Caddyfilethe hostname and TLS33 lines

authored from upstream docs, never pasted · 1,368 bytes

# Tolgee · the Caddy site block for this service.
#
# Authored by caniselfhostit from
# https://docs.tolgee.io/platform/self_hosting/running_with_docker and
# https://caddyserver.com/docs/automatic-https
#
# Append this to /etc/caddy/Caddyfile, the Caddy that Prompt Zero installed,
# with <DOMAIN> replaced by the hostname pointed at this box. That hostname is
# also TOLGEE_FRONT_END_URL in .env and the apiUrl every SDK carries, so pick
# the one you intend to keep.

<DOMAIN> {
	# Spring Boot exposes health, info and prometheus on this port and
	# Tolgee permits anything outside /api and /v2 without a token. Health
	# has a job here; the other two hand a stranger every metric it keeps.
	@management path /actuator/info /actuator/prometheus /actuator/prometheus/*
	respond @management 403

	# HSTS is the one Tolgee cannot send for itself, and a project API key
	# rides in a header on every SDK request. The other three repeat what
	# the application already sends, for answers Caddy makes itself.
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
		-Server
	}

	# 8178 is the loopback port compose publishes on this host. It is not a
	# container port and it is not open in the firewall.
	reverse_proxy 127.0.0.1:8178
}
install.shthe same install, no agent168 lines

authored from upstream docs, never pasted · 7,995 bytes

#!/usr/bin/env bash
# Tolgee · the agent-free install.
#
# Everything prompt.md tells an agent to do, as a script you can read first.
# Run it on the VPS, as a non-root user who is in the docker group:
#
#   DOMAIN_HOST=tolgee.example.com ./install.sh
#
# Authored by caniselfhostit from the upstream documentation:
#   https://docs.tolgee.io/platform/self_hosting/running_with_docker
#   https://docs.tolgee.io/platform/self_hosting/configuration
#   https://github.com/tolgee/tolgee-platform/blob/v3.218.0/docker/app/Dockerfile
#
# Three secrets are generated here, on this machine: the PostgreSQL password,
# the JWT signing secret, and the password of the admin account Tolgee creates
# on its first start. All three go into /srv/tolgee/.env with mode 600 and none
# of them is ever printed.
#
# The check that matters is the one at the end: with TOLGEE_AUTHENTICATION_ENABLED
# unset, the tolgee/tolgee image has no login screen and hands every caller a
# session as the administrator. This script refuses to finish until an
# anonymous request comes back as nobody.
#
# DOMAIN_HOST is also TOLGEE_FRONT_END_URL and the apiUrl every SDK you point at
# this server will carry. Choose it once.
#
# NOT YET VERIFIED: no harness run has been recorded against this script.
set -euo pipefail

APP_DIR="${APP_DIR:-/srv/tolgee}"
DOMAIN_HOST="${DOMAIN_HOST:-}"

die() { printf 'install.sh: %s\n' "$1" >&2; exit 1; }

# --- 1. Refuse to start on a machine that is not ready -----------------------

[ -n "$DOMAIN_HOST" ] || die "set DOMAIN_HOST to the hostname you pointed at this server, e.g. tolgee.example.com"
command -v docker >/dev/null 2>&1 || die "docker is not installed. Run Prompt Zero first."
docker compose version >/dev/null 2>&1 || die "the docker compose plugin is missing"
command -v caddy >/dev/null 2>&1 || die "caddy is not installed on the host. Run Prompt Zero first."
command -v openssl >/dev/null 2>&1 || die "openssl is not installed"

avail_mb="$(free -m | awk '/^Mem:/ {print $7}')"
[ "$avail_mb" -ge 2048 ] || die "only ${avail_mb} MB of RAM available; a JVM plus PostgreSQL wants 2048 MB"
avail_gb="$(df -BG --output=avail /srv | tail -1 | tr -dc '0-9')"
[ "$avail_gb" -ge 10 ] || die "only ${avail_gb} GB free on /srv; this install wants 10 GB"

resolved="$(getent hosts "$DOMAIN_HOST" | awk '{print $1; exit}' || true)"
[ -n "$resolved" ] || die "$DOMAIN_HOST does not resolve yet. Add the A record, wait a minute, run this again."

# --- 2. Lay the files out ----------------------------------------------------

sudo install -d -m 750 -o "$(id -u)" -g "$(id -g)" "$APP_DIR" "$APP_DIR/backups"
sudo install -d -m 700 "$APP_DIR/postgres" "$APP_DIR/data"
install -m 0644 "$(dirname "$0")/compose.yml" "$APP_DIR/compose.yml"
install -m 0644 "$(dirname "$0")/Caddyfile" "$APP_DIR/Caddyfile"

# --- 3. Generate the three secrets, on the server ----------------------------
#
# Hex rather than base64: the JWT secret is taken as raw bytes and upstream
# requires at least 32 characters, and the other two travel inside a JDBC
# connection string. Read the admin password later with
#   sudo grep TOLGEE_AUTHENTICATION_INITIAL_PASSWORD /srv/tolgee/.env

if [ ! -f "$APP_DIR/.env" ]; then
	umask 077
	cat > "$APP_DIR/.env" <<-ENVFILE
		TOLGEE_FRONT_END_URL=https://${DOMAIN_HOST}
		DB_PASSWORD=$(openssl rand -hex 32)
		TOLGEE_AUTHENTICATION_JWT_SECRET=$(openssl rand -hex 32)
		TOLGEE_AUTHENTICATION_INITIAL_PASSWORD=$(openssl rand -hex 24)
	ENVFILE
	chmod 600 "$APP_DIR/.env"
	umask 022
fi

cd "$APP_DIR"
docker compose config >/dev/null

# --- 4. Caddy site block, on the host ----------------------------------------

if ! sudo grep -qF "$DOMAIN_HOST {" /etc/caddy/Caddyfile; then
	sudo cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.before-tolgee"
	printf '\n' | sudo tee -a /etc/caddy/Caddyfile >/dev/null
	sed "s|<DOMAIN>|${DOMAIN_HOST}|g" "$APP_DIR/Caddyfile" | sudo tee -a /etc/caddy/Caddyfile >/dev/null
fi
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

# --- 5. Ports: two open, and none of 8178, 5432 or 25432 is one of them ------

if command -v ufw >/dev/null 2>&1; then
	echo "==> 80/tcp and 443/tcp for Caddy, 443/udp for HTTP/3; 8178, 5432 and 25432 stay closed"
	sudo ufw allow 80/tcp
	sudo ufw allow 443/tcp
	sudo ufw allow 443/udp
	sudo ufw status verbose
fi

# --- 6. Start it -------------------------------------------------------------
#
# Tolgee runs its own schema migrations on the way up. A JVM starting cold on a
# small VPS takes over a minute before it answers anything.

docker compose pull
docker compose up -d

echo "==> waiting for https://${DOMAIN_HOST}/api/public/configuration"
for _ in $(seq 1 30); do
	code="$(curl -sS -o /dev/null -w '%{http_code}' "https://${DOMAIN_HOST}/api/public/configuration" || true)"
	[ "$code" = "200" ] && break
	sleep 10
done
[ "${code:-}" = "200" ] || die "/api/public/configuration answered ${code:-nothing}. Check: docker compose logs --tail 40 tolgee"

conf="$(curl -sS "https://${DOMAIN_HOST}/api/public/configuration" || true)"
printf '%s' "$conf" | grep -q '"version":"v3.218.0"' \
	|| die "the running version is not v3.218.0. Check the image line in $APP_DIR/compose.yml"
printf '%s' "$conf" | grep -q '"allowRegistrations":false' \
	|| die "self-registration is open. Stop and investigate before anyone finds this hostname."

# --- 7. The assert this whole install turns on -------------------------------
#
# With authentication disabled, Tolgee gives an anonymous caller a super-powered
# session as the administrator, so /v2/public/initial-data comes back with a
# populated userInfo object. null is the only acceptable answer here.

printf '%s' "$conf" | grep -q '"authentication":true' \
	|| die "authentication is disabled: this hostname has no login screen. Check TOLGEE_AUTHENTICATION_ENABLED in $APP_DIR/compose.yml"

curl -sS "https://${DOMAIN_HOST}/v2/public/initial-data" | grep -q '"userInfo":null' \
	|| die "an anonymous request was answered as a logged-in user. Stop and investigate."

blocked="$(curl -sS -o /dev/null -w '%{http_code}' "https://${DOMAIN_HOST}/actuator/prometheus" || true)"
[ "$blocked" = "403" ] || die "/actuator/prometheus returned ${blocked}, not 403. The Caddy site block did not take."

# --- 8. The first backup, before day one ends --------------------------------

STAMP="$(date +%Y%m%d-%H%M%S)"
docker compose exec -T postgres pg_dump -U tolgee -d tolgee | gzip > "$APP_DIR/backups/tolgee-db-${STAMP}.sql.gz"
sudo tar -czf "$APP_DIR/backups/tolgee-config-${STAMP}.tar.gz" -C "$APP_DIR" compose.yml .env data -C /etc/caddy Caddyfile
ls -lh "$APP_DIR/backups/"
[ -s "$APP_DIR/backups/tolgee-db-${STAMP}.sql.gz" ] || die "the database dump is empty"

cat <<-DONE

	Tolgee is answering at https://${DOMAIN_HOST}

	  1. Sign in with the username admin. The login box is labelled Email and
	     the account is not an email address; type admin into it anyway. Your
	     password is in $APP_DIR/.env, mode 600. Read it with
	       sudo grep TOLGEE_AUTHENTICATION_INITIAL_PASSWORD $APP_DIR/.env
	     and put it in your password manager. It was not printed here, and
	     there is no mail server, so there is no reset link.
	  2. Authentication was checked, not assumed: an anonymous request to
	     /v2/public/initial-data came back as nobody, and self-registration is
	     off. Leave both that way.
	  3. Nothing is translated yet. Create a project, add your languages, make
	     a project API key in its settings, and point your application's Tolgee
	     SDK at apiUrl https://${DOMAIN_HOST} with that key. The key can edit
	     translations, so keep it in a development configuration.
	  4. First backup written to $APP_DIR/backups: a database dump and a config
	     archive that includes .env. The JWT secret in that file validates every
	     session already issued, so the two files are one backup. They are on
	     the same disk as the data, which is not a backup. Copy them somewhere
	     else tonight.

DONE

What you're signing up for

The part a vendor's comparison page leaves out. None of it is a reason not to do this; all of it is yours the moment you cancel Tolgee.

  • The shipped Docker image has no login screen. Tolgee's docker profile sets authentication to false, and false does not mean a default password, it means every request is served as the administrator, with a super token, to anyone who finds the hostname. This install sets TOLGEE_AUTHENTICATION_ENABLED to true and then proves it by checking that an anonymous API call comes back as nobody. If you install Tolgee any other way, that check is the first minute's work and it is not optional.
  • The licence is split, and the split is where the money is. Everything outside ee/ and webapp/src/ee is Apache-2.0, and those two directories carry Tolgee's own EE licence, which permits non-commercial use only. That is what the paid editions sell: single sign-on and the rest of the enterprise column. What you install here is uncapped on keys, seats and projects, and stops short of that list.
  • You own a PostgreSQL, and the one inside the image is a dead end. The tolgee/tolgee image is built on postgres:13 and will start a database inside itself unless told not to; upstream deprecated that and removes it in v4, and PostgreSQL 13 went end of life in November 2025. This install runs a separate PostgreSQL 17, which means backups are a pg_dump rather than a folder copy.
  • No mail, so no invitations and no password resets. Tolgee works fine without SMTP, which is why this install skips it, but the administrator password generated during setup is the whole way back in, and a second translator arrives only by an invitation you cannot send until you configure mail. Put that password in a password manager on day one.
  • The half that makes Tolgee Tolgee is work in your repository, not on this server. In-context editing means adding the SDK to your application and giving it an API key that can edit translations, which is why that key belongs in a development configuration rather than a shipped bundle. Machine translation and the AI features also stop being metered credits and become API keys you bring from Google, DeepL or whoever you already pay.

Where this came from

“When authentication is disabled, there are no login screen and no permission control. Users get automatically logged in as the administrator account of the Tolgee instance.”

  • The Docker profile inside the image sets tolgee.authentication.enabled to false, and with authentication disabled Tolgee logs every caller in automatically as the administrator account, so an install that does not set TOLGEE_AUTHENTICATION_ENABLED has no login screen at all. source
  • The authentication filter builds a super-token session for the initial user on any request that arrives with no credential whenever authentication is disabled, which is what makes an anonymous caller an administrator rather than a guest. source
  • The tolgee/tolgee image runs a PostgreSQL inside its own container by default, and upstream removes that bundled database in Tolgee v4, so every setup relying on it has to move to an external PostgreSQL first. The bundled server is PostgreSQL 13, which reached end of life in November 2025. source
  • Everything under the ee/ and webapp/src/ee directories is governed by a separate Tolgee EE licence that permits non-commercial use only; everything outside those directories defaults to Apache License 2.0, which is why GitHub reports no single licence for the repository. source
  • The published image is built on postgres:13.23-alpine with a Temurin JRE copied in, exposes 8080, declares /data as a volume, and carries its own healthcheck against /actuator/health every ten seconds with twenty retries. source

Questions people actually ask

Answered from this page's own data — the same numbers, in sentences.

  • Can I self-host Tolgee?

    Not Tolgee itself — the vendor does not ship a version you can run on your own server. What you can self-host is the job people pay it for, and the answer to that is Tolgee. Translation keys, in-context editing and an API your own app calls, on a hostname you own, with no key quota and no seat count. The install is one evening: 2 containers behind Caddy with automatic TLS, secrets generated on the server rather than in a chat window, and a first backup taken before the agent says it is done, in about 90 minutes. The prompt on this page does it; the compose.yml, Caddyfile and install.sh below do the same install with no agent at all.

  • What replaces Tolgee?

    Tolgee. Translation keys, in-context editing and an API your own app calls, on a hostname you own, with no key quota and no seat count. The same repository the cloud runs, published by the same company, and the one place the in-context editing story survives self-hosting intact: the SDKs, the CLI, the REST API, the Figma plugin and the alt-click editor are all in the Apache-2.0 core. What self-hosting removes is exactly what the pricing page is built on, the key count and the seat count, and both become whatever your PostgreSQL will hold. Two costs are real. The Docker image ships with authentication disabled, so an install that does not deliberately turn it on hands every visitor a session as the administrator, and that is the first thing to check rather than the last. And the ee/ directory carries a separate non-commercial licence covering what the paid editions sell, so this is the free tier of a commercial product rather than a fully open stack. Also evaluated and ranked below it: Weblate. The prompt on this page installs Tolgee only. Tolgee is Apache-2.0-licensed and free; nothing on this page is a hosted service we sell you.

  • What does self-hosting cost compared to Tolgee?

    2048 MB of RAM and 10 GB of disk — the smallest tier most VPS hosts sell, about $10 a month. Tolgee itself is free and Apache-2.0-licensed; the bill is the server, plus a domain you probably already own. What you stop paying: Tolgee Team, $49/mo — $588 a year.

  • How hard is it really?

    ONE EVENING — 1–3 hours. The rule that produced that verdict: up to three containers and at most one outside integration. You will type more than one command and read a page of documentation, and it will be running before you go to bed. The tier is derived from seven countable facts about the Tolgee install, not from anyone's impression of it, and the whole rubric is published on the methodology page.

  • Can I run Tolgee on my own computer instead of a server?

    Yes — that is the second path in the prompt box above. "On my computer" installs the same Tolgee on the machine you are sitting at: no VPS, no domain, no DNS, and nothing exposed to the internet. It checks for Docker first and installs Docker Desktop if the machine does not have it — macOS, Windows and Linux each get their own step — then binds everything to loopback, so the app answers on http://localhost and only on that computer. The catch: Everything answers at http://localhost:8178, so the SDK only reaches it from an application running on this same computer and no translator on another machine can open a key: on this path Tolgee is a private editor for one person's strings. Same discipline as the cloud path: pinned images, secrets generated on the machine, and a first backup taken before the prompt says it is done.

Content last checked 2026-08-07. Verdicts are derived from the published rubric on /methodology; corrections go through the issue tracker.