Deploy a full-stack Python app with Postgres, Redis, and workers in 5 minutes
Deploy a full-stack Python app with Postgres, Redis, and workers in 5 minutes
By Platform Super Admin • • Python · DevOps · Databases
# Deploy a full-stack Python app with Postgres, Redis, and workers in 5 minutes
**Published:** May 15, 2026
**Category:** Python · DevOps · Databases
**Reading time:** 8 minutes
**Author:** NEXUS AI Team
---
Most Python apps are not just one web process.
Even a small production app usually needs:
- A web API, often FastAPI, Flask, or Django.
- PostgreSQL for durable application data.
- Redis for queues, caching, rate limits, or sessions.
- A background worker for slow jobs like emails, scraping, AI calls, imports, PDF generation, or webhook processing.
The painful part is usually not the code. It is wiring the app container, database, cache, worker process, service networking, ports, health checks, environment variables, volumes, logs, and scaling rules.
With the NEXUS AI CLI, you can deploy the whole stack from a Git repository with one command.
This post walks through a practical Python deployment using:
- Python web app
- PostgreSQL service
- Redis service
- RQ background worker
- Internal service networking
- Scaling
- Logs
- Backup
---
## What we are deploying
The target architecture looks like this:
```text
Internet
|
v
NEXUS AI
|
v
Python web container
|-- connects to postgresql:5432
|-- connects to redis:6379
|
v
Worker container
|-- runs rq worker default
|-- connects to the same Postgres and Redis services
PostgreSQL container
Redis container
```
The important detail: the app and worker do not connect to `localhost`.
Inside the deployment network, the database hostnames are:
```text
postgresql:5432
redis:6379
```
NEXUS AI injects the environment variables your app needs, including `DATABASE_URL` and `REDIS_URL`.
---
## Prerequisites
You need:
- A NEXUS AI account.
- The NEXUS CLI installed.
- A Git repository containing your Python app.
- A Python app that listens on a known port, usually `8000`.
Install the CLI:
```bash
curl -fsSL https://nexusai.run/install.sh | bash
```
On macOS:
```bash
curl -fsSL https://nexusai.run/install-mac.sh | bash
```
Authenticate:
```bash
nexus auth login
nexus auth status
```
---
## Example Python app structure
Your repository can be simple:
```text
my-python-app/
app.py
worker.py
requirements.txt
```
Example `requirements.txt`:
```text
fastapi
uvicorn[standard]
psycopg[binary]
redis
rq
```
Example `app.py`:
```python
import os
from fastapi import FastAPI
from redis import Redis
from rq import Queue
app = FastAPI()
redis_conn = Redis.from_url(os.environ["REDIS_URL"])
queue = Queue("default", connection=redis_conn)
def run_task(name: str):
return f"processed {name}"
@app.get("/healthz")
def healthz():
return {"ok": True}
@app.post("/jobs/{name}")
def enqueue_job(name: str):
job = queue.enqueue(run_task, name)
return {"job_id": job.id, "status": "queued"}
```
Example `worker.py`:
```python
import os
from redis import Redis
from rq import Worker, Queue
redis_conn = Redis.from_url(os.environ["REDIS_URL"])
worker = Worker([Queue("default", connection=redis_conn)], connection=redis_conn)
worker.work()
```
For a real app, your task functions usually live in a separate module so both the web process and worker can import them cleanly.
---
## Deploy the full stack
Run one command:
```bash
nexus deploy source \
--repo https://github.com/your-org/my-python-app.git \
--name my-python-app \
--provider docker \
--framework python \
--branch main \
--services postgresql,redis \
--start-command "uvicorn app:app --host 0.0.0.0 --port 8000" \
--worker-command "python worker.py" \
--worker-name jobs-worker \
--wait
```
That command does the operational work:
1. Pulls your Git repository.
2. Detects/builds the Python app image.
3. Creates the web app container.
4. Creates a PostgreSQL service.
5. Creates a Redis service.
6. Creates a worker container from the same app image.
7. Attaches the app, worker, Postgres, and Redis to the same service network.
8. Injects database and Redis environment variables.
9. Exposes only the web app publicly.
10. Keeps the worker private.
The worker does not need a public port. It runs inside the same deployment network and talks to Redis/Postgres by internal hostname.
---
## Environment variables your app receives
For PostgreSQL:
```text
POSTGRES_HOST=postgresql
POSTGRES_PORT=5432
POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=<generated>
DATABASE_URL=postgresql://appuser:<generated>@postgresql:5432/appdb
```
For Redis:
```text
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_URL=redis://redis:6379/0
```
Use these values inside the app and worker.
Do not hardcode:
```text
localhost
127.0.0.1
host.docker.internal
```
Those point to the wrong place from inside a container. Use `postgresql` and `redis`.
---
## Check deployment status
```bash
nexus deploy status my-python-app
```
You should see the deployment move through states such as:
```text
PENDING
BUILDING
RUNNING
```
When it is running, open the deployment URL and check the health route:
```bash
curl https://your-app-url/healthz
```
Expected response:
```json
{"ok":true}
```
---
## Watch app and worker logs
Tail logs:
```bash
nexus deploy logs my-python-app --follow
```
If your worker is connected correctly, you should see it listening on the queue:
```text
Listening on default...
```
If the worker cannot resolve Redis, you will see errors like:
```text
Error -2 connecting to redis:6379. Name or service not known.
```
That usually means the worker was not attached to the deployment network or the app is using the wrong Redis hostname.
Use `REDIS_URL=redis://redis:6379/0`, not `localhost`.
---
## Test the queue
Send a job to the web app:
```bash
curl -X POST https://your-app-url/jobs/demo
```
Expected response:
```json
{
"job_id": "...",
"status": "queued"
}
```
Then check logs:
```bash
nexus deploy logs my-python-app --follow
```
You should see the worker pick up the job.
---
## Scale the web app
Scale the web containers to two replicas:
```bash
nexus deploy scale my-python-app 2
```
Scale to three:
```bash
nexus deploy scale my-python-app 3
```
Scale back down:
```bash
nexus deploy scale my-python-app 1
```
Scaling changes the web app replicas. PostgreSQL and Redis are not duplicated. They remain the shared backing services for the deployment.
This matters because a scaled Python app should be stateless at the web layer:
- Store durable data in Postgres.
- Store queue/cache/session data in Redis.
- Store uploaded files in a bucket or attached volume.
- Do not rely on local container files unless you intentionally attached persistent storage.
---
## Add production environment variables
Pass runtime variables directly:
```bash
nexus deploy source \
--repo https://github.com/your-org/my-python-app.git \
--name my-python-app \
--provider docker \
--framework python \
--services postgresql,redis \
--env APP_ENV=production \
--env QUEUE_NAME=default \
--worker-command "python worker.py" \
--wait
```
Or load them from a file:
```bash
nexus deploy source \
--repo https://github.com/your-org/my-python-app.git \
--name my-python-app \
--provider docker \
--framework python \
--services postgresql,redis \
--env-file .env.production \
--worker-command "python worker.py" \
--wait
```
For sensitive values, use the secrets vault instead of committing `.env` files.
```bash
nexus secret create OPENAI_API_KEY
```
Then reference secrets during deployment or from the dashboard, depending on your workflow.
---
## Back up Postgres
List database services:
```bash
nexus db services my-python-app
```
Create a backup:
```bash
nexus db backup <postgres-service-id>
```
List backups:
```bash
nexus db backups <postgres-service-id>
```
Download a backup:
```bash
nexus db backup-download <postgres-service-id> <backup-id> --out ./postgres.dump
```
Restore into the same service:
```bash
nexus db restore <postgres-service-id> <backup-id> --yes
```
Restore into another deployment's Postgres service in the same organization:
```bash
nexus db restore-to <target-postgres-service-id> <backup-id> --yes
```
Before restoring production data, pause write-heavy workers or scale them down if your workflow supports it. A restore can replace database state.
---
## Common fixes
### The app cannot connect to Postgres
Check that the app is using:
```text
DATABASE_URL=postgresql://appuser:<password>@postgresql:5432/appdb
```
The hostname must be `postgresql`.
### The worker cannot connect to Redis
Check that the worker is using:
```text
REDIS_URL=redis://redis:6379/0
```
The hostname must be `redis`.
### The worker starts, then exits
Make sure the worker command is a long-running process:
```bash
--worker-command "python worker.py"
```
or:
```bash
--worker-command "rq worker default"
```
Do not use a one-shot command unless you intentionally want a short task.
### The app deploys, but health checks fail
Confirm the app listens on `0.0.0.0`, not `127.0.0.1`:
```bash
uvicorn app:app --host 0.0.0.0 --port 8000
```
Also make sure the app exposes a health endpoint such as:
```text
/healthz
```
### Scaling up works, but file uploads disappear
Local container files are ephemeral. Use a NEXUS AI bucket for user uploads or a persistent volume for filesystem-backed data.
For object storage:
```bash
nexus bucket create user-uploads --display-name "User uploads"
nexus bucket attach <bucket-id> <deployment-id>
nexus deploy redeploy <deployment-id> --wait
```
---
## The 5-minute path
If your app already has a working Python web process and worker command, the full deployment is one command:
```bash
nexus deploy source \
--repo https://github.com/your-org/my-python-app.git \
--name my-python-app \
--provider docker \
--framework python \
--services postgresql,redis \
--start-command "uvicorn app:app --host 0.0.0.0 --port 8000" \
--worker-command "python worker.py" \
--worker-name jobs-worker \
--wait
```
Then verify:
```bash
nexus deploy status my-python-app
nexus deploy logs my-python-app --follow
curl https://your-app-url/healthz
```
That is the core workflow: deploy the web app, provision Postgres and Redis, attach the worker to the same network, and manage the whole stack from the CLI.
---
## FAQ
**Can I use Celery instead of RQ?**
Yes. Use a Celery worker command:
```bash
--worker-command "celery -A app.celery worker --loglevel=info"
```
**Does the worker get the same environment variables as the app?**
Yes. The worker uses the same built image and receives the same deployment environment variables, including database and Redis connection values.
**Does scaling duplicate Postgres or Redis?**
No. Scaling changes app replicas. Database and cache services remain shared deployment resources.
**Should the worker expose a port?**
No. Workers should stay private. They consume jobs from Redis and do not need public ingress.
**Can I deploy Django with this pattern?**
Yes. Use a Django web command such as `gunicorn config.wsgi:application --bind 0.0.0.0:8000` and a worker command such as `celery -A config worker --loglevel=info`.
**Can I use this with private GitHub repositories?**
Yes. Store your repository token as a secret and deploy with `--repo-secret`.
```bash
nexus deploy source \
--repo https://github.com/your-org/private-python-app.git \
--repo-secret GITHUB_TOKEN \
--name private-python-app \
--provider docker \
--framework python \
--services postgresql,redis \
--worker-command "python worker.py" \
--wait
```