- Cython 74%
- Python 17.4%
- Jinja 3.4%
- HTML 1.9%
- CSS 1.5%
- Other 1.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo | ||
| alembic | ||
| app | ||
| config | ||
| docker | ||
| docs | ||
| original_assets | ||
| scripts | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| .pylintrc | ||
| alembic.ini | ||
| CLAUDE.md | ||
| compose.dev.yml | ||
| compose.prod.yml | ||
| Dockerfile | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
Great Football Pool
A FastAPI app for running an NFL pick'em pool.
Two workflows live in this README and they have almost nothing in common, so they are kept apart:
- Production deployment — pull a prebuilt image and run it. No checkout, no build, no Python on the host.
- Local development — run the app from a virtualenv against a containerised Postgres.
Production deployment
Production runs a prebuilt image from this Forgejo instance's registry, built
by .forgejo/workflows/build-image.yml on
every merge to main. The image contains the app, its dependencies, the
Alembic migrations, and the 1Password CLI.
Prerequisites
- Docker with Compose
- A 1Password service-account token, scoped read-only to the
tgfpvault
That is the whole list. The host needs no Python, no op installation, no
clone of this repository, and no config/ directory.
Deploy
Put compose.prod.yml on the host along with a .env
holding the token:
echo 'OP_SERVICE_ACCOUNT_TOKEN=<your token>' > .env
Then:
docker compose -f compose.prod.yml pull && docker compose -f compose.prod.yml up -d
Updating is the same two commands. Under Komodo, the token is a Komodo secret and this is the stack's deploy action.
No docker login is needed — the package is public, so the pull is anonymous
(verified 2026-08-14). The image is built single-arch for linux/amd64.
How secrets get in
Nothing on the host holds application secrets, and no resolved .env is ever
written. config/op.env — a template of op:// references
with no values — is baked into the image, and
docker/entrypoint.sh resolves it through op run at
container start, passing the values straight into the app's environment.
Adding a config key is therefore a one-line commit to op.env plus the entry
in 1Password. Nothing is duplicated into Komodo, and rotating a secret takes
effect on the next container restart.
ENVIRONMENT (set to production in the compose file) selects which
1Password sub-path is read, since most keys are addressed as
op://tgfp/${APP_ENV}/KEY.
Note:
DATABASE_URLin thetgfp/productionvault entry must point at the compose service —postgresql+psycopg://tgfp:tgfp@postgres:5432/tgfp. The database is not published to the host.
Migrations
alembic upgrade head runs automatically at container start, from the
migration scripts baked into the image. There is no separate migration step.
Set TGFP_DEBUG_MIGRATIONS=1 to log the Alembic revision before and after the
upgrade, and to trace the entrypoint.
Versioning
Bump once per PR before merging:
uv version --bump minor # or patch / major
This edits version in pyproject.toml and refreshes uv.lock; commit both.
Each build is pushed as latest, the version, and the short commit SHA, so an
unbumped version means the running app reports the previous release's number
and the build overwrites that release's tag.
Local development
The app runs from a local virtualenv (port 6801, hot reload); only Postgres runs in Docker (port 6432). Docker is not part of the edit loop.
Prerequisites
- Docker with Compose
- uv
1. Clone the repository
git clone ssh://git@192.168.192.129/johnsturgeon/tgfp-web.git
cd tgfp-web
2. Create the development config
Development still uses a generated .env file, because the app runs outside a
container here and so never passes through the image's entrypoint.
scripts/create_dev_env.sh
This runs op inject over config/op.env to produce
config/.env.development. It needs the op CLI installed locally and
OP_SERVICE_ACCOUNT_TOKEN exported — see
the op docs, and
op vault list to check it works.
Not using 1Password? See docs/sample.env and write
config/.env.developmentby hand.
(Optional) Restore the database from another instance
If you are deploying a new instance and want to seed it with data from an existing production
tgfpinstance, follow these steps. This produces a logical dump on the source and restores it into the freshly bootstrapped database on the destination.
1. Dump the database on the source instance
Run from the source host (the pg_dump runs inside the running postgres
container):
docker exec tgfp-postgres-prod pg_dump -Fc -U tgfp -d tgfp > tgfp.dump
2. Transfer tgfp.dump to the destination host
Copy the dump file to the destination host by whatever means is convenient
(e.g. scp over your private network). Place it somewhere accessible from
the deployment directory.
3. Bootstrap the stack once, then bring up only postgres
The first compose up lets the postgres:16 image initialize an empty
tgfp database (and the web container may run Alembic migrations against it).
Then shut down and bring up only postgres so nothing connects during the
restore:
docker compose -f compose.prod.yml up -d # bootstrap (creates empty DB)
docker compose -f compose.prod.yml down
docker compose -f compose.prod.yml up -d postgres
Wait for it to report healthy:
docker compose -f compose.prod.yml ps postgres
4. Drop and recreate the database for a clean restore
Because bootstrapping created a schema (Alembic migrations), drop and recreate the database so the restore lands on a blank slate:
docker exec tgfp-postgres-prod psql -U tgfp -d postgres -c 'DROP DATABASE tgfp;'
docker exec tgfp-postgres-prod psql -U tgfp -d postgres -c 'CREATE DATABASE tgfp OWNER tgfp;'
If
DROP DATABASEcomplains about active connections, terminate them first:docker exec tgfp-postgres-prod psql -U tgfp -d postgres \ -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='tgfp';"
5. Restore the dump
docker exec -i tgfp-postgres-prod pg_restore -U tgfp -d tgfp < tgfp.dump
6. Verify, then bring up the full stack
docker exec tgfp-postgres-prod psql -U tgfp -d tgfp -c '\dt'
If your tables are present, continue with the normal startup below.
3. Start the database
Development runs only Postgres in Docker, on port 6432:
docker compose -f compose.dev.yml up -d postgres
The app itself runs from your virtualenv — see the PyCharm steps below — and is reachable at http://localhost:6801/.
(Production is the other file, and a different workflow entirely:
docker compose -f compose.prod.yml up -d, serving port 8000. See
Production deployment.)
Prep environment for development (pycharm on a Mac as an example)
NOTE: Do the following BEFORE starting pycharm
- check out the code
- cd into dir
- run
scripts/create_local_python.shto make sure your local python environment is ready for development - fire up pycharm
- make sure your interpreter is set to the local .venv
- Delete all previous docker volumes / images / containers
- install (or confirm) psql tools on your Mac:
brew install libpq
brew link --force libpq
- run
scripts/create_dev_env.shto create the development env - fire up the dev container for DB
docker compose -f compose.dev.yml up -d --build postgres - fire up the tgfp-web site locally NOTE: Don't worry about the website not firing up yet, we'll get to that
Initialize the Postgresql DB
- read in the config
set -a
source config/.env.development
set +a