Self-hosted sync server for OxiTick: private, offline-first tasks and notes (amd64/arm64).
5.0K
Self-hosted sync server for OxiTick — a private, offline-first app for personal or collaborative to-do lists and notes. The app works fully offline; point it at this server to sync your data across devices and share lists with the people you choose. Your data never leaves infrastructure you control: no third-party cloud, no tracking, no ads.
⚠️ Never store passwords in OxiTick. A to-do/notes manager is private, but it is not a vault — passwords, recovery codes, and other secrets belong in a specialized password manager with encryption at rest and a secret-specific security model. Our recommendation for a personal, private password manager without cloud sync: IntelliWallet.
Images: linux/amd64 and linux/arm64 (Raspberry Pi 4/5, Apple Silicon hosts, ARM VPS).
docker run -d --name oxitick -p 15773:15773 -v ./data:/app/data oxisoft/oxitick:latest
That's it — no configuration required. Auth secrets are generated automatically on first start and stored in the data volume. Or with compose:
# docker-compose.yml
services:
oxitick:
image: oxisoft/oxitick:latest
ports:
- "15773:15773"
volumes:
- ./data:/app/data
environment:
# Recommended: create the admin account at first start (see below)
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=change-me-min-8-chars
restart: unless-stopped
docker compose up -d
Then open http://your-host:15773/admin, log in, and create app user accounts under Users. In the OxiTick app: Settings → Sync Settings → enter the server URL and the user credentials.
Two ways to create the admin (for the /admin panel):
ADMIN_USERNAME and ADMIN_PASSWORD — the account is created before the server accepts its first request, so there is no window where an unauthenticated visitor could claim /admin/setup. Once an admin exists, these variables are ignored (they never overwrite)./admin redirects to a one-time setup page. Fine for trusted networks; on anything internet-reachable, prefer option 1.App user accounts are admin-created only — there is no public registration endpoint.
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | no | auto | Signing secret for auth tokens. If unset, a strong secret is generated on first start and stored at DATA_DIR/jwt_secret (keep the volume — losing it logs every device out). Set it explicitly only if you rotate secrets yourself or run multiple instances against one database. |
ADMIN_USERNAME | no | — | Bootstrap admin account name (min 3 chars). Ignored once an admin exists. |
ADMIN_PASSWORD | no | — | Bootstrap admin password (min 8 chars). Both variables must be set together. |
PORT | no | 15773 | HTTP listen port inside the container. OxiTick deliberately defaults to its own unique port instead of a common one like 8080, so it never collides with other services. |
HOST | no | 0.0.0.0 | Listen address. |
DB_PATH | no | /app/data/oxitick.db | SQLite database path (inside the volume by default). |
DATA_DIR | no | /app/data | Directory for admin config, file-attachment uploads, and the generated JWT secret. |
JWT_EXPIRY | no | 24h | Access token lifetime (Go duration). |
REFRESH_TOKEN_EXPIRY | no | 720h | Refresh token lifetime. |
| Path | Contents |
|---|---|
/app/data | SQLite database, admin configuration, uploaded file attachments, and the generated JWT secret — this is all your state; back it up. |
⚠️ Always mount
/app/datato a named volume or host directory. If you run the container without a mount, Docker puts/app/datain an anonymous volume: your data survives restarts, but recreating the container (docker rm+run, image update, compose recreate) silently starts with an empty database and a fresh JWT secret — all data is gone and every device is logged out. With a real mount (-v ./data:/app/data), containers are disposable and your state isn't.
To keep the data at a fixed path on the host (easy to find, easy to back up), bind-mount an absolute directory instead of the relative ./data:
# docker-compose.yml — data lives in /srv/oxitick/data on the host
services:
oxitick:
image: oxisoft/oxitick:latest
ports:
- "15773:15773"
volumes:
- /srv/oxitick/data:/app/data
environment:
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=change-me-min-8-chars
restart: unless-stopped
The container runs as a non-root user (UID 1000), so the host directory must be writable by it:
sudo mkdir -p /srv/oxitick/data
sudo chown -R 1000:1000 /srv/oxitick/data
Backing up is then just copying /srv/oxitick/data (stop the container first, or use SQLite's .backup, so the database isn't copied mid-write).
Alternatively, a named volume (- oxitick_data:/app/data plus a top-level volumes: {oxitick_data:}) lets Docker manage the location and permissions for you — best when you don't care where the files physically live.
GET /health → {"status":"ok","service":"oxitick","version":"v1.2.3"} — the running version is also shown on the admin dashboard.
The container serves plain HTTP; run it behind your reverse proxy of choice. Self-contained automatic HTTPS with a DOMAIN variable works well with a Caddy sidecar:
services:
oxitick:
image: oxisoft/oxitick:latest
volumes: [ "./data:/app/data" ]
restart: unless-stopped
caddy:
image: caddy:2
ports: [ "80:80", "443:443" ]
environment: [ "DOMAIN=${DOMAIN}" ]
volumes:
- caddy_data:/data
- ./Caddyfile:/etc/caddy/Caddyfile
restart: unless-stopped
volumes:
caddy_data:
# Caddyfile
{$DOMAIN} {
reverse_proxy oxitick:15773
}
DNS for DOMAIN must point at the host and ports 80/443 must be reachable; certificates are obtained and renewed automatically.
latest — most recent releaseX.Y.Z — exact release (recommended for production)X.Y — latest patch of a minor lineMade by OxiSoft · App website: oxitick.com
Content type
Image
Digest
sha256:692617824…
Size
24.4 MB
Last updated
8 days ago
docker pull oxisoft/oxitick