All apps · 0 apps
questly
Docker app from mahansford's Repository
Overview
Readme
View on GitHub
Questly
A family points-and-rewards app.
Grown-ups award points for quests and good behaviour.
Kids spend those points in a shop of rewards you control.
[!NOTE]
Built with AI
Every line of this project — the application code, the stylesheet, the icons and this README — was written by Claude (Anthropic's Claude Opus 5) running in Claude Code, to a human's direction. Every commit carries a
Co-Authored-By: Claudetrailer, so the history is honest about it.It is not untested.
tests/smoke_test.pywalks every flow end to end against a real MongoDB — awarding, spending, approving, refunding, PIN login, account changes — and every screen was driven in a browser during development.It has not had a third-party security audit, and it hasn't run anywhere long enough to call it battle-tested. It is a family app intended for a trusted home network. Read the code before running it on anything you care about, as you would with any code you find on the internet.
Self-hosted, no accounts with anyone else, no ads, no subscriptions. It runs on a spare machine or a NAS and is used from any phone, tablet or laptop on the same network. The layout is mobile-first and installs to a home screen as a web app.
- Two sides. Grown-ups log in with an email and password. Kids tap their avatar and type a 4–6 digit PIN — or no PIN at all for the youngest.
- Quests — recurring jobs worth points. A kid taps Done!, a grown-up approves, the points land.
- A shop you stock yourself, with prices, icons and optional limited stock.
- A full ledger. Every point in or out is recorded with a running balance.
What it looks like
For kids
| Pick your face | Type your PIN | Today's quests | Spend your points |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
For grown-ups
| Award points | Approve what's waiting | Run the quest board |
|---|---|---|
![]() |
![]() |
![]() |
Install on Unraid
Questly is in Community Applications — search for Questly in the Apps tab.
It needs a MongoDB server, which isn't bundled: install one from Community
Applications first, then point Questly's MONGO_URI at it. Questly keeps no
local state, so there's no appdata share to configure and nothing to back up
but the database.
Requirements
Docker Engine with the Compose plugin (docker compose). Nothing else — Python
and MongoDB both run inside containers. Works on x86-64 and ARM64.
Quick start
Pre-built multi-architecture images (amd64 and arm64) are published on every release:
docker pull ghcr.io/mahansford/questly:latest # or hansford909/questly:latest
To run the whole stack, database included:
git clone https://github.com/mahansford/questly.git && cd questly
cp .env.example .env
Set a real SECRET_KEY in .env:
sed -i.bak "s|^SECRET_KEY=.*|SECRET_KEY=$(openssl rand -hex 32)|" .env && rm -f .env.bak
docker compose up --build -d
Open http://localhost:37000. The first visit asks you to create a grown-up account, then walks you to the Family page to add your kids.
Optional: start with example content
Fills the shop and quest board with sensible starter items (and, if the app is completely empty, two demo kids):
docker compose exec web flask --app wsgi:app seed-demo
How it works
| Thing | What happens |
|---|---|
| Award points | Quick +10 / +25 / +50 / +100 buttons on each kid's card, or any custom amount, with an optional reason. Points can be taken away too. |
| Quests | Recurring jobs (daily, weekly or one-off) worth a set number of points, assignable to specific kids or everyone. A kid taps Done!, you approve, the points land. |
| Shop | Rewards you create, each with a point cost, an icon, a description and optional limited stock. |
| Buying | Points are deducted the moment a kid buys, and the reward queues up for you to hand over. Turning a request down refunds the points automatically. |
| Approvals | One page listing every quest and purchase waiting on you, with a count badge in the nav. |
| Accounts | Each grown-up can change their own name, login email, icon and password. |
| Locked out? | Either grown-up can set a new password for the other, confirmed with their own password. There is no email recovery, so this is the way back in. |
The rules it enforces
- A kid can never go below zero points, and can't overspend by double-tapping Buy — the deduction is a conditional atomic update.
- Two kids racing for the last limited-stock item can't both win it, for the same reason.
- A daily quest can be claimed once per calendar day and a weekly one once per
ISO week, both in the timezone set by
TZ. - Rejecting a quest claim lets the kid try again in the same period.
- Every mutation is CSRF-protected, and kids can't reach grown-up pages.
Configuration
Everything is set in .env, which Docker Compose reads:
| Variable | Default | Meaning |
|---|---|---|
PORT |
37000 |
Host port the app is served on |
SECRET_KEY |
— | Set this. Signs session cookies |
MONGO_DB |
questly |
Database name |
MONGO_URI |
bundled container | Only needed for an external MongoDB — see below |
TZ |
Europe/London |
Drives dates and daily/weekly quest resets |
.env is gitignored, so secrets stay out of version control.
Using a MongoDB you already run
The compose file ships a MongoDB container for convenience, but the app is
happy talking to any MongoDB 5+ instance. Set MONGO_URI in .env and start
only the web service:
docker compose up -d --no-deps web
--no-deps is what stops Compose from also starting the bundled mongo
service. Without it you get two databases running and only one in use.
MongoDB has to be reachable from inside the web container, which means one of:
- Same docker network — put both containers on the same user-defined
network and address it by container name (
mongodb://my-mongodb:27017/). Docker's defaultbridgenetwork does not resolve container names, so a custom network is required. - Via the host — publish MongoDB's port and point at the host's LAN IP
(
mongodb://192.168.1.50:27017/).
With authentication enabled, include the credentials and the auth database:
mongodb://user:pass@host:27017/?authSource=admin.
The app creates its own collections and indexes on first use, so an empty
database is all it needs. It only ever touches the database named by
MONGO_DB, so sharing an instance with other apps is fine.
Running on a NAS? Docker named volumes often live inside a fixed-size virtual disk that your normal share backups don't cover. Either bind-mount the database to real storage or point
MONGO_URIat a MongoDB container you manage yourself.
On your home network
Once it's running, the app is reachable from other devices at
http://<server-ip>:37000 — handy for kids on tablets and phones. Find the
address with hostname -I (Linux) or ipconfig getifaddr en0 (macOS). A DHCP
reservation in your router keeps it from changing.
Install it to a home screen
Open that address on the device and choose Add to Home Screen (Share menu on iOS, browser menu on Android). It launches full-screen with its own icon and no browser chrome, which is how it's meant to be used.
Questly speaks plain HTTP and assumes a trusted home network. Don't expose it to the internet without putting HTTPS and, ideally, a VPN in front of it.
Everyday commands
docker compose up -d # start
docker compose down # stop (data is kept)
docker compose logs -f web # tail the app logs
docker compose up --build -d # rebuild after changing the code
Add a grown-up from the command line — useful if everyone is locked out:
docker compose exec web flask --app wsgi:app create-parent
Backing up
All data lives in the mongo-data Docker volume.
docker compose exec -T mongo mongodump --db questly --archive --gzip > questly-backup.gz
Restore:
docker compose exec -T mongo mongorestore --archive --gzip --drop < questly-backup.gz
Starting completely over
docker compose down -v # -v also deletes the database volume
Development
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export SECRET_KEY=dev MONGO_URI=mongodb://localhost:27017/
flask --app wsgi:app run --debug --port 8000
You'll need MongoDB listening on localhost:27017.
Tests
An end-to-end smoke test walks every flow — awarding, spending, approving, refunding, PIN login, account changes, the lot — against a throwaway database:
docker compose exec -e MONGO_DB=questly_test web python tests/smoke_test.py
It refuses to run unless the database name ends in _test, so it can't touch
real data.
Regenerating the icons
Every icon comes from one geometric spec, so the SVG and the PNGs can't drift
apart. Edit tools/make_icons.py and run:
pip install pillow && python tools/make_icons.py
Changelog
Notable changes are recorded in CHANGELOG.md, and each release is tagged on the releases page.
Licence
GNU Affero General Public License v3.0 — free and open source.
Use it, run it, modify it, fork it, and use it commercially. One condition: if you distribute a modified version, or run one as a network service other people use, you have to make your source available under the same licence.
That network clause is what AGPL adds over the ordinary GPL, and it's why it suits a self-hosted app — improvements come back to everyone instead of disappearing into a closed hosted product.
Copyright © 2026 Matt Hansford.
[!TIP] Running a modified copy for other people? Section 13 asks you to offer those users its source. A "Source" link in the page footer pointing at your fork is the usual way to satisfy it.
Project layout
app/
__init__.py app factory, CSRF, template filters
db.py Mongo connection + indexes
models.py all domain logic (points ledger, shop, quests, accounts)
cli.py flask CLI commands (seed-demo, create-parent)
views/
public.py landing, who's-here, health check
auth.py first-run setup, grown-up login, kid PIN login
kid.py kid home, shop, history
parent.py dashboard, awards, approvals, shop & quests, family, account
templates/ Jinja templates
static/
css/ one stylesheet
js/ a little vanilla JS — no build step, no dependencies
icons/ app icons, generated
manifest.webmanifest
tools/
make_icons.py regenerates every icon from one spec
tests/
smoke_test.py end-to-end walk through every flow
Collections
| Collection | Holds |
|---|---|
users |
grown-ups and kids (kids carry their cached balance) |
transactions |
the point ledger — every change, with balance after |
rewards |
shop items |
redemptions |
purchases and their approval state |
quests |
the quest board |
quest_claims |
a kid's claim on a quest for one period |
The app has no JavaScript build step and no frontend dependencies — it's Jinja templates, one stylesheet and one small script.
Media gallery
1 / 4Install Questly on Unraid in a few clicks.
Find Questly in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Requirements
Categories
Download Statistics
Related apps
Explore more like this
Explore allDetails
hansford909/questly:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8000]- Network
bridge- Shell
sh- Privileged
- false
Template configuration
Port for the Questly web interface.
- Target
- 8000
- Default
- 37000
- Value
- 37000
Where your MongoDB server is. Use the LAN IP unless both containers share a custom network. Add credentials if authentication is on: mongodb://user:pass@host:27017/?authSource=admin
- Target
- MONGO_URI
- Default
- mongodb://192.168.1.50:27017/
Signs login cookies. Set a long random value - generate one with: openssl rand -hex 32
- Target
- SECRET_KEY
Drives dates and the daily and weekly quest resets, e.g. Europe/London or America/New_York.
- Target
- TZ
- Default
- UTC
- Value
- UTC
Name of the database inside MongoDB. Only change this if you run more than one Questly.
- Target
- MONGO_DB
- Default
- questly
- Value
- questly






