Sign inSign up

vscarpenter/gsd-task-manager

By vscarpenter

Updated 3 days ago

Self-hosted offline-first Eisenhower task manager with bundled PocketBase sync.

Image
Developer tools
1

6.1K

vscarpenter/gsd-task-manager repository overview

GSD Task Manager

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:

  • Static Next.js PWA served by Caddy
  • PocketBase sync backend on the same HTTPS origin under /api/*
  • Persistent PocketBase data in /pb_data
  • Server-side at-rest encryption hooks for task content fields
  • Non-root runtime with the PocketBase admin UI hidden from the public app origin

The 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.

Tags

  • edge: latest build from main
  • sha-<short>: immutable build for a specific commit
  • latest, X.Y.Z, X.Y: release tags from vX.Y.Z git tags

Images are published for linux/amd64 and linux/arm64.

Docker Setup & Run Guide

Step-by-step instructions for building and running GSD Task Manager locally with Docker.

Prerequisites

1. Install Docker Desktop
brew install --cask docker

Launch Docker Desktop from Applications and wait for the whale icon in the menu bar to show "running".

2. Verify Docker is available
docker --version
docker compose version

Both commands should print version information without errors.

Build & Run

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 encryption key (required)

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:

  • Recommended: create 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
    
  • Alternative: run compose from inside the docker/ directory (where .env is found automatically):
    cd docker && docker compose up --build
    
  • Shell export: export GSD_TASKS_ENC_KEY=<key> before running compose.

Note: Running docker compose -f docker/docker-compose.yml up from the repo root without --env-file will load .env from the repo root (CWD), not from docker/. 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.

Staging verification gate (JSON fields)

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.

First-Time PocketBase Setup

PocketBase starts with no collections or admin account. Complete these steps once:

  1. Create or update a PocketBase superuser inside the container:
    docker compose exec gsd pocketbase superuser upsert [email protected] 'change-this-password' --dir=/pb_data
    
  2. Set up the tasks collection via the setup script from the repo root:
    PB_URL=https://localhost \
    [email protected] \
    PB_ADMIN_PASSWORD='change-this-password' \
    ./scripts/setup-pocketbase-collections.sh
    
  3. To use the admin UI, temporarily publish PocketBase on localhost only:
    ports:
      - "443:443"
      - "80:80"
      - "127.0.0.1:8090:8090"
    
    Restart, open http://127.0.0.1:8090/_/, then remove the port mapping when setup is complete.
  4. Optionally configure OAuth providers under Settings → Auth providers in the local admin dashboard.

Test the App

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.

Verify the Reverse Proxy

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.

Useful Commands

CommandPurpose
docker compose up --buildBuild and start (foreground, shows logs)
docker compose up --build -dBuild and start in background
docker compose logs -fFollow logs when running detached
docker compose downStop and remove container (data preserved)
docker compose down -vStop and delete all PocketBase data
docker compose build --no-cacheForce full rebuild (skip layer cache)

Note: Run all docker compose commands from the docker/ directory.

Trusted Local Certificates (Optional)

By default Caddy generates a self-signed certificate, which causes browser warnings. To eliminate them, use mkcert:

1. Install mkcert and create a local CA
brew install mkcert
mkcert -install
2. Generate certificates for localhost
mkdir -p docker/certs
mkcert -cert-file docker/certs/cert.pem -key-file docker/certs/key.pem localhost
3. Configure docker-compose.yml

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
4. Restart
docker compose up --build

No more certificate warnings — the browser trusts the mkcert-issued certificate.

Custom PocketBase Version

Override the default PocketBase version at build time:

docker compose build --build-arg POCKETBASE_VERSION=0.26.9

Data Persistence & Backups

PocketBase data (database, uploaded files) is stored in a Docker volume named pb_data. Data survives container rebuilds.

Back up data
docker cp $(docker compose ps -q gsd):/pb_data ./pb_backup
Reset all data
docker compose down -v

Warning: This permanently deletes all PocketBase data including tasks, users, and settings.

Troubleshooting

Browser shows certificate warning

Expected with self-signed certificates. Click Advanced → Proceed (or equivalent). For trusted certs, follow the mkcert instructions above.

Port 443 already in use

Change the host port mapping in docker-compose.yml:

ports:
  - "8443:443"

Then access the app at https://localhost:8443.

PocketBase admin not loading

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 not working

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).

Build fails during bun install

Make sure Docker Desktop has sufficient resources allocated (at least 4 GB RAM). Check Docker Desktop → Settings → Resources.

Tag summary

Content type

Image

Digest

sha256:9c578c8e4

Size

49.9 MB

Last updated

3 days ago

docker pull vscarpenter/gsd-task-manager:sha-5c16b83