Repository navigation
Deployment
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.
This setup puts both ports on their own domains with automatic HTTPS, the way an S3 service exposes its endpoint.
-
Create DNS records for two names pointing at the server, for example
api.example.comfor the private port andfiles.example.comfor links. -
Copy
.env.exampleto.envand 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
-
Start tralha with the proxy profile:
docker compose --profile proxy up -d --build
-
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/10keeps the panel visible only from the listed addresses. The API and MCP keep working from anywhere. - Agent keys with
--expires, and a look attralha key listnow and then.
Connect your local agent to https://api.example.com/mcp as described in MCP server.
Easypanel builds tralha from this repository and serves it over HTTPS through its own Traefik, so the bundled Caddy is not needed.
-
In an Easypanel project, open Templates → Create from Schema.
-
Paste the contents of
deploy/easypanel/schema.jsonand create. -
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.
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.
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.
- Publish port 8000 on the VPN address only, for example
"100.64.0.5:8000:8000"incompose.yaml. - Set
TRALHA_API_URLto the address your machine uses to reach the server over the VPN, such ashttp://my-vps:8000with MagicDNS. Upload URLs given to agents are built from it. - Serve only
TRALHA_FILES_DOMAINthrough a proxy for public links.
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 --buildFile contents stay on disk either way; only metadata and the search index move to Postgres.
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 tralhaThe 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.
git pull
docker compose up -d --buildRead 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.
uv sync
uv run tralha serveImage 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.