Self-hosted offline-first Eisenhower task manager with bundled PocketBase sync.
6.1K
Self-hosted image for GSD Task Manager, an offline-first Eisenhower matrix task manager. This image runs the full self-hosted stack in one container:
/api/*/pb_dataThe app works local-first in the browser. Cloud sync is optional; when enabled, synced data lives in your self-hosted PocketBase volume. At-rest encryption is server-side, not end-to-end encryption: the backend decrypts records for API clients.
edge: latest build from mainsha-<short>: immutable build for a specific commitlatest, X.Y.Z, X.Y: release tags from vX.Y.Z git tagsImages are published for linux/amd64 and linux/arm64.
Step-by-step instructions for building and running GSD Task Manager locally with Docker.
brew install --cask docker
Launch Docker Desktop from Applications and wait for the whale icon in the menu bar to show "running".
docker --version
docker compose version
Both commands should print version information without errors.
cd docker
docker compose up --build
The first build takes a few minutes (bun install + Next.js build + PocketBase download). Subsequent builds use Docker layer caching and are much faster.
Once ready you'll see:
===========================================
GSD Task Manager is running!
App: https://localhost
Admin: not exposed on the public app origin
===========================================
Task content is encrypted at rest on the server. Generate a 32-character key once:
openssl rand -hex 16 # 32 hex chars
The docker compose command must be able to find this key. Three equivalent options:
docker/.env with GSD_TASKS_ENC_KEY=<key> and pass it explicitly:
docker compose -f docker/docker-compose.yml --env-file docker/.env up --build
docker/ directory (where .env is found automatically):
cd docker && docker compose up --build
export GSD_TASKS_ENC_KEY=<key> before running compose.Note: Running
docker compose -f docker/docker-compose.yml upfrom the repo root without--env-filewill load.envfrom the repo root (CWD), not fromdocker/. If the key is missing there, the container refuses to start.
Back up the key separately from the database — a database backup is useless without this key, and a lost key makes encrypted data unrecoverable.
Before relying on at-rest encryption in production, validate it against a running PocketBase using the verification harness:
PB_BIN=/path/to/pocketbase ./scripts/verify-pb-encryption.sh
This harness creates a task with non-empty tags, subtasks, and time_entries values,
asserts that all fields (including the json-typed columns) are stored as enc:v1: ciphertext
in SQLite, and asserts that all fields round-trip back to their original arrays over the REST API.
If any json-column assertion fails, the remedy is to change those three columns from json to
text type in scripts/setup-pocketbase-collections.sh and re-confirm that the decrypt hook
still returns arrays over the API.
PocketBase starts with no collections or admin account. Complete these steps once:
docker compose exec gsd pocketbase superuser upsert [email protected] 'change-this-password' --dir=/pb_data
PB_URL=https://localhost \
[email protected] \
PB_ADMIN_PASSWORD='change-this-password' \
./scripts/setup-pocketbase-collections.sh
ports:
- "443:443"
- "80:80"
- "127.0.0.1:8090:8090"
Open https://localhost in your browser. You'll see a self-signed certificate warning — click Advanced → Proceed to localhost (or equivalent). The full app should load with sync pointed at the local PocketBase instance.
Confirm the single-origin setup works by running these from a terminal:
# Static site responds
curl -ks https://localhost/ | head -5
# PocketBase API responds on same origin
curl -ks https://localhost/api/health
# PocketBase admin is not exposed on the app origin
curl -ks -o /dev/null -w "%{http_code}" https://localhost/_/
The static site and API should return 200 from the same https://localhost origin. The admin check should return 404 unless you temporarily publish 127.0.0.1:8090:8090.
| Command | Purpose |
|---|---|
docker compose up --build | Build and start (foreground, shows logs) |
docker compose up --build -d | Build and start in background |
docker compose logs -f | Follow logs when running detached |
docker compose down | Stop and remove container (data preserved) |
docker compose down -v | Stop and delete all PocketBase data |
docker compose build --no-cache | Force full rebuild (skip layer cache) |
Note: Run all
docker composecommands from thedocker/directory.
By default Caddy generates a self-signed certificate, which causes browser warnings. To eliminate them, use mkcert:
brew install mkcert
mkcert -install
mkdir -p docker/certs
mkcert -cert-file docker/certs/cert.pem -key-file docker/certs/key.pem localhost
Uncomment the certificate lines in docker-compose.yml:
volumes:
- pb_data:/pb_data
- ./certs:/certs:ro # ← uncomment this line
environment:
- SITE_ADDRESS=localhost
- TLS_CERT=/certs/cert.pem # ← uncomment this line
- TLS_KEY=/certs/key.pem # ← uncomment this line
docker compose up --build
No more certificate warnings — the browser trusts the mkcert-issued certificate.
Override the default PocketBase version at build time:
docker compose build --build-arg POCKETBASE_VERSION=0.26.9
PocketBase data (database, uploaded files) is stored in a Docker volume named pb_data. Data survives container rebuilds.
docker cp $(docker compose ps -q gsd):/pb_data ./pb_backup
docker compose down -v
Warning: This permanently deletes all PocketBase data including tasks, users, and settings.
Expected with self-signed certificates. Click Advanced → Proceed (or equivalent). For trusted certs, follow the mkcert instructions above.
Change the host port mapping in docker-compose.yml:
ports:
- "8443:443"
Then access the app at https://localhost:8443.
The admin dashboard is not exposed through https://localhost/_/ by default. Temporarily add 127.0.0.1:8090:8090 to ports, restart, and open http://127.0.0.1:8090/_/. If the local port still does not respond, wait a few seconds after container start and check logs:
docker compose logs -f
OAuth providers must be configured in PocketBase admin (Settings → Auth providers). The redirect URL should be https://localhost/api/oauth2-redirect (or your custom SITE_ADDRESS).
bun installMake sure Docker Desktop has sufficient resources allocated (at least 4 GB RAM). Check Docker Desktop → Settings → Resources.
Content type
Image
Digest
sha256:9c578c8e4…
Size
49.9 MB
Last updated
3 days ago
docker pull vscarpenter/gsd-task-manager:sha-5c16b83