Analytics cookies only (Google Analytics, PostHog). No ad networks, no cross-site tracking. See our privacy policy.
A modern open-source time tracker for freelancers and agencies: track time against projects, tasks and clients, set billable rates per project, project member or organization, work in several organizations with roles and permissions, and import from Toggl and Clockify. A Laravel application with PostgreSQL; the self-hosting examples run an app, scheduler and queue worker plus Gotenberg.
Runs on a VPS from $6/mo (Kamatera 2GB, 2 GB RAM) · prices checked Sep 2026
Pick solidtime when you bill by the hour and want modern time tracking you can host: the README calls it a modern open-source time tracking application for freelancers and agencies, with projects, tasks and clients, billable rates per project, project member, organization member or organization, several organizations under one account, roles and permissions, and import from Toggl and Clockify. It is AGPL-3.0 PHP (Laravel) over PostgreSQL, and a hosted solidtime cloud exists. Our snippet follows the project's `1-docker-with-database` self-hosting example (app, scheduler, queue worker, PostgreSQL and Gotenberg) with the image pinned to a release tag. We did not find an upstream RAM minimum, so our 1 GB floor is an estimate. The catalog's Kimai is the long-established AGPL-3.0 alternative with SAML and LDAP login, and Traggo and TimeTagger are the lighter tag-based trackers.
Paid link — we earn a commission if you shop through it.
Run these commands on your server:
# solidtime — time tracking — based on the project's self-hosting examples (1-docker-with-database), pinned to 0.22.1
mkdir -p logs app-storage
sudo chown -R 1000:1000 logs app-storage
DB_PW=$(openssl rand -hex 12)
cat > .env <<EOF
DB_DATABASE=solidtime
DB_USERNAME=solidtime
DB_PASSWORD=$DB_PW
FORWARD_APP_PORT=8000
SOLIDTIME_IMAGE_TAG=0.22.1
EOF
cat > laravel.env <<EOF
APP_NAME="solidtime"
VITE_APP_NAME="solidtime"
APP_ENV="production"
APP_DEBUG="false"
APP_URL="http://SERVER_IP:8000"
APP_FORCE_HTTPS="false"
SUPER_ADMINS="admin@example.com"
LOG_CHANNEL="stderr_daily"
LOG_LEVEL="info"
DB_CONNECTION="pgsql"
DB_HOST="database"
DB_PORT="5432"
DB_SSL_MODE="prefer"
DB_DATABASE="solidtime"
DB_USERNAME="solidtime"
DB_PASSWORD="$DB_PW"
MAIL_MAILER="log"
QUEUE_CONNECTION="database"
FILESYSTEM_DISK="local"
PUBLIC_FILESYSTEM_DISK="public"
GOTENBERG_URL="http://gotenberg:3000"
EOF
cat > docker-compose.yml <<'EOF'
services:
app:
restart: always
image: "solidtime/solidtime:${SOLIDTIME_IMAGE_TAG}"
user: "1000:1000"
ports:
- '${FORWARD_APP_PORT}:8000'
volumes:
- "app-storage:/var/www/html/storage"
- "./logs:/var/www/html/storage/logs"
- "./app-storage:/var/www/html/storage/app"
environment:
CONTAINER_MODE: http
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/health-check/up"]
env_file:
- laravel.env
depends_on:
- database
scheduler:
restart: always
image: "solidtime/solidtime:${SOLIDTIME_IMAGE_TAG}"
user: "1000:1000"
volumes:
- "app-storage:/var/www/html/storage"
- "./logs:/var/www/html/storage/logs"
- "./app-storage:/var/www/html/storage/app"
environment:
CONTAINER_MODE: scheduler
healthcheck:
test: ["CMD", "healthcheck"]
env_file:
- laravel.env
depends_on:
- database
queue:
restart: always
image: "solidtime/solidtime:${SOLIDTIME_IMAGE_TAG}"
user: "1000:1000"
volumes:
- "app-storage:/var/www/html/storage"
- "./logs:/var/www/html/storage/logs"
- "./app-storage:/var/www/html/storage/app"
environment:
CONTAINER_MODE: worker
WORKER_COMMAND: "php /var/www/html/artisan queue:work"
healthcheck:
test: ["CMD", "healthcheck"]
env_file:
- laravel.env
depends_on:
- database
database:
restart: always
image: postgres:15
environment:
POSTGRES_DB: ${DB_DATABASE}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- database-storage:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}"]
retries: 3
timeout: 5s
gotenberg:
image: gotenberg/gotenberg:8
volumes:
database-storage:
app-storage:
EOF
docker compose run --rm -T scheduler php artisan self-host:generate-keys >> laravel.env
until docker compose exec -T database pg_isready -q -U solidtime; do sleep 2; done
docker compose run --rm -T scheduler php artisan migrate --force
docker compose run --rm -T scheduler php artisan admin:user:create "Admin" "admin@example.com" --verify-email
docker compose up -d
timeout 180 bash -c 'until curl -fsS http://localhost:8000/health-check/up >/dev/null; do sleep 5; done'
# → http://SERVER_IP:8000 — sign in as admin@example.com with the password printed abovesolidtime's self-hosting guide and example files flag these before you rely on the stack: