DEV Community

Cover image for Deploy a full-stack Python app with Postgres, Redis, and workers in 5 minutes
Saif Ali
Saif Ali

Posted on • Originally published at nexusai.run on

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

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:

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
Enter fullscreen mode Exit fullscreen mode

The important detail: the app and worker do not connect to localhost.

Inside the deployment network, the database hostnames are:

postgresql:5432
redis:6379
Enter fullscreen mode Exit fullscreen mode

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:

curl -fsSL https://nexusai.run/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

On macOS:

curl -fsSL https://nexusai.run/install-mac.sh | bash
Enter fullscreen mode Exit fullscreen mode

Authenticate:

nexus auth login
nexus auth status
Enter fullscreen mode Exit fullscreen mode

Example Python app structure

Your repository can be simple:

my-python-app/
  app.py
  worker.py
  requirements.txt
Enter fullscreen mode Exit fullscreen mode

Example requirements.txt:

fastapi
uvicorn[standard]
psycopg[binary]
redis
rq
Enter fullscreen mode Exit fullscreen mode

Example app.py:

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"}
Enter fullscreen mode Exit fullscreen mode

Example worker.py:

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()
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

POSTGRES_HOST=postgresql
POSTGRES_PORT=5432
POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=<generated>
DATABASE_URL=postgresql://appuser:<generated>@postgresql:5432/appdb
Enter fullscreen mode Exit fullscreen mode

For Redis:

REDIS_HOST=redis
REDIS_PORT=6379
REDIS_URL=redis://redis:6379/0
Enter fullscreen mode Exit fullscreen mode

Use these values inside the app and worker.

Do not hardcode:

localhost
127.0.0.1
host.docker.internal
Enter fullscreen mode Exit fullscreen mode

Those point to the wrong place from inside a container. Use postgresql and redis.

Check deployment status

nexus deploy status my-python-app
Enter fullscreen mode Exit fullscreen mode

You should see the deployment move through states such as:

PENDING
BUILDING
RUNNING
Enter fullscreen mode Exit fullscreen mode

When it is running, open the deployment URL and check the health route:

curl https://your-app-url/healthz
Enter fullscreen mode Exit fullscreen mode

Expected response:

{"ok":true}
Enter fullscreen mode Exit fullscreen mode

Watch app and worker logs

Tail logs:

nexus deploy logs my-python-app --follow
Enter fullscreen mode Exit fullscreen mode

If your worker is connected correctly, you should see it listening on the queue:

Listening on default...
Enter fullscreen mode Exit fullscreen mode

If the worker cannot resolve Redis, you will see errors like:

Error -2 connecting to redis:6379. Name or service not known.
Enter fullscreen mode Exit fullscreen mode

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:

curl -X POST https://your-app-url/jobs/demo
Enter fullscreen mode Exit fullscreen mode

Expected response:

{
  "job_id": "...",
  "status": "queued"
}
Enter fullscreen mode Exit fullscreen mode

Then check logs:

nexus deploy logs my-python-app --follow
Enter fullscreen mode Exit fullscreen mode

You should see the worker pick up the job.


Scale the web app

Scale the web containers to two replicas:

nexus deploy scale my-python-app 2
Enter fullscreen mode Exit fullscreen mode

Scale to three:

nexus deploy scale my-python-app 3
Enter fullscreen mode Exit fullscreen mode

Scale back down:

nexus deploy scale my-python-app 1
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

Or load them from a file:

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
Enter fullscreen mode Exit fullscreen mode

For sensitive values, use the secrets vault instead of committing .env files.

nexus secret create OPENAI_API_KEY
Enter fullscreen mode Exit fullscreen mode

Then reference secrets during deployment or from the dashboard, depending on your workflow.


Back up Postgres

List database services:

nexus db services my-python-app
Enter fullscreen mode Exit fullscreen mode

Create a backup:

nexus db backup <postgres-service-id>
Enter fullscreen mode Exit fullscreen mode

List backups:

nexus db backups <postgres-service-id>
Enter fullscreen mode Exit fullscreen mode

Download a backup:

nexus db backup-download <postgres-service-id> <backup-id> --out ./postgres.dump
Enter fullscreen mode Exit fullscreen mode

Restore into the same service:

nexus db restore <postgres-service-id> <backup-id> --yes
Enter fullscreen mode Exit fullscreen mode

Restore into another deployment's Postgres service in the same organization:

nexus db restore-to <target-postgres-service-id> <backup-id> --yes
Enter fullscreen mode Exit fullscreen mode

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:

DATABASE_URL=postgresql://appuser:<password>@postgresql:5432/appdb
Enter fullscreen mode Exit fullscreen mode

The hostname must be postgresql.

The worker cannot connect to Redis

Check that the worker is using:

REDIS_URL=redis://redis:6379/0
Enter fullscreen mode Exit fullscreen mode

The hostname must be redis.

The worker starts, then exits

Make sure the worker command is a long-running process:

--worker-command "python worker.py"
Enter fullscreen mode Exit fullscreen mode

or:

--worker-command "rq worker default"
Enter fullscreen mode Exit fullscreen mode

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:

uvicorn app:app --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode

Also make sure the app exposes a health endpoint such as:

/healthz
Enter fullscreen mode Exit fullscreen mode

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:

nexus bucket create user-uploads --display-name "User uploads"
nexus bucket attach <bucket-id> <deployment-id>
nexus deploy redeploy <deployment-id> --wait
Enter fullscreen mode Exit fullscreen mode

The 5-minute path

If your app already has a working Python web process and worker command, the full deployment is one command:

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
Enter fullscreen mode Exit fullscreen mode

Then verify:

nexus deploy status my-python-app
nexus deploy logs my-python-app --follow
curl https://your-app-url/healthz
Enter fullscreen mode Exit fullscreen mode

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:

--worker-command "celery -A app.celery worker --loglevel=info"
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)