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.
Drake Nguyen
Founder · System Architect
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.