Oct 23, 20267 min read

How to Add Postgres to a Django App

Moving a Django project from SQLite to PostgreSQL with Encore, dj-database-url or plain settings, plus psycopg, connection pooling, local databases, tests and production.

Django ships with SQLite configured, which is fine for a first run and rarely what a team wants in production. Moving to Postgres is mostly a settings change, and most of the time goes into the work around it, such as picking a driver, deciding how connection details reach settings.py, pooling connections, giving every developer and CI run a database and creating the production instance. This guide covers Encore, dj-database-url and hand-written settings, with code for each.

PostgreSQL was the database the most respondents had done extensive work in, at 55.6%, in the 2025 Stack Overflow Developer Survey, which also ranked it the most desired and most admired database.

What you need to decide:

  • How the connection details reach DATABASES.
  • Whether Django keeps one connection per request or uses a pool.
  • Where Postgres comes from for local development and tests.
  • Who creates and secures the production database.

Postgres options for Django at a glance

CapabilityEncoredj-database-urlHand-written settings
Connection detailsFrom the declared database through db.connection_stringParsed from DATABASE_URLSeparate environment variables
ORM and migrationsStandard Django ORM and manage.py migrateStandardStandard
Local Postgresencore run starts itYou run itYou run it
Production databaseProvisioned by Encore in your AWS or GCP accountYou provision itYou provision it

Encore: Postgres for your existing Django project

Encore

Encore combines the database your Django project uses with the Postgres server behind it, locally and in your cloud, while the ORM, admin and migrations stay standard Django. It is an infrastructure SDK you add to the project, so models and views do not change.

The project keeps its manage.py, config/ package and apps. Encore needs a few additions, taken from the Django example in the Encore SDK repository. The dependencies in pyproject.toml include encore-dev and psycopg[binary]>=3.2, the project pins Python 3.14 in .python-version and uses uv for dependency management:

[project] name = "django-hello-world" version = "0.1.0" requires-python = ">=3.14" dependencies = [ "django>=5.2,<6", "encore-dev", "psycopg[binary]>=3.2", ]

An encore.service.json in the project root names the service and exposes its HTTP routes:

{ "name": "django_hello_world", "lang": "python", "api": [ { "type": "http", "prefix": "/", "name": "django_hello_world", "expose": true } ] }

A small main.py adapter starts Django on the address Encore assigns. It disables Django's reloader because Encore restarts the service on changes:

"""Adapt Encore's listen address to Django's development server.""" import os from django.core.management import execute_from_command_line def main() -> None: os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") address = os.environ.get("ENCORE_LISTEN_ADDR", "127.0.0.1:8000") # Encore restarts the service on changes, so a second reloader is unnecessary. execute_from_command_line(["manage.py", "runserver", address, "--noreload"]) if __name__ == "__main__": main()

The database is declared in a module of its own. Django keeps managing the schema through its own migrations, so no migrations directory is passed:

from encore.storage import postgres # Django manages the schema through its own migrations. db = postgres.Database("django")

settings.py builds DATABASES from the connection string the SDK provides, parsed with psycopg so escaping and options survive. SECRET_KEY and DEBUG come from the environment rather than being written into the file:

import os from psycopg.conninfo import conninfo_to_dict from storage.resources import db SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true" # The SDK supplies the local runtime proxy URL, including routing credentials. # Parse with psycopg so escaping and connection options are preserved. connection = conninfo_to_dict(db.connection_string) DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": connection.pop("dbname"), "USER": connection.pop("user", ""), "PASSWORD": connection.pop("password", ""), "HOST": connection.pop("host", ""), "PORT": connection.pop("port", ""), "OPTIONS": connection, } }

Encore provisions the database but does not run Django migrations. Management commands run through encore exec so the SDK receives its runtime configuration, and the migrations are created and applied before the server starts:

~/app
$ encore exec -- uv run python manage.py makemigrations$ encore exec -- uv run python manage.py migrate$ encore run

encore run starts Postgres locally and runs the Django server against it, and the ORM, auth, admin and sessions all use that database. Commit the generated Django migrations and apply them in each environment before serving ORM endpoints. encore test supplies an isolated database, and the tests in the reference project run migrate against it in a pytest fixture. On deploy, Encore provisions the infrastructure in your AWS or GCP account, and if the project already has a production database you can import the RDS instance instead of creating a new one.

