Setting Up PostgreSQL for Django on Ubuntu (Production)
Set up PostgreSQL for a Django app in production on Ubuntu: install the server, create a database and least-privilege user, and wire it into Django with a .env.
Long Nguyen
Fullstack Developer · AI Engineer · Researcher
Why PostgreSQL for Production
Django ships with SQLite, which is perfect for local development and wrong for production. A real deployment wants a proper database server — concurrent writes, real user management, and durability under load. This guide sets up PostgreSQL for Django on Ubuntu the production way: installing the server, creating a dedicated least-privilege database user, and connecting Django to it.
The theme throughout is least privilege: the app gets its own database and its own user with exactly the rights it needs, and nothing more. That single habit prevents a whole class of problems later.
Installing PostgreSQL
Install the server and its extras from Ubuntu's repositories:
sudo apt update
sudo apt install postgresql postgresql-contrib -y
PostgreSQL starts automatically and creates a system user called postgres. You administer the database by switching to that user, which is how the first connection is authenticated.
Creating the Database and a Least-Privilege User
Open the PostgreSQL shell as the postgres superuser:
sudo -u postgres psql
Then create a dedicated user and a database owned by that user. Give the user a strong password:
CREATE USER aiintervieweruser WITH PASSWORD 'your-strong-password';
CREATE DATABASE aiinterviewer OWNER aiintervieweruser;
\c aiinterviewer
Now lock down the public schema so only your app's user can use it — not every role on the server. This is the least-privilege step most quick tutorials skip:
ALTER SCHEMA public OWNER TO aiintervieweruser;
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE, CREATE ON SCHEMA public TO aiintervieweruser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO aiintervieweruser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO aiintervieweruser;
The REVOKE ALL ... FROM PUBLIC line is the important one: by default PostgreSQL lets any role create objects in the public schema, and revoking that closes a quiet gap. The ALTER DEFAULT PRIVILEGES lines make sure the user automatically owns the tables and sequences Django's migrations will create.
Tuning the Role for Django
A few role settings make PostgreSQL behave the way Django expects — the same recommendations Django's own docs give:
ALTER ROLE aiintervieweruser SET client_encoding TO 'utf8';
ALTER ROLE aiintervieweruser SET default_transaction_isolation TO 'read committed';
ALTER ROLE aiintervieweruser SET timezone TO 'UTC';
ALTER ROLE aiintervieweruser SET search_path = public;
\q
These set the connection encoding to UTF-8, use the "read committed" isolation level Django assumes, and standardise on UTC — storing time in UTC and converting for display is the sane default for any production app. The \q exits the shell.
Connecting Django
Django talks to PostgreSQL through the psycopg driver. Install it in your virtual environment:
pip install "psycopg[binary]"
Keep credentials out of your code — read them from environment variables via a .env file, never hard-coded in settings.py:
# settings.py
from decouple import config
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": config("DB_NAME"),
"USER": config("DB_USER"),
"PASSWORD": config("DB_PASSWORD"),
"HOST": config("DB_HOST", default="127.0.0.1"),
"PORT": config("DB_PORT", default="5432"),
}
}
Then run your migrations against the new database:
python manage.py migrate
If that completes without a permissions error, your least-privilege user has exactly the access it needs — and nothing it doesn't. That's PostgreSQL wired into Django, production-ready.
Prefer MySQL for this project instead? The same production setup for MySQL — install, secure, least-privilege user, and driver — is covered in Setting Up MySQL for Django on Ubuntu. And once your database is ready, the next step is getting the app online: see Deploying a Django App to Production With Gunicorn & Nginx.
FAQ
Frequently asked questions
Why create a dedicated database user instead of using postgres?
The postgres superuser can do anything on the whole server. Giving your app its own user with rights to only its own database means a compromise of the app can't touch other databases or administer the server — the least-privilege principle.
What does REVOKE ALL ON SCHEMA public FROM PUBLIC do?
By default PostgreSQL lets any role create objects in the public schema. Revoking that and granting usage only to your app's user closes a quiet gap, so only your application can create tables there.
Why set the timezone to UTC on the role?
Storing all timestamps in UTC and converting for display is the standard, headache-free approach. It avoids ambiguity around daylight saving and makes Django's timezone-aware datetimes behave predictably.
Which driver should I use for Django and PostgreSQL?
psycopg (version 3) is the current recommended driver. Installing psycopg[binary] pulls in a precompiled build so you don't need local compilation, which is the simplest path on a fresh server.