Migrating production infrastructure? Get up to $10K in migration credits.

Apply now

Deploy a Django app with Postgres and Render Workflows using Claude Code

This tutorial shows how to use Claude Code's Render plugin to take an existing Django repo to a live deploy with Render Postgres and a Render Workflows task. Render's official skills let agents deploy, debug, and monitor your apps. Along the way, the agent tends to hit a predictable set of Django production problems. For each one, this tutorial gives you the prompt to send Claude Code and the fix it should produce.

The example app uses no Celery and no message broker. All background and long-running work goes through Render Workflows.

Here's the path:

  1. Install the plugin.
  2. Prompt the deploy.
  3. Fix the Django pitfalls.
  4. Add a Workflows task.
  5. Validate the configuration.
  6. Deploy.
  7. Check the logs.

Prerequisites: an existing Django repo and a working Claude Code setup.

Install the Render plugin in Claude Code

The official Render plugin for Claude Code bundles Render's skills and configures the Render MCP server (Model Context Protocol server) with OAuth automatically. The MCP server exposes platform actions to the agent.

To install, run these two commands in Claude Code:

The first time Claude uses a Render MCP tool, you approve a browser prompt to connect with OAuth, and you don't need a Render API key. For the full setup, see Render's Claude Code agent page. For more on the bundled skills, see the agent skills and plugins docs.

Prompt Claude Code to deploy

Give the agent a specific prompt that names the database and asks for a Blueprint file. Ask it to prepare the changes without deploying, so you can validate them first:

You can include extra details such as environment variables or a branch, and you can fix anything you forget with a follow-up prompt.

The agent writes render.yaml as part of this step. The first draft usually looks something like the simplified skeleton below. Comments mark the lines the next section corrects.

For the full field reference, see the Blueprint spec. Expect to make Django-specific corrections before this file is ready.

Django pitfalls the agent hits

Each pitfall below follows the same pattern: the symptom, the prompt to give Claude Code, and the fix it should produce. For Gunicorn tuning and overall production topology, see Render's Django deploy guide.

Gunicorn and PORT binding

Symptom: The deploy times out or reports no open ports, because Gunicorn isn't bound to 0.0.0.0 on the port Render expects.

Render expects your app to bind to 0.0.0.0, optionally using the PORT environment variable to set a custom port (the default port is 10000). Render forwards inbound traffic to only one HTTP port per web service.

The fix looks like this:

Static files with collectstatic and WhiteNoise

Symptom: Pages load without CSS, and the Django admin is unstyled.

Django designed its built-in static file serving for development with DEBUG on, so you need another approach in production. WhiteNoise serves collected static files from the web process, which means collectstatic has to run during the build.

A simplified version of the settings change looks like this:

The build script installs dependencies and collects static files:

ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS

Symptom: Requests fail with a DisallowedHost error once DEBUG is off. Admin logins and form posts fail CSRF verification.

Follow Render's Django guide and move secrets and debug settings out of the code first: read SECRET_KEY from an environment variable instead of storing it in source, and turn DEBUG off on Render by checking for the RENDER environment variable. A web service's hostname is its onrender.com hostname, such as myapp.onrender.com.

The fix should look something like this:

In render.yaml, let Render generate the key. With generateValue: true, Render sets a randomized, base64-encoded, 256-bit value:

When you add a custom domain later, add it to both host settings. Otherwise the same errors return on the new hostname.

DATABASE_URL wiring from Postgres

Symptom: The app silently uses SQLite or a localhost default, or it fails to connect to the database at startup.

Every Render Postgres database has an internal URL for services in the same region and an external URL for everything else. Use the internal URL wherever possible, because it keeps traffic on your private network.

The render.yaml side of the fix:

The settings side of the fix fails at startup instead of quietly connecting to the wrong database:

Migrations as a pre-deploy command

Symptom: Tables are missing after the first deploy. Alternatively, the agent puts migrate in build.sh or in front of Gunicorn in the start command.

When you set a pre-deploy command, Render runs it just before each deploy, which makes it a good fit for database migrations.

The fix:

If the pre-deploy command fails or runs longer than 30 minutes, the deploy fails and your service keeps running its most recent successful deploy. For details, see Deploying on Render.

Run background work as a Render Workflows task

Suppose the example app has one long-running job: generating a monthly report. This example doesn't use Celery, a broker or a worker service. Define the job as a Render Workflows task instead, so all async work in this app goes through Workflows. A workflow service is a collection of tasks you define as code and register with Render. You write those tasks as Python functions using the Render SDK, in a Git repo that the workflow service pulls from. Each run executes in its own instance, which Render spins up on demand.

Name the skill explicitly in your prompt, and ask for every piece you need:

The agent should add a workflow service like this under services: in render.yaml. A workflow in a Blueprint requires a region. Set it to the same region as mysite-db, because the internal connection string only works for services in the same region. If you don't set a region on the database, Render uses oregon. A workflow also requires repo. Unlike web services, the validator rejects a workflow missing repo ("repo is required for git-based services"), even when you validate from inside that repository, according to the render-workflows skill. branch stays optional.

Don't set plan, previews, or preDeployCommand on a workflow in a Blueprint. Workflows set compute per task in code, not on the service.

The task module

This simplified example follows the Python SDK pattern from the Render Workflows docs:

The Django trigger

Install the Render SDK (pip install render) and add render to requirements.txt. Set RENDER_API_KEY in the Django service's environment, because the SDK client detects and uses that variable automatically. The Render SDK calls the Render API under the hood to trigger task runs.

A trigger helper keeps SDK calls in one place and handles failures. start_task starts the run and returns without waiting for it to finish (see Triggering Task Runs).

A management command calls the helper, and a view can call it the same way:

Add the key and slug to the web service in render.yaml. sync: false keeps the API key out of the file:

By default, task runs time out after 2 hours, and you can raise the limit to up to 24 hours per task. See Limits and Pricing for Render Workflows.

Design limits

  • Scheduling: Workflows has no native scheduling. Each run starts when your code calls the SDK or API. To run the report on a schedule, ask the agent to add a Render cron job that runs python manage.py run_monthly_report with the same RENDER_API_KEY and REPORT_TASK_SLUG as the web service. Also set DATABASE_URL on the cron job with fromDatabase and property: connectionString, because settings.py raises ImproperlyConfigured at startup when DATABASE_URL is missing. Each cron run can last up to 12 hours.
  • No incoming connections: Task runs can't receive incoming connections. Pass everything a task needs as input arguments, and write results to Postgres instead of exposing an endpoint from the task.
  • Preview environments: Preview environments don't support Render Workflows. If you enable previews for this Blueprint, Render skips creating a preview copy of mysite-workflows. See Preview Environments.

Validate the generated configuration

Before you deploy, review the agent's changes against this checklist:

Check
What to look for
Bind address
--bind 0.0.0.0:$PORT in startCommand
Static files
WhiteNoise after SecurityMiddleware, STATIC_ROOT set, collectstatic in build.sh
Hosts and CSRF
RENDER_EXTERNAL_HOSTNAME in ALLOWED_HOSTS and as an https:// origin in CSRF_TRUSTED_ORIGINS
Secrets and debug
SECRET_KEY read from the environment (generateValue: true in render.yaml), and DEBUG off on Render
Database
DATABASE_URL uses fromDatabase with connectionString, parsed by dj-database-url, no local fallback
Migrations
preDeployCommand: python manage.py migrate with a paid plan, or migrate in build.sh on a Free compute plan, and never in startCommand
Workflow service
type: workflow in render.yaml with region, repo, startCommand: python workflows/tasks.py, and DATABASE_URL from fromDatabase
Workflow
Render SDK (render, 1.x) in requirements.txt, task takes ctx: TaskContext first
Schedule
If you added the cron job: cron job runs the trigger command with RENDER_API_KEY, REPORT_TASK_SLUG, and DATABASE_URL

You can validate a Blueprint file with Render CLI v2.7.0 or later. The command exits with a non-zero status if the file fails validation:

Your IDE can also validate render.yaml live through SchemaStore.org. Then ask the agent to cross-check its own work:

Deploy through the agent

When validation passes, commit your changes and prompt the deploy:

If you added the scheduling cron job, it deploys with them. The agent determines the build configuration and deploys your services.

Verify the deploy with logs

Ask the agent to read the logs and trigger one test run. Django settings still require DATABASE_URL at startup, so set it for the local run too. From your machine, use the database's external URL:

A successful deploy shows these signs:

  • The pre-deploy migrate command completed.
  • Gunicorn logs that it's listening at 0.0.0.0 on your port.
  • No DisallowedHost or CSRF errors appear.
  • A triggered generate_monthly_report run appears for the workflow service.

If something's off, map the symptom back to its fix:

Log symptom
Go back to
Port scan timeout, no open ports
Gunicorn and PORT binding
404s on /static/ paths, unstyled admin
Static files with collectstatic and WhiteNoise
DisallowedHost, CSRF verification failed
ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS
ImproperlyConfigured: DATABASE_URL isn't set
DATABASE_URL wiring from Postgres
relation does not exist, pre-deploy timeout
Migrations as a pre-deploy command
Task run didn't start, authentication errors from the SDK
The Django trigger
Task never registers in the workflow service
The task module

Where to go from here

Frequently asked questions