Appearance
Self-Hosting with Docker
Deploy the full TT Time Tracker stack — frontend, API, worker, database, cache, and file storage — using Docker Compose.
Prerequisites
- A Linux server with Docker and Docker Compose installed
- At least 1 GB of free RAM
- A domain name (optional but recommended for HTTPS)
- A Google OAuth app (for user login) — create one at console.cloud.google.com
- An OpenAI API key (for invoice OCR — optional)
Step 1 — Get the source
bash
git clone <repository-url> tt-time-tracker
cd tt-time-trackerStep 2 — Configure the environment
bash
cp .env.backend.example .env.backendEdit .env.backend and fill in every required variable:
bash
# Generate a secure secret (run this on your server)
openssl rand -base64 32Required values to set:
| Variable | What to set |
|---|---|
POSTGRES_PASSWORD | A strong password — do not leave it as changeme |
BETTER_AUTH_SECRET | Output of openssl rand -base64 32 |
BETTER_AUTH_URL | Your API's public URL, e.g. https://api.yourdomain.com |
GOOGLE_CLIENT_ID | From your Google OAuth app |
GOOGLE_CLIENT_SECRET | From your Google OAuth app |
RUSTFS_ACCESS_KEY | A strong random key |
RUSTFS_SECRET_KEY | A strong random secret |
Step 3 — Build and start the stack
bash
docker compose -f docker-compose.yaml up --build -dThis builds the API and worker images and starts all five services:
postgreson port 35432redison port 36379rustfson port 39000 (API) and 39001 (console)apion port 33000worker(no exposed port)
The API automatically runs database migrations on first startup.
Step 4 — Verify the deployment
bash
# Check all services are healthy
docker compose ps
# Tail the API logs
docker compose logs api -fThe API should log Application is running on: http://[::1]:33000.
Step 5 — Set up a reverse proxy (optional)
If you want to serve the frontend and API on standard ports with HTTPS, place a reverse proxy (nginx, Caddy, Traefik) in front:
Example nginx configuration:
nginx
# API
server {
listen 443 ssl;
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:33000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Frontend (if serving the built static files)
server {
listen 443 ssl;
server_name app.yourdomain.com;
location / {
proxy_pass http://localhost:80; # or serve dist/ directly
}
}Step 6 — Build and serve the frontend
The frontend is a static Vue SPA. Build it with the correct API URL:
bash
VITE_API_URL=https://api.yourdomain.com pnpm build:clientThe output is in dist/. Serve it with any static file server, or use the included Dockerfile which produces an nginx image:
bash
docker build \
--build-arg VITE_API_URL=https://api.yourdomain.com \
--build-arg VITE_APP_BASE_URL=https://app.yourdomain.com \
-t tt-frontend .
docker run -d -p 80:80 tt-frontendUpdating
bash
git pull
docker compose -f docker-compose.yaml up --build -dMigrations run automatically when the API container restarts.
Data persistence
All persistent data is stored under ./data/ on the host:
| Directory | Contents |
|---|---|
./data/pg/ | PostgreSQL data |
./data/redis/ | Redis AOF journal |
./data/rustfs/data/ | Uploaded invoice files |
Back up these directories to protect your data.