When an agent adds a model, it writes the migration and the view together and runs migrate against its own local database rather than a shared staging one.

Without Encore

Without Encore, the same project reads its connection details from the environment, as the next two sections show, and someone runs a Postgres server for each developer and CI job and creates the RDS instance, security group and secret for production. The Django side is identical, and only who runs the database changes.

Good to know

Django ORM queries reach the database through psycopg and the Encore runtime's database proxy, not through the SDK, so they do not show up in Encore traces. The runserver adapter and the example settings are meant for local development.

Try Encore →

dj-database-url

dj-database-url parses a single DATABASE_URL into the dictionary Django expects, which matches how most hosting platforms hand out connection details:

import dj_database_url DATABASES = { "default": dj_database_url.config( default="postgres://app:app@localhost:5432/app", conn_max_age=600, conn_health_checks=True, ) }

Good to know

conn_max_age keeps a connection open between requests in the same worker, which removes the connection setup cost from each request at the price of one idle connection per worker.

Hand-written settings with psycopg 3

Without a helper library, each value comes from its own environment variable. With psycopg 3 installed, Django can also keep a connection pool per process:

import os DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.environ["DB_NAME"], "USER": os.environ["DB_USER"], "PASSWORD": os.environ["DB_PASSWORD"], "HOST": os.environ.get("DB_HOST", "localhost"), "PORT": os.environ.get("DB_PORT", "5432"), "OPTIONS": {"pool": True}, } }

Good to know

Pools are per process, so the total is processes times pool size, and since Postgres allows typically 100 connections by default, a few Gunicorn workers on several instances get there faster than expected.

Moving existing data from SQLite

Data moves with Django's own serialization: python manage.py dumpdata --natural-foreign --exclude contenttypes > data.json against SQLite, then migrate and loaddata data.json against Postgres. Large tables are faster to copy with a dedicated tool, but for most projects that are still on SQLite the fixture route is enough.

Postgres in production

On AWS the database is usually RDS, with the password in Secrets Manager and a security group that admits only the application. Many concurrent workers can exhaust connections, which RDS Proxy pools in front of the instance. Backups deserve the same attention as the instance. In GitLab's postmortem of its January 2017 database outage, the team found the pg_dump backups missing from the S3 bucket they were meant to be uploaded to, and restored from a disk snapshot taken 6 hours before the outage. With Encore, those resources are created in your own account and cloud access sits with Encore's IAM role, not with the developers or agents writing the Django code.

How to choose

  • Encore if you want Postgres running locally and in your AWS or GCP account for the same Django project with no connection setup per environment.
  • dj-database-url if your platform hands you a DATABASE_URL.
  • Hand-written settings if you prefer every value explicit and want Django's psycopg 3 pool.

Python support in Encore is in beta, and you can sign up for access to try it with your Django project.

Frequently asked questions

How do I switch Django from SQLite to Postgres?

Install psycopg, set ENGINE to django.db.backends.postgresql in DATABASES with the host, port, name, user and password of your Postgres server, then run python manage.py migrate against the new database. Use dumpdata and loaddata if you need to carry existing rows across.

Which Postgres driver should Django use?

Django supports psycopg 3 and psycopg2. New projects should use psycopg 3, which also enables Django's built-in connection pool through the pool option.

How do I use the Django ORM with Encore?

Declare postgres.Database in a module, then build DATABASES in settings.py from db.connection_string with the standard django.db.backends.postgresql engine. Django owns the schema, so you run makemigrations and migrate through encore exec, since Encore does not run Django migrations for you. encore run starts Postgres locally.

How do I run Postgres locally for Django?

Install Postgres on your machine or run it in a container, then read the connection details from environment variables in settings.py. With Encore, encore run starts Postgres locally for the database declared in your code.

How do I deploy Django with Postgres to AWS?

Create an RDS instance, store the password in Secrets Manager, allow the service's security group to reach the database and pass the connection details to Django at runtime. With Encore, you connect your AWS account and Encore provisions the infrastructure in that account from the declarations in your code.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders