Skip to content

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-tracker

Step 2 — Configure the environment

bash
cp .env.backend.example .env.backend

Edit .env.backend and fill in every required variable:

bash
# Generate a secure secret (run this on your server)
openssl rand -base64 32

Required values to set:

VariableWhat to set
POSTGRES_PASSWORDA strong password — do not leave it as changeme
BETTER_AUTH_SECRETOutput of openssl rand -base64 32
BETTER_AUTH_URLYour API's public URL, e.g. https://api.yourdomain.com
GOOGLE_CLIENT_IDFrom your Google OAuth app
GOOGLE_CLIENT_SECRETFrom your Google OAuth app
RUSTFS_ACCESS_KEYA strong random key
RUSTFS_SECRET_KEYA strong random secret

Step 3 — Build and start the stack

bash
docker compose -f docker-compose.yaml up --build -d

This builds the API and worker images and starts all five services:

  • postgres on port 35432
  • redis on port 36379
  • rustfs on port 39000 (API) and 39001 (console)
  • api on port 33000
  • worker (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 -f

The 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:client

The 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-frontend

Updating

bash
git pull
docker compose -f docker-compose.yaml up --build -d

Migrations run automatically when the API container restarts.

Data persistence

All persistent data is stored under ./data/ on the host:

DirectoryContents
./data/pg/PostgreSQL data
./data/redis/Redis AOF journal
./data/rustfs/data/Uploaded invoice files

Back up these directories to protect your data.

TT Time Tracker — Internal Documentation