PocketKid is a mobile-first family ledger for keeping a shared tally of money parents and children owe one another. Each child has a signed account balance and a chronological history of entries; a negative balance is a normal ledger state, not an invalid wallet condition.
Fork repository: https://github.com/nocona71/pocketkid
Upstream project: https://github.com/pernastefano/pocketkid
Parents and children access the same app with role-based features:
- Children can propose reward, credit, and debit entries for their own account.
- Parents can approve/reject proposals, set per-child minimum balances, choose the ledger currency and date display format, record direct or recurring entries, manage users, and review full history.
- Transaction creation and approval actors are retained as identity snapshots, even if the user account is later removed.
The signed balance is expressed from the child's perspective: a positive value is owed or credited to the child, zero is settled, and a negative value is owed by the child to the family. See the family-ledger product decision for the preferred terminology and compatibility boundaries.
The app is installable as a Progressive Web App (PWA) and supports real Web Push notifications (VAPID) for system-level alerts.
PocketKid uses a simple, production-friendly stack:
- Backend: Python + Flask
- Server-side rendered pages
- Session-based authentication
- Role-based access (parent/child)
- Production container runtime via Gunicorn
- Database: SQLite (local file under
data/) - ORM: Flask-SQLAlchemy
- Frontend:
- Jinja templates
- Custom CSS (mobile-first layout)
- Vanilla JavaScript for dynamic UI, polling, auto-refresh, and push registration
- PWA Layer:
- Web App Manifest
- Service Worker (offline static caching + push event handling)
- Notifications:
- In-app notifications
- Real Web Push (VAPID) via
pywebpush
- Containerization:
- Docker + Docker Compose
- Internationalization (i18n):
- English, Italian, and German locale files
- Per-user preferred language setting
- Python 3.12+
pip
cp .env.example .env
# edit .env with VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY (or VAPID_PRIVATE_KEY_B64)
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-dev.txt
python app.pyOpen:
http://localhost:8000
- No demo users are auto-created.
- The app opens the setup screen to create the first parent account.
- You can optionally create the first child during setup.
docker build -t pocketkid .Before running, provide VAPID keys via environment variables:
cp .env.example .env
# edit .env with real VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY (or VAPID_PRIVATE_KEY_B64)docker run --rm \
--env-file .env \
-p 8000:8000 \
-v $(pwd)/data:/app/data \
pocketkidOpen:
http://localhost:8000
Prepare env file first:
cp .env.example .env
# edit .env with real VAPID keys
# optional: set host user/group for container process
# PUID=1000
# PGID=1000
# optional: pin a published fork image, for example 0.1.0
# POCKETKID_IMAGE_TAG=0.1.0docker compose up -d --buildThe default compose file:
- maps port
8000 - persists app data in
./data - restarts container automatically (
unless-stopped) - supports optional
PUID/PGIDto run the container as a specific host UID/GID (default1000:1000)
This project is developed in the devcontainer and validated through GitHub Actions.
make test
make coverage
make smoke
make devThe equivalent direct commands are:
./scripts/check
python -m coverage run --source=pocketkid -m unittest discover -s tests
python -m coverage report -m
./scripts/container-smoke --build
python app.pyCoverage is a local development report; CI does not enforce a coverage threshold.
The Dev Container uses the Dev Containers Docker-outside-of-Docker feature.
It installs the Docker CLI in the container and connects it to the Docker
engine on the host; it does not run a separate Docker daemon inside the
container. This lets make smoke build and run the application image without
installing Docker tooling separately in the Dev Container.
After changing the feature configuration, run Dev Containers: Rebuild Container, then verify the connection with:
docker info
make smokeThe Docker engine must be available on the machine running the Dev Container. Commands issued by the container control that engine, so treat Docker access as highly privileged: only use it with trusted repository code and do not expose the Docker socket to untrusted containers or processes.
GitHub CLI authentication is stored in the persistent gh config volume.
Authenticate from a Dev Container terminal with gh auth login; do not pass
GH_TOKEN through the host environment when using this login. The container
repairs ownership of the mounted config volume after creation, including
volumes created before this ownership fix.
SSH keys are stored in a separate persistent volume mounted at ~/.ssh.
Treat both volumes as sensitive because they contain GitHub credentials and
private keys. A key generated before this volume is first mounted is not copied
into it; after rebuilding, create and upload a key with ssh-keygen and
gh ssh-key add ~/.ssh/id_ed25519.pub --title pocketkid-devcontainer if SSH
authentication is not available.
- Create feature and bug-fix branches from
master. - Open PRs through GitHub or the
ghCLI. - Feature and bug-fix PRs must reference at least one GitHub issue using closing syntax, for example:
Closes #123
- Doc-only and maintenance PRs are exempt from the issue-link requirement.
- GitHub Issues remain the live source of truth for status, ownership, and discussion.
This keeps issue tracking, implementation work, and release validation linked in a simple GitHub-native flow.
This fork uses an independent semantic version from the upstream project. The
current fork version is stored in VERSION, and release tags use the form
fork-v<version>.
See docs/RELEASING.md for the tested GitHub Release and
GHCR publishing process.
- Manage children (create/delete with double confirmation)
- Manage other parents (create/delete with safety constraints)
- Configure challenges with predefined reward amounts
- Review and approve/reject proposed child entries
- Record direct credit and debit entries
- Create recurring entries with configurable frequency:
- daily
- weekly
- biweekly
- monthly
- Reset child passwords
- Configure the ledger currency and family-wide date display format
- Configure personal language and password
- View their signed balance and ledger history with the resulting balance after each entry
- Submit reward request (linked to configured challenge)
- Propose debit and credit entries
- Change personal language and password
- Child proposes a reward, credit, or debit entry.
- Parent receives notification and sees pending request.
- Parent approves or rejects:
- Approve reward/credit → the signed account balance increases.
- Approve debit → the balance decreases down to the configured minimum and may become negative.
- Reject → request state updates to rejected.
- An approved entry is saved in ledger history and notifications are generated.
- In-app notifications are available in the top-right mail icon.
- System notifications are sent via Web Push when supported.
- Pages auto-refresh on incoming events to keep dashboards synchronized without manual actions.
- Static assets are cached by the Service Worker.
- Navigation is network-first to avoid stale pages after submit/approval actions.
HTTPS is required for full PWA + Push behavior in real devices/environments (except localhost during development).
- Service Worker reliability in production
- Web Push delivery on iOS/Android/browser
- Proper installability and secure context
Use a reverse proxy with TLS termination (Nginx/Caddy/Traefik) in front of the Flask app/container.
Example architecture:
https://your-domain→ reverse proxy (TLS certificate)- proxy forwards to PocketKid (Gunicorn) on internal port
8000
PocketKid reads VAPID keys from environment variables:
VAPID_PUBLIC_KEY(required)VAPID_PRIVATE_KEY(required ifVAPID_PRIVATE_KEY_B64is not set)VAPID_PRIVATE_KEY_B64(optional alternative toVAPID_PRIVATE_KEY)VAPID_SUBJECT(optional, default:mailto:pocketkid@example.com)
For Docker, set these in .env and load them via --env-file or Docker Compose.
Run from the project root after creating the virtual environment:
source .venv/bin/activate
python - <<'PY'
import base64
from py_vapid import Vapid
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
v = Vapid()
v.generate_keys()
public_bytes = v.public_key.public_bytes(
encoding=Encoding.X962,
format=PublicFormat.UncompressedPoint,
)
public_key = base64.urlsafe_b64encode(public_bytes).decode().rstrip('=')
private_key = v.private_pem().decode('utf-8').replace('\n', '\\n')
print(f"VAPID_PUBLIC_KEY={public_key}")
print(f"VAPID_PRIVATE_KEY={private_key}")
print("VAPID_SUBJECT=mailto:pocketkid@example.com")
PYCopy the output lines into your .env file.
source .venv/bin/activate
python -m py_vapid --gen
python -m py_vapid --applicationServerKey -k private_key.pem- Use the
Application Server Keyvalue asVAPID_PUBLIC_KEY. - Put the full PEM private key into
VAPID_PRIVATE_KEYusing escaped newlines (\n), or encode it to base64 and useVAPID_PRIVATE_KEY_B64.
You can generate keys inside the same Docker image used by the app.
docker compose run --rm pocketkid python - <<'PY'
import base64
from py_vapid import Vapid
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
v = Vapid()
v.generate_keys()
public_bytes = v.public_key.public_bytes(
encoding=Encoding.X962,
format=PublicFormat.UncompressedPoint,
)
public_key = base64.urlsafe_b64encode(public_bytes).decode().rstrip('=')
private_key = v.private_pem().decode('utf-8').replace('\n', '\\n')
print(f"VAPID_PUBLIC_KEY={public_key}")
print(f"VAPID_PRIVATE_KEY={private_key}")
print("VAPID_SUBJECT=mailto:pocketkid@example.com")
PYThen paste these lines into .env and restart:
docker compose up -d --buildPocketKid includes a helper script to generate .env-ready VAPID values:
source .venv/bin/activate
python scripts/generate_vapid.pyOptional subject:
python scripts/generate_vapid.py --subject mailto:admin@example.comWrite directly to .env:
python scripts/generate_vapid.py --write-envCustom env file:
python scripts/generate_vapid.py --write-env --env-file .env.production- Open the app URL in Safari.
- Tap Share.
- Tap Add to Home Screen.
- Confirm installation.
Notes:
- Push notifications on iOS require HTTPS and supported iOS versions.
- User must allow notification permissions.
- Open the app URL.
- Tap browser menu.
- Tap Install app / Add to Home screen.
- Confirm installation.
Notes:
- Push notifications require HTTPS in production.
- User must grant notification permission.
app.py→ minimal application entrypointpocketkid/config.py→ configuration and pathspocketkid/extensions.py→ shared extensions (SQLAlchemy)pocketkid/models.py→ database modelspocketkid/services.py→ support/business utilities (auth helpers, i18n, push, recurring)pocketkid/routes.py→ HTTP routespocketkid/__init__.py→ app factory and bootstraptemplates/→ UI viewsstatic/→ CSS, JS, Service Worker, manifest, iconslocales/→ i18n files (en.json,it.json)data/→ SQLite DB.env.example→ template for VAPID environment variablesVERSION→ fork release versiondocs/decisions/0002-family-ledger-product-model.md→ product model and terminologydocs/RELEASING.md→ release and container publishing processdocs/upstream-pull-requests.md→ preparing focused contributions for upstreamDockerfile,docker-compose.yml→ container setup
Stefano Perna
This project is licensed under the MIT License. See the LICENSE file for details.





