Best practices for Django database configuration?

Hi everyone,

The Django docs show the following example for connecting to PostgreSQL:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "mydatabase",
        "USER": "mydatabaseuser",
        "PASSWORD": "mypassword",
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

Docs link: https://docs.djangoproject.com/en/6.0/ref/settings/#databases

I understand that hardcoding values like database credentials—especially sensitive ones such as passwords—directly in settings.py is not a good idea and that this snippet shouldn’t be followed verbatim in a real project.

What I’m interested in is the recommended overall approach for a Django project: how people typically structure database configuration across environments, what settings are commonly adjusted beyond the basics shown here, and how you manage differences between local development and production.

Any pointers to official documentation or canonical references would also be appreciated.

Thanks!

While this is generally true, I wouldn’t call it an absolute. There are a number of niche or limited circumstances where this could be considered perfectly acceptable. It’s important to understand why this could be a problem, and evaluate the risks accordingly.

In general, you would find that people tend to keep a set of settings in common for all environments, with a way of configuring those items being different.

The reason you won’t find much (if anything) about this in the official docs is that there are multiple ways of handling it, and they’re generally all good. There are at least three different libraries that I can think of to allow you to use environment variables. You can also store the settings in files using different mechanisms for using those settings. And, depending upon the setting, people might use more than one of these in a single project.

The point is, it’s flexible. Do it however you feel comfortable with managing it, and that satifies whatever external policies or limitations you may be required to comply with. (For example, I once worked with someone in an environment where they could not do a deployment to production - a different team was responsible for that. Additionally, that team did not have access to the production databases - those credentials had to be supplied by yet another team. Now, that was in my “pre-Django” days, so I never saw Django deployed in that environment - but I understand what would be necessary.)

There’ve been a number of discussions in the forum about this topic to one degree or another. For example, see Settings refactor and the various discussions it references. So it’s not like there isn’t some interest in identifying a “standard” or “canonical” solution - but we’re not there yet.

In my case, I don’t typically have database credentials in the settings at all. Most of the time, I connect using a unix domain socket with peer authentication. (See PostgreSQL: Documentation: 18: 20.9. Peer Authentication)

Or, if I need to do something else, I might use a .pgpass file. (See PostgreSQL: Documentation: 18: 32.16. The Password File)

Either way, the database credentials are managed completely outside the context of the application itself.

I am not consistent with how I manage other configuration items. I have tried a number of different methods through the years, some with more success than others. Since I’m now just coding for my own pleasure, things are a lot simpler for me. My current personal favorite includes adding code to my settings.py file to test the DEBUG setting, and changing behavior based on that.

(Side note: I actually got this idea from django-debug-toolbar. In older releases, you would conditionally add the urls in an if settings.DEBUG statement. I figured if it was good enough for them, it’s good enough for me - and I like the idea of having all settings in one file.)

You’ll find most of them at Deployment checklist | Django documentation | Django

1 Like

Thanks Ken! Always appreciate your thorough answers on here.

You mentioned using Unix domain sockets with peer authentication; is that something that works well alongside containerized setups, or is it more suited to traditional deployments where the app and database share a host?

This is not an area I’m really familiar with. It’s been 5 years since I experimented with that, so I’m working from some hazy memories. I know I tried it, got it to work, and then moved away from it for some reason I don’t exactly remember - other than a vague feeling that I decided it wasn’t worth the effort.

(I think it was a situations where I thought there was too much effort to manage and maintain it - but I could easily be mixing it up with a different issue. We had three different services that we wanted to use domain sockets for communications - PostgreSQL, redis, and nginx, and I had some difficulty making it all work together the way I had intended.)

There are basically two different situations:

  • PostgreSQL on host, applications in containers.
  • PostgreSQL and applications in containers.

The first is the easier one of the two. Each application needs to mount the directory where the socket file is stored (e.g. /var/run/postgresql on Ubuntu) and the application container needs to run as the UID being used for the connection. But since you’ve got PostgreSQL running on the host, making the changes to the pg_hba.conf file is easier.

The second situation requires the socket file to be in a shared volume that both containers mount. This means that when you have multiple instances of PostgreSQL running in separate containers, that there is a different volume for each or you configure each for a unique port.

Beyond this, I don’t remember what particular difficulties I encountered along the way. (They might be more easily solved by someone with more experience with Docker Compose than I have.)

Got it. I want to bring it back to two earlier points you raised.

First, you mentioned that there are “at least three different libraries” you could think of for working with environment variables, along with other mechanisms for storing settings, and that people sometimes use more than one of these in a single project. Since I don’t have external policies or constraints pushing me toward a particular solution and I’m working on a personal project, I’m trying to get a clearer picture of what those options actually are in practice. What library would you default to for a simple Django project, and what other configuration mechanisms were you thinking of?

Related to that, I’d also like to understand where the standard library fits in here. Is using the os module (e.g. os.environ) generally enough for managing environment-based configuration, or are there specific limitations that usually justify pulling in third-party packages?

Second, you mentioned that your current personal preference is to add logic in settings.py that branches on DEBUG, inspired by the approach used by django-debug-toolbar.

Rather than the DDT implementation itself, I’d be interested in a small, self-contained example of what that looks like in a project’s settings.py file. For example, a minimal snippet showing how settings differ when DEBUG is True vs False, and how much conditional logic you consider reasonable to keep inline.

Seeing that pattern in isolation would help me better understand how you’re applying it in practice.

From Carlton’s post at Settings refactor - #2 by carltongibson -

In addition to Django-environ, I was also thinking of goodconf and django-classy-settings. (I may have been aware of the other two, but they just didn’t come to mind when I wrote my post.)

It’s not “limitations”, it’s the benefits that justify it. When you directly access the environment variables, you get a string. Libraries such as django-environ can parse those strings and return specific data types. (Saving you the effort of writing that code.)

Snippet:

INSTALLED_APPS = [ 'app1', 'app2', 'app3']

if DEBUG:
    INSTALLED_APPS += 'app4'

(Or, if the position of the new app is important)

if DEBUG:
    INSTALLED_APPS.insert(2, 'app4')

(Or, if you need to install one of two different apps.)

if DEBUG:
    INSTALLED_APPS.insert(2, 'app4a')
else:
    INSTALLED_APPS.insert(2, 'app4b')

Anyway, that’s the basic idea.

As much as necessary.

In real terms, I don’t have that much that changes between environments.

I may have some debugging related apps or middleware for my development environment - possibly logging changes as well - but that’s about it.

1 Like