This runs one PostgreSQL database, two Timekeeper server instances and HAProxy
on your computer. The API is available at http://127.0.0.1:8080/api/v1.
Database files and credentials live in a host directory outside the checkout.
Use Docker Desktop or Docker Engine with the Compose plugin. The commands below use a Bash shell; on Windows, use WSL. You also need Git and Python 3. Java and Maven run inside the Docker build.
For a new checkout:
git clone https://github.com/turesheim/eclipse-timekeeper.git
cd eclipse-timekeeper/server/deploy
If you already have the repository, open server/deploy in that checkout.
Confirm Docker is running:
docker version
docker compose version
Run the remaining commands from server/deploy.
For a fresh installation, create .env with these values. If .env already
exists, edit it instead of replacing your existing settings.
cat > .env <<EOF
COMPOSE_PROJECT_NAME=timekeeper-local
TIMEKEEPER_STATE_DIR="$HOME/.local/share/timekeeper-server/state"
TIMEKEEPER_BIND_ADDRESS=127.0.0.1
TIMEKEEPER_HTTP_PORT=8080
TIMEKEEPER_SERVER_IMAGE=timekeeper-server:local
TIMEKEEPER_POSTGRES_IMAGE=postgres:18.6-bookworm
EOF
Choose a different HTTP port if 8080 is occupied. Use an absolute, writable
path for TIMEKEEPER_STATE_DIR; keep the same path when restarting or rebuilding.
On Docker Desktop, allow file sharing for that directory if requested.
Create credentials and the initial account:
python3 setup.py --owner alice
Replace alice with the stable owner ID to initialize, or omit --owner for a
new generated identity. Save the one-time administrator code printed by setup;
it expires after 15 minutes. Setup creates database passwords and an empty legacy
token registry. Re-running preserves credentials and data. See the
account guide for expired codes and migration of existing owners.
Build the server from your checkout, then start the regular Compose deployment:
export TIMEKEEPER_BUILD_COMMIT="$(git rev-parse HEAD)"
export TIMEKEEPER_BUILD_DIRTY=$(if [ -z "$(git status --porcelain)" ]; then printf false; else printf true; fi)
docker compose -f compose.yaml -f compose.build.yaml build api-1
sh set-deployment-time.sh
docker compose up -d --wait --wait-timeout 180
docker compose ps
Both server instances use the same built image. Docker downloads the official PostgreSQL and HAProxy images as needed. The first build needs internet access and can take a few minutes. All four services should become healthy.
The web client’s footer identifies the running version and source commit. Open
its details to see when it was built, deployed and last started. The build
commands record whether the checkout had modified source. set-deployment-time.sh
records the deployment time in .env for both replicas; use it when deploying a
new build, and keep the existing timestamp when simply restarting services.
If startup fails, inspect the logs:
docker compose logs --tail=100 postgres api-1 api-2 gateway
Check readiness:
curl --fail --silent --show-error http://127.0.0.1:8080/q/health/ready
The response’s status should be UP. If you changed the port, use that
port in all URLs below.
Open http://127.0.0.1:8080/web/, choose First administrator setup, and use the code to choose your username and password. Subsequent visits use normal sign-in. Reload restores the browser cookie. Create a personal Eclipse API token under Account and copy it while it is shown once. The web client is bundled in the server image; no separate frontend service is needed.
To check the account, enter the personal API token at the prompt. These Bash commands keep it out of shell history and curl’s command-line arguments:
printf 'Bearer token: '
read -r -s TIMEKEEPER_TOKEN
printf '\n'
printf 'Authorization: Bearer %s\n' "$TIMEKEEPER_TOKEN" \
| curl --fail --silent --show-error --header @- http://127.0.0.1:8080/api/v1/me
unset TIMEKEEPER_TOKEN
The response’s ownerId should match the account chosen during setup.
In Eclipse, open Preferences → Timekeeper → Backend, select Timekeeper server, and set:
http://127.0.0.1:8080/api/v1Apply the settings and restart Eclipse. Tokens are kept in Eclipse secure storage. Existing local Eclipse records remain in their original database; selecting the server does not import them.
The Docker listener is local to its host. For other devices in your tailnet, use the shared Tailscale guide. Other remote deployments use an HTTPS reverse proxy as described in the deployment guide.
Use the shared Tailscale access guide to reach the server from
other devices in your tailnet over HTTPS. The same setup works on Synology.
Keep TIMEKEEPER_BIND_ADDRESS=127.0.0.1 in .env; Serve runs on the Docker host
and forwards requests to the existing local gateway.
docker compose stop
docker compose start
To remove the containers while retaining their host-mounted data:
docker compose down
docker compose up -d --wait --wait-timeout 180
The state directory contains postgres, config, secrets and backups.
Keep .env and the original secrets along with the data. To make a consistent
logical backup while the database is running:
sh backup.sh
The script prints the backup directory. Restore during maintenance using its absolute path:
sh restore.sh /absolute/path/to/state/backups/<backup-directory>
Restore replaces current application data and stops both server instances for the operation. Do not change PostgreSQL’s major version against existing files; database upgrades require a planned migration.
For an image built locally, rebuild after updating your checkout:
export TIMEKEEPER_BUILD_COMMIT="$(git rev-parse HEAD)"
export TIMEKEEPER_BUILD_DIRTY=$(if [ -z "$(git status --porcelain)" ]; then printf false; else printf true; fi)
docker compose -f compose.yaml -f compose.build.yaml build api-1
sh backup.sh
sh set-deployment-time.sh
docker compose up -d --no-deps --wait --wait-timeout 180 api-1 api-2
This development update can briefly interrupt requests. For rolling updates,
use a server image in a registry: once the server image is published, set
TIMEKEEPER_SERVER_IMAGE=ghcr.io/turesheim/eclipse-timekeeper-server:edge in
.env, then run:
sh update.sh
The updater pulls the configured image, backs up the database, drains one
server at a time and waits for application health and gateway routing before
updating the other. Failed replacements restore the previous images.
PostgreSQL stays running. Run update.sh again whenever you want to check for
a newer image, or schedule it using your operating system’s scheduler.
The updater pulls from a registry; it cannot update the local-only
timekeeper-server:local tag. edge follows tested main builds. For a fixed
version, use an immutable sha-<full-commit> image tag. If GHCR access is denied,
authenticate with docker login ghcr.io using an account with package access,
or use the source-build setup above.