Self-hosting Sumhour
Sumhour runs anywhere Docker runs. Self-hosting is free and has every feature: there are no plans, trials or limits on your own server. You need a machine with 1 GB of memory, Docker with Compose v2, and a domain name if people outside your network will use it.
Install in one step
Section titled “Install in one step”curl -fsSL https://raw.githubusercontent.com/sumhour/sumhour/main/deploy/selfhost/install.sh | sh -s -- --url https://time.example.comThe installer makes a sumhour folder with compose.yml, backup.sh, restore.sh and a .env
holding fresh secrets. It then starts three containers (the app, the background worker and
Postgres 17) and waits until Sumhour answers. Options:
| Option | Default | What it does |
|---|---|---|
--url |
http://localhost:3000 |
The address people open. Sign-in links and passkeys depend on it. |
--port |
3000 |
The port on this machine. |
--dir |
./sumhour |
Where the files go. |
Running the installer again keeps your .env and your data. It pulls the newest images and
restarts, which makes it an upgrade too.
Install by hand
Section titled “Install by hand”- Download
deploy/selfhost/compose.ymlanddeploy/selfhost/env.exampleinto an empty folder. - Copy
env.exampleto.envand fill inAPP_URL,AUTH_SECRETandPOSTGRES_PASSWORD. For the two secrets,openssl rand -hex 32gives a good value. - Run
docker compose up -d. Migrations run on every start, so the database is ready the first time.
The first sign-in
Section titled “The first sign-in”Open your address and sign up with your email. Sumhour signs people in with emailed links or passkeys; there are no passwords. Until email is set up, each link is written to the app log:
docker compose logs app | grep -o 'http[^ ]*magic-link[^ ]*' | tail -n 1Once you are in, add a passkey under your name, then Sign-in and security. After that you can sign in without email at all.
Set one of these in .env and restart with docker compose up -d:
SMTP_URL=smtps://user:[email protected]:465works with any mail provider.RESEND_API_KEY=re_...uses Resend.
Set EMAIL_FROM to an address your provider allows you to send from, such as
Sumhour <[email protected]>.
Put a reverse proxy in front of port 3000 and set APP_URL to the https address. With Caddy, which
gets certificates by itself, the whole configuration is:
time.example.com { reverse_proxy localhost:3000}Sumhour trusts forwarded headers only from proxies on the same machine or a private network, so a proxy on the same host or in the same Docker network works without settings.
Backups
Section titled “Backups”./backup.sh saves the database and the uploaded files (logos and receipts) into backups/. It
checks that the database dump can be read and keeps the newest 14 of each (KEEP=30 ./backup.sh
keeps more). Run it every night with cron, and copy the backups folder to another machine:
15 3 * * * cd /opt/sumhour && ./backup.sh >> backups/backup.log 2>&1To restore, run it with the two files. This replaces the current data, so it asks you to type
restore first:
./restore.sh backups/sumhour-db-20261001-031500.dump backups/sumhour-files-20261001-031500.tar.gzKeep your .env with the backups. Without the same AUTH_SECRET, restored workspaces lose their
connected integrations and everyone has to sign in again.
Upgrades
Section titled “Upgrades”./backup.shdocker compose pulldocker compose up -dSUMHOUR_VERSION=latest in .env follows each release. To upgrade only when you decide, set it to
a version such as 0.1.0 and change it when you are ready. Release notes are in CHANGELOG.md.
One container
Section titled “One container”The standard setup runs the background jobs (reminders, recurring invoices, imports, webhooks) in
their own worker container. Platforms that run a single container can set WORKER=inline on the
app instead, and the jobs run inside it. Never run both.
More CPU cores
Section titled “More CPU cores”The app runs as one process, which uses one CPU core. On a server with spare cores, set
WEB_CONCURRENCY in .env to the number of cores you can give it, then run docker compose up -d.
The processes share the port. Each one takes about 60 MB of memory when idle and up to 300 MB when
busy. With WORKER=inline, the background jobs still run once, in the first process. Leave it at 1
on a small server.
Unraid
Section titled “Unraid”Add deploy/unraid/sumhour.xml as a template in Community Applications. It runs the app and the
jobs in one container and needs a Postgres 17 database; install one from Community Applications,
create a sumhour database, and enter its address as the Database URL.
Portainer
Section titled “Portainer”Add https://raw.githubusercontent.com/sumhour/sumhour/main/deploy/portainer/templates.json under
Settings, App Templates. The Sumhour template deploys deploy/selfhost/compose.yml as a stack and
asks for the address and the two secrets.
One-click hosting
Section titled “One-click hosting”On Render, render.yaml in the repository creates the app (with the jobs inside), a Postgres
database and a small disk. Render fills in APP_URL with its own address; set yours when you add a
domain. On DigitalOcean App Platform, .do/deploy.template.yaml does the same with a development
database.
Optional features
Section titled “Optional features”| Setting | Turns on |
|---|---|
ANTHROPIC_API_KEY |
The in-app assistant. |
QBO_CLIENT_ID, QBO_CLIENT_SECRET |
Sending invoices to QuickBooks Online (your own Intuit app). |
XERO_CLIENT_ID, XERO_CLIENT_SECRET |
Sending invoices to Xero (your own Xero app). |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
Time suggestions from Google Calendar, and “Continue with Google” on the sign-in pages (add the redirect URI <APP_URL>/api/auth/callback/google). |
MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET |
“Continue with Microsoft” on the sign-in pages: an Entra app registration for organizational and personal accounts, redirect URI <APP_URL>/api/auth/callback/microsoft, optional ID-token claims email and xms_edov. |
Every other variable is described in .env.example at the root of the repository.
When something is wrong
Section titled “When something is wrong”docker compose psshows whether all three containers are up.docker compose logs app --tail 100shows the app’s last messages, including why it would not start (a missing or too shortAUTH_SECRETis the usual one).curl http://localhost:3000/healthzanswers{"ok":true,...}with the running version.
