Deploy to a server
deploy/ runs the portal on one Linux server with K3s, a small Kubernetes
distribution. The Helm chart in deploy/chart defines the stack:
| Part | Kind | What it does |
|---|---|---|
matefactory-db |
StatefulSet, postgres:17 |
The database, on a 10 GiB volume (K3s local-path storage) |
matefactory-backend |
Deployment, deploy/backend.Dockerfile |
Runs alembic upgrade head, then the API as one uvicorn worker. One replica, replaced with Recreate, because the agent gateway keeps its connections in that process. |
matefactory-web |
Deployment, deploy/web.Dockerfile |
Caddy with the built web app on port 8080 and the docs on port 8081 |
matefactory |
Ingress | Sends DOMAIN/api to the backend, the rest of DOMAIN to the web app, and DOCS_DOMAIN to the docs. It redirects http:// to https://. |
matefactory-letsencrypt |
cert-manager Issuer | Gets one Let’s Encrypt certificate for both host names over HTTP-01 |
The production deployment is https://factory.tarx.app, on the Hetzner VM 46.224.173.231.
Before the first deploy
Section titled “Before the first deploy”-
The server needs Ubuntu (or another Linux that K3s supports), Docker to build the images, and SSH access as a user who can use
sudo. -
Point DNS for
DOMAINandDOCS_DOMAINat the server. Without a DNS record for the docs, use an sslip.io name such asdocs.203.0.113.7.sslip.io. -
Open ports 80 and 443 in the server’s firewall. Let’s Encrypt checks the domains on port 80. Keep the K3s ports, such as 6443, closed to the internet.
-
Register the GitHub apps with the URLs of the deployment, as in GitHub apps:
- Callback URL (sign-in):
https://DOMAIN/api/auth/callback - Setup URL (GitHub App install):
https://DOMAIN/api/github/setup
- Callback URL (sign-in):
-
Create
/etc/matefactory/matefactory.envon the server fromdeploy/env.example, owned by root with mode 600. It holds the settings from Configuration and these values:Variable Value DOMAINThe portal’s host name. The chart sets APP_BASE_URLtohttps://DOMAIN.DOCS_DOMAINThe docs site’s host name PUBLIC_IPThe server’s public address. The ingress takes ports 80 and 443 on this address only, so services that listen on other addresses, such as Tailscale, keep theirs. POSTGRES_PASSWORDA random password. The chart builds DATABASE_URLfrom it.Write
GITHUB_APP_PRIVATE_KEYon one line, with\nfor each line break.
The Daytona API key is not a server setting. Each workspace connects Daytona in
Settings → Integrations, and the portal stores the key encrypted with ENCRYPTION_KEY.
Deploy
Section titled “Deploy”From a clean checkout, run:
deploy/deploy.sh user@hostThe script uploads the committed HEAD to /opt/matefactory/src and runs deploy/server.sh there
as root. server.sh:
- Installs K3s (without its ServiceLB), Helm and cert-manager on the first run, at the versions
pinned in the script. It configures K3s’s Traefik to listen on
PUBLIC_IP, and turns off Traefik’s 60-second read timeout so agent event streams, terminals and desktops stay open. - Builds the backend and web images with Docker, tagged with the commit, and imports them into K3s. Nothing is pushed to a registry.
- Copies the env file into the Kubernetes Secret
matefactory-env. The Secret is never in Git. - Runs
helm upgrade --install matefactory deploy/chart. A changed env file restarts the backend.
The script then waits for https://DOMAIN/api/health. The env file and the database volume stay
as they are. Pass SSH options in DEPLOY_SSH_OPTS, for example
DEPLOY_SSH_OPTS="-i ~/.ssh/deploy_key -o IdentitiesOnly=yes".
Operate
Section titled “Operate”Run these on the server:
alias k='sudo k3s kubectl -n matefactory'k get pods,ingress,certificatek logs -f deploy/matefactory-backendk rollout restart deploy/matefactory-backendk exec matefactory-db-0 -- pg_dump -U factory -Fc factory > factory.dumpk exec -i matefactory-db-0 -- pg_restore -U factory -d factory --clean --if-exists --no-owner < factory.dumpAfter you edit the env file, run deploy/deploy.sh again: it updates the Secret and restarts the
backend.
