Web container for the Great Football Pool
  • Cython 74%
  • Python 17.4%
  • Jinja 3.4%
  • HTML 1.9%
  • CSS 1.5%
  • Other 1.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
johnsturgeon cb1e6a762e
All checks were successful
Python FastAPI Jinja Linting / build (push) Successful in 30s
Tests / pytest (push) Successful in 16s
Merge pull request '[skip review] Added titles to all pages' (#544) from add-proper-page-titles-for-each-new-page-in-v3-525 into main
Reviewed-on: #544
2026-09-16 20:21:43 +02:00
.forgejo Gate build and release on the version bump 2026-09-07 11:21:52 +02:00
alembic Make drop_team_data_source migration idempotent 2026-09-06 19:49:08 +02:00
app Added titles to all pages 2026-09-16 20:20:28 +02:00
config [skip review] Removed umami and also fixed a couple pycharm inspection issues 2026-09-13 17:34:28 +02:00
docker build-docker-image-and-host-on-this-forgejo-instance-370 (#383) 2026-08-14 20:44:46 +02:00
docs [skip review] Removed umami and also fixed a couple pycharm inspection issues 2026-09-13 17:34:28 +02:00
original_assets Added the award embed (#293) 2025-09-17 12:57:02 -07:00
scripts Checkpoint commit 2026-09-09 15:34:21 +02:00
tests Addressed review feedback 2026-09-11 20:46:48 +02:00
.dockerignore build-docker-image-and-host-on-this-forgejo-instance-370 (#383) 2026-08-14 20:44:46 +02:00
.gitignore Ignore the Tailwind toolchain 2026-09-09 21:07:19 +02:00
.pylintrc Normalize first-party imports to the app. namespace 2026-08-13 19:33:53 +02:00
alembic.ini 196 migrate from mongo to sqlmodel (#203) 2025-09-01 16:42:32 -07:00
CLAUDE.md Gate build and release on the version bump 2026-09-07 11:21:52 +02:00
compose.dev.yml Fixed 1Password and the op.env file to properly handle shared secrets (#361) 2026-08-12 20:34:12 +02:00
compose.prod.yml build-docker-image-and-host-on-this-forgejo-instance-370 (#383) 2026-08-14 20:44:46 +02:00
Dockerfile Add Tank01 provider and test harness ahead of the ESPN swap (#363) (#390) 2026-08-20 18:05:21 +02:00
LICENSE Tgfp Web copied over from great football pool 2022-10-01 08:50:50 -07:00
package-lock.json Checkpoint commit 2026-09-09 15:34:21 +02:00
package.json Checkpoint commit 2026-09-09 15:34:21 +02:00
pyproject.toml [skip review] Updated version for prod release 2026-09-12 17:57:00 +02:00
README.md build-docker-image-and-host-on-this-forgejo-instance-370 (#383) 2026-08-14 20:44:46 +02:00
uv.lock [skip review] Updated version for prod release 2026-09-12 17:57:00 +02:00

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 tgfp vault

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_URL in the tgfp/production vault 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.development by 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 tgfp instance, 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 DATABASE complains 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.sh to 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.sh to 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