Skip to content

Deployment

Lucas Borges edited this page Oct 29, 2026 · 2 revisions

Both tralha ports bind to localhost in the provided compose.yaml. How you expose them depends on where your agents run and how much of the private port you want on the internet.

On a VPS with the bundled Caddy

This setup puts both ports on their own domains with automatic HTTPS, the way an S3 service exposes its endpoint.

  1. Create DNS records for two names pointing at the server, for example api.example.com for the private port and files.example.com for links.

  2. Copy .env.example to .env and switch to the VPS block:

    TRALHA_API_URL=https://api.example.com
    TRALHA_PUBLIC_URL=https://files.example.com
    TRALHA_API_DOMAIN=api.example.com
    TRALHA_FILES_DOMAIN=files.example.com
  3. Start tralha with the proxy profile:

    docker compose --profile proxy up -d --build
  4. Create buckets and keys inside the container:

    docker compose exec tralha tralha bucket create pr-assets
    docker compose exec tralha tralha key create admin --scope '*:admin'

Caddy listens on ports 80 and 443 and obtains HTTPS certificates on its own; no other port needs to be open in the firewall. It reaches tralha over the compose network, whose subnet 172.30.0.0/24 is the default value of TRALHA_FORWARDED_ALLOW_IPS in .env.example. That lets tralha trust Caddy's X-Forwarded-* headers, so it records real client IPs and knows requests arrived over HTTPS.

Recommended additions when the private port is on the internet:

  • TRALHA_ADMIN_ALLOWED_IPS=203.0.113.7,100.64.0.0/10 keeps the panel visible only from the listed addresses. The API and MCP keep working from anywhere.
  • Agent keys with --expires, and a look at tralha key list now and then.

Connect your local agent to https://api.example.com/mcp as described in MCP server.

On Easypanel

Easypanel builds tralha from this repository and serves it over HTTPS through its own Traefik, so the bundled Caddy is not needed.

  1. In an Easypanel project, open Templates → Create from Schema.

  2. Paste the contents of deploy/easypanel/schema.json and create.

  3. Wait for the first build, then open the service's console and run:

    tralha key create admin --scope '*:admin'
    tralha bucket create pr-assets

The schema creates one app service built from master, the branch that only receives releases. The service domain serves the API, the MCP server and the panel; files- plus the same domain serves links. Data lives in a volume mounted at /data, and the container runs with every Linux capability dropped.

The service has no published ports, so only Traefik reaches it. Because of that, TRALHA_FORWARDED_ALLOW_IPS safely trusts the private address ranges Traefik connects from, and tralha sees real client IPs and HTTPS.

To restrict the panel, add TRALHA_ADMIN_ALLOWED_IPS in the service's environment tab. Zero-downtime deploys are off, so an update briefly stops the service instead of running two copies against the same SQLite database.

With your own proxy

Any reverse proxy works if it terminates TLS, forwards X-Forwarded-For and X-Forwarded-Proto, and sends one hostname to port 8000 and another to port 8001. Set TRALHA_FORWARDED_ALLOW_IPS to the address the proxy connects from: 127.0.0.1 when it runs on the host, or its network when it runs in another container. If that value is wrong, tralha logs the proxy's IP instead of the client's and may issue panel cookies without the Secure flag.

Behind a VPN

If you would rather keep the API off the internet, put the server and your machines on a VPN such as Tailscale and expose only the link domain publicly.

  1. Publish port 8000 on the VPN address only, for example "100.64.0.5:8000:8000" in compose.yaml.
  2. Set TRALHA_API_URL to the address your machine uses to reach the server over the VPN, such as http://my-vps:8000 with MagicDNS. Upload URLs given to agents are built from it.
  3. Serve only TRALHA_FILES_DOMAIN through a proxy for public links.

Postgres

SQLite in the data volume is enough for one person and their agents. To use Postgres instead, uncomment both Postgres lines in .env with the same password and start the extra profile:

docker compose --profile postgres --profile proxy up -d --build

File contents stay on disk either way; only metadata and the search index move to Postgres.

Backups

Everything lives in the tralha-data volume: the SQLite database, the blobs and the thumbnails, which can be regenerated. A consistent backup needs the database and the blobs from the same moment, so stop tralha briefly while copying:

docker compose stop tralha
docker run --rm -v tralha_tralha-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/tralha-$(date +%F).tar.gz -C /data .
docker compose start tralha

The volume name starts with the compose project name, which is the directory name by default; docker volume ls shows it. With Postgres, back up the database with pg_dump and the volume as above.

Upgrading

git pull
docker compose up -d --build

Read the changelog first. tralha has no migrations yet, so a release that changes the schema says how to move your data, or that it needs a fresh database.

Running without Docker

uv sync
uv run tralha serve

Image sandboxing works the same, since it only needs Linux. Run tralha as a dedicated unprivileged user: the worker's process limit has no effect for root.

Clone this wiki locally