A minimal Flask web server that connects to PostgreSQL and reads from a tasks table. The full producer/consumer flow:
- PostgreSQL runs in your UIS-managed Kubernetes cluster (deployed once during cluster setup, or via
uis deploy postgresql). dev-template-configurecreates a per-app database and user, applies the init SQL, and writesDATABASE_URLto.envfor local dev.- The Flask app reads
DATABASE_URLfrom.env, connects to PostgreSQL viahost.docker.internal:35432(the local port forward UIS exposes), and serves the seeded data.
A small but complete Flask application:
| Endpoint | Method | Returns |
|---|---|---|
/ |
GET | Plain-text greeting with the template name and current time |
/tasks |
GET | JSON list of rows from the tasks table (the seeded data, plus anything you've added) |
/health |
GET | {"status": "ok", "database": "connected"} if the DB is reachable, or a 503 if not |
The app requires DATABASE_URL and exits immediately with a clear error if it's missing — there's no fallback. This is intentional: the template demonstrates the producer/consumer pattern where credentials always come from dev-template-configure.
This template uses UIS to configure PostgreSQL. Verify the UIS provision-host container is running:
docker ps --filter name=uis-provision-host --format '{{.Status}}'You should see Up X minutes. If not, start UIS from the urbalurba-infrastructure repo. Inside DCT (devcontainer-toolbox v1.7.34 or later) you also have the uis shim, which routes uis ... commands to the provision-host automatically.
If PostgreSQL isn't deployed in your cluster, don't worry — dev-template-configure will detect it in step 4 and tell you exactly what to run (uis deploy postgresql).
dev-template python-basic-webserver-databaseDCT downloads the template from the registry and copies all files to your current project directory, including app/, manifests/, Dockerfile, requirements.txt, .gitignore, template-info.yaml, and config/init-database.sql.
Open template-info.yaml and find the params: section near the bottom. Set values for your app:
params:
app_name: "my-cool-app"
database_name: "my_cool_app_db"The defaults (my-app, my_app_db) work, but pick names that match your project — these become the PostgreSQL user and database names.
The full template-info.yaml declares the PostgreSQL dependency in the requires: section:
params:
app_name: "my-app"
database_name: "my_app_db"
requires:
- service: postgresql
config:
database: "{{ params.database_name }}"
init: "config/init-database.sql"DCT reads this file when you run dev-template-configure in the next step. The {{ params.database_name }} reference is substituted with the value you set above.
This file is the schema and seed data UIS applies to your database. The default creates a tasks table with 3 rows:
CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
INSERT INTO tasks (title, status) VALUES
('Set up the database connection', 'done'),
('Build something with Flask + PostgreSQL', 'pending'),
('Deploy to Kubernetes via ArgoCD', 'pending')
ON CONFLICT DO NOTHING;All statements are idempotent (IF NOT EXISTS, ON CONFLICT DO NOTHING) so re-running configure is safe. UIS applies the file with psql --set ON_ERROR_STOP=on, so any syntax error fails fast with a clear message.
For your real schema, edit this file to add your own tables, indexes, and seed data.
dev-template-configureWhat happens:
- DCT reads
template-info.yamland validates that theparams:are filled in - DCT calls
uis configure postgresql --app <app_name> --database <database_name> --init-file -via the bridge, piping in the substituted SQL - UIS creates the database and user, applies the init SQL, and writes connection details
- UIS also creates a Kubernetes Secret in your app's namespace so the deployed pod can read
DATABASE_URLlater (when yougit pushand ArgoCD deploys) - DCT writes
.envto your project root (gitignored) with the local connection string
If PostgreSQL isn't deployed in your cluster, this step fails with a clear error from UIS telling you to run uis deploy postgresql.
You should see something like:
📦 Configuring postgresql...
✅ postgresql — configured
→ .env: DATABASE_URL=postgresql://my_cool_app:Xa7mP9...@host.docker.internal:35432/my_cool_app_db (local)
→ K8s Secret: <repo-name>-db in namespace <repo-name> (cluster)
Inspect the seeded data without starting the app:
uis connect postgresql my_cool_app_dbInside psql:
SELECT * FROM tasks;
\qYou should see 3 rows. If they're there, the database is set up correctly and DATABASE_URL is in your .env.
DCT ships with uv for fast Python package management. Create a virtualenv, install dependencies, and run the app:
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
python app/app.pyOr one-liner (no manual activation):
uv venv
uv pip install -r requirements.txt
uv run python app/app.pyThe Flask debug server starts on port 3000.
VS Code tip (optional): if you see "Error refreshing packages" from VS Code's Python extension, add this to your workspace .vscode/settings.json:
{
"python-envs.alwaysUseUv": true
}The error happens because uv venv doesn't install pip into the venv (it doesn't need to), and VS Code's Python extension defaults to pip list for package enumeration. The setting tells it to use uv instead. If your project's .vscode/settings.json already exists with other keys, just add this one — don't replace the whole file.
VS Code's "Ports" tab in the bottom panel auto-forwards port 3000. Click the globe icon next to it to open these URLs:
http://localhost:3000/— Home pagehttp://localhost:3000/tasks— JSON list of seeded rowshttp://localhost:3000/health— DB connectivity check
If /tasks shows the 3 seeded rows, your producer/consumer chain is working end-to-end: Flask → DATABASE_URL → host.docker.internal → UIS port-forward → PostgreSQL pod in K8s.
After installation, your project contains:
├── app/
│ └── app.py # Flask app reading from PostgreSQL
├── config/
│ └── init-database.sql # Schema + seed data (applied by uis configure)
├── manifests/
│ ├── deployment.yaml # K8s Deployment + Service (uses Secret for DATABASE_URL)
│ └── kustomization.yaml # ArgoCD configuration
├── .github/
│ └── workflows/
│ └── urbalurba-build-and-push.yaml # CI/CD pipeline
├── .gitignore # Excludes .env*, .venv/, etc.
├── Dockerfile # Container build
├── requirements.txt # Flask, psycopg2-binary, python-dotenv
├── template-info.yaml # Template metadata (read by dev-template-configure)
└── README-python-basic-webserver-database.md # This file
- Edit
app/app.py— the main Flask application. Changes auto-reload in debug mode. - Edit
config/init-database.sqlto change the schema. Re-rundev-template-configureto apply the changes. - Edit
template-info.yamlto changeparams. Re-rundev-template-configureafterward (it's idempotent — safe to run repeatedly).
The standard workflow uses GitHub Actions + ArgoCD — no manual docker build or kubectl apply:
-
Push your code to GitHub:
git push
GitHub Actions builds and pushes the container image to GitHub Container Registry. The image is credential-free —
DATABASE_URLis injected at runtime from a Kubernetes Secret. -
Register the app with ArgoCD (one-time per project, from your host machine):
./uis argocd register <app-name> <github-repo-url>
This creates an ArgoCD Application that watches your repo and auto-deploys updates on every push.
-
Access the app at
http://<app-name>.localhost. ArgoCD applies the deployment manifest, K8s injectsDATABASE_URLfrom the Secret UIS created in step 4 above, and the pod connects to PostgreSQL via the cluster service DNS (postgresql.default.svc.cluster.local).
You don't need to create the Kubernetes Secret manually — dev-template-configure already created it in the right namespace. The deployment manifest references it via secretKeyRef.
This is the consumer side of the producer/consumer pattern. The producer side is:
- PostgreSQL Demo — a UIS stack template that deploys PostgreSQL standalone, useful for verifying your UIS setup. You don't need to install it for
python-basic-webserver-databaseto work —dev-template-configurehandles everything.
The GitHub Actions workflow (.github/workflows/urbalurba-build-and-push.yaml) automatically builds and pushes the Docker image to GitHub Container Registry when changes are pushed to the main branch. ArgoCD picks up the new image and deploys it.