The Django app behind www.pigscanfly.ca — the Pigs Can Fly Labs site and store.
Requires Python 3.13 (matching the Docker image; 3.10+ works).
python3.13 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
./manage.py migrate
./manage.py loaddata initial_products
./run_local.sh # runserver_plus with TLS via mkcertThe Dev configuration (sqlite, file-based email in sent_emails/) is the
default; set ENVIRONMENT=Prod (or DJANGO_CONFIGURATION=Prod) for the
production settings class.
Checks — one script shared by local dev, build.sh, and GitHub Actions
(.github/workflows/ci.yml):
./scripts/checks.sh| Variable | Used by | Notes |
|---|---|---|
SECRET_KEY |
Prod (required), Base (optional) | Base falls back to an insecure dev-only value. |
STRIPE_TEST_SECRET_KEY |
Dev / Base | Test-mode Stripe key. |
STRIPE_LIVE_SECRET_KEY |
Prod | Live Stripe key. |
DBHOST / DBNAME / DBUSER / DBPASSWORD |
Prod | Postgres connection; wired in deploy.yaml to the in-cluster DB. |
EMAIL_HOST / EMAIL_HOST_USER / EMAIL_HOST_PASSWORD |
Prod | SMTP. |
MAXMIND_LICENSE_KEY |
build.sh (image build) | Bundles the GeoLite2 country DB for region-specific buy links; optional. |
GEOIP_PATH |
all | Directory holding GeoLite2-Country.mmdb; defaults to <repo>/geoip (set to /opt/app/geoip in the image). |
Note: a Stripe test key and a mkcert dev key were committed to this repo's history in the past. Both should be treated as burned — rotate the Stripe test key in the dashboard; the settings now only read keys from the environment.
Bootstrap products (Holden's books) live in
main/fixtures/initial_products.yaml. Rules:
- Fixture rows use pks 100+ so they never collide with rows created directly in prod.
- The primary pod re-runs
loaddata initial_productson every deploy, so admin edits to those pks get overwritten — edit the YAML instead. external_product_id(Stripe) is generated lazily on first add-to-cart, so loading fixtures needs no Stripe access.
Books carry amazon_link and bookshop_link (shown to everyone) plus
amazon_in_link / flipkart_link, which are shown first to visitors whose
IP resolves to India via MaxMind GeoLite2. Country detection needs
GeoLite2-Country.mmdb in GEOIP_PATH — the Docker build downloads it when
MAXMIND_LICENSE_KEY is set (passed as a BuildKit secret so it stays out of
the image history). Without the database the site quietly serves the default
links only.
./build.sh is the whole pipeline: mypy → migration check → tests →
template validation → collectstatic (assets are copied in from a sibling
pcfweb-assets checkout) → multi-arch Docker build/push
(holdenk/pcfweb:<tag>) → kubectl apply.
The Kubernetes objects:
pg-bootstrap.yaml— the database: a CloudNativePGCluster(3 instances, 10Giencrypted-local-pathstorage, nightly backups to B2), plus manualBackupand nightlyScheduledBackup.deploy.yaml— the app:web-primary(1 replica; runsmigrate+loaddataon start),web(3 replicas),web-svc, and the ingress forwww.pigscanfly.ca.
The app reaches Postgres through the operator-created pcfweb-pg-rw
Service; DBHOST/DBNAME/DBUSER are set directly in deploy.yaml and
DBPASSWORD comes from the pcfweb-internal-pg-secret Secret.
- CloudNativePG operator installed cluster-wide.
- The
encrypted-local-pathStorageClass. - Secrets in the
pcfwebnamespace:pcfweb-superuser-pg-secret—kubernetes.io/basic-auth, usernamepostgres+ password.pcfweb-internal-pg-secret—kubernetes.io/basic-auth, usernamepigscanfly+ password (the app role).pg-backup—PG_ACCESS_KEY_ID/PG_ACCESS_SECRET_KEYfor thepcfweb-pg-backupB2 bucket (use a bucket dedicated to pcfweb).pcfweb-secret— the app env (SECRET_KEY, Stripe, email, …).
The site previously ran against an external MySQL. To carry data over:
- From a checkout of the last pre-Postgres revision (which still has the
MySQL settings) with access to the old DB:
./manage.py dumpdata --natural-foreign -e contenttypes -e auth.Permission -e sessions -e cal_sync_magic -o prod-dump.json(calendar sync no longer ships in this repo, so its rows can't be loaded here; drop-e cal_sync_magicif that old checkout doesn't have the calendar app installed.) - On this revision:
kubectl -n pcfweb port-forward svc/pcfweb-pg-rw 5432:5432, setDBHOST=127.0.0.1etc., then./manage.py migrateand./manage.py loaddata prod-dump.json. - Reset sequences (explicit-pk loads don't advance them):
./manage.py sqlsequencereset main auth | kubectl -n pcfweb exec -i pcfweb-pg-1 -- psql -U postgres pigscanfly