Skip to content

Add docker compose support#2295

Open
helioascorreia wants to merge 2 commits intodjango-commons:mainfrom
helioascorreia:feature/add-docker-compose-support
Open

Add docker compose support#2295
helioascorreia wants to merge 2 commits intodjango-commons:mainfrom
helioascorreia:feature/add-docker-compose-support

Conversation

@helioascorreia
Copy link

@helioascorreia helioascorreia commented Jan 17, 2026

Description

This feature is to add support to use this package with the docker compose

Fixes # (issue)

Checklist:

  • I have added the relevant tests for this change.
  • I have added an item to the Pending section of docs/changes.rst.

AI/LLM Usage

  • This PR includes code generated with the help of an AI/LLM

@matthiask
Copy link
Member

I don't really know docker compose all that well. Would it be possible to expand the docs a bit? Is there no way to somewhat reliably auto detect the value when using compose?

@tim-schilling
Copy link
Member

@matthiask I think it's a case of someone using a different host than the convention when running a docker container. The callout on compose is only part of the story from what I understand. Compose allows you to define configuration to customize it, but it's not just a compose feature.

I think I'd rather have this documented and ask the dev to implement the function themselves rather than us add a setting. Perhaps we can rework the checks so that we can reuse more of the toolbar's functions? For example show_toolbar_with_docker could be:

def show_toolbar_with_docker(request: HttpRequest) -> bool:
    """
    Default function to determine whether to show the toolbar on a given page.
    """
    if not show_toolbar(request):
        return False

    # Test: Docker
    try:
        # This is a hack for docker installations. It attempts to look
        # up the IP address of the docker host.
        # This is not guaranteed to work.
        docker_ip = (
            # Convert the last segment of the IP address to be .1
            ".".join(socket.gethostbyname("host.docker.internal").rsplit(".")[:-1])
            + ".1"
        )
        if request.META.get("REMOTE_ADDR") == docker_ip:
            return True
    except socket.gaierror:
        # It's fine if the lookup errored since they may not be using docker
        pass
    # No test passed
    return False

@helioascorreia
Copy link
Author

No problem, I'll explain you with more detail. Docker compose is a tool to help orchestrate more than one container, it's not something complex like kubernetes and for a long time the docker don't advise to use in production (I don't know if it's the case now).
This package is to use with development, so docker compose helps me put all the project dependencies managed with docker easier.

For example in this project that I'm working, besides the Dockerfile I have the compose.yml file

services:
  web:
    build:
      context: .
      args:
        REQUIREMENTS: local.txt
    command: python manage.py runserver 0:8000
    volumes:
      - .:/project
    ports:
      - ${WEB_PORT:-8000}:8000
    depends_on:
      - postgres

  postgres:
    image: postgis/postgis:16-3.5
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
    ports:
      - ${POSTGRES_PORT:-5432}:5432
    volumes:
      - pgdata:/var/lib/postgresql/data

  selenium:
    image: selenium/standalone-firefox:latest
    ports:
      - "4444:4444"
      - "7900:7900"  # VNC port for viewing the browser (optional but useful). Password is secret
    shm_size: 2gb  

volumes:
  pgdata:

Here I have a container web for django (but I can call anything i want) and I also have the other dependencies, postgres and seleninum,.

In the settings I have this

DATABASES = {
    "default": {
        "ENGINE": "django.contrib.gis.db.backends.postgis",
        "NAME": "postgres",
        "USER": "postgres",
        "PASSWORD": "postgres",
        "HOST": "postgres",
    }
}

here the host is the name of the container name.
For example for functional tests I have this

from selenium import webdriver


def get_default_firefox_options():
    options = webdriver.FirefoxOptions()
    return options


class NewVisitor(unittest.TestCase):
    def setUp(self) -> None:
        # Connect to the remote Selenium server
        self.browser = webdriver.Remote(
            command_executor="http://selenium:4444/wd/hub",
            options=get_default_firefox_options(),
        )

    def tearDown(self) -> None:
        self.browser.quit()

    def test_can_see_homepage(self):
        # Access the web service using Docker's internal network
        self.browser.get("http://web:8000")

This creates the alias for the network. About your question "Is there no way to somewhat reliably auto detect the value when using compose?" I don't know because I can have a celery that runs a python process and I don't know how to catch the name for what you suggested. To my head it only comes a way of reading the compose file, finding the runserver but it's tricky because it doesn't need to run by that and I think it will be easy to have a lot of bugs.

In this project to make the package work I did this in my settings

DEBUG_TOOLBAR_CONFIG = {
    "SHOW_TOOLBAR_CALLBACK": "utils.django_debug_toolbar.show_toolbar_with_docker_compose",
}

And that file I have

def show_toolbar_with_docker_compose(request):
    """
    Default function to determine whether to show the toolbar on a given page.
    """
    if not settings.DEBUG:
        return False

    # Test: settings
    if request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS:
        return True

    # Test: Docker
    try:
        # This is a hack for docker installations. It attempts to look
        # up the IP address of the docker host.
        # This is not guaranteed to work.
        docker_ip = (
            # Convert the last segment of the IP address to be .1
            ".".join(
                socket.gethostbyname(
                    "web"
                ).rsplit(".")[:-1]
            )
            + ".1"
        )
        if request.META.get("REMOTE_ADDR") == docker_ip:
            return True
    except socket.gaierror:
        # It's fine if the lookup errored since they may not be using docker
        pass

    # No test passed
    return False

It was a bit tricky to make this work and I think it be a good addiction to the package.

With this what are your thoughts to make this easier for other person add the package with docker compose?

@tim-schilling
Copy link
Member

Thanks for the information @helioascorreia. Can you please clarify what case(s) that the toolbar was not showing up?

You mentioned functional tests too. Those probably shouldn't be using the toolbar. Not sure if you mentioned them for completeness or there's a misunderstanding there.

@matthiask
Copy link
Member

Thanks, both of you!

I know what docker compose does, it's just that I don't use podman/docker in development, only in production environments.

With this what are your thoughts to make this easier for other person add the package with docker compose?

Since this is about development it would be easier to replace the toolbar callback with something which just returns True for all requests, something like:

DEBUG_TOOLBAR_CONFIG = {
    "SHOW_TOOLBAR_CALLBACK": lambda request: True,
}

This is obviously unsafe for when the development server is exposed to anyone, even to internal networks. The usual caveats also apply! https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#show-toolbar-callback

(In development I still use the old hack which makes ip in settings.INTERNAL_IPS return True for all IPs there are...)

I had some doubts when we introduced the host.docker.internal autodetection since I thought It's just one more case covered and not a solution for all cases. I still would like an option which gets us 80% of the way with 20% of the effort, but it will only be possible with the benefit of hindsight to decide where we should have stopped applying more changes.

So, despite the additional setting im -0 to +0 on this change. I don't think it will hurt, the easier and more obvious solution would have been a custom SHOW_TOOLBAR_CALLBACK some time ago, but since we already went so far... That said, as a reader of the code I would probably appreciate a really simple solution directly in a project's settings file instead of having to go research the meaning of this additional setting.

@helioascorreia
Copy link
Author

Oh the functional test I only mention for completeness. So when I try to add the debug toolbar to this project I followed the docs https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#process. I read the part talking about docker and I added the debug_toolbar.middleware.show_toolbar_with_docker and it doesn't show the debug toolbar in the pages. I tried to find something about it on the troubleshooting section but I didn't see anything about it.
After that I tried to debug the problem and I saw that I needed to change this function

def show_toolbar_with_docker(request: HttpRequest) -> bool:
    """
    Default function to determine whether to show the toolbar on a given page.
    """
    if not settings.DEBUG:
        return False

    # Test: settings
    if request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS:
        return True

    # Test: Docker
    try:
        # This is a hack for docker installations. It attempts to look
        # up the IP address of the docker host.
        # This is not guaranteed to work.
        docker_ip = (
            # Convert the last segment of the IP address to be .1
            ".".join(socket.gethostbyname("host.docker.internal").rsplit(".")[:-1])
            + ".1"
        )
        if request.META.get("REMOTE_ADDR") == docker_ip:
            return True
    except socket.gaierror:
        # It's fine if the lookup errored since they may not be using docker
        pass

    # No test passed
    return False

to instead have socket.gethostbyname("host.docker.internal") i needed to have socket.gethostbyname("web") because is the name of the docker compose container

@gegoune
Copy link

gegoune commented Jan 18, 2026

I don't think passing service name in is a solution here. I also didn't manage to get built in detection to work, but this does the job just fine:

DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG}

@matthiask
Copy link
Member

After that I tried to debug the problem and I saw that I needed to change this function [...]

I think that points to the underlying problem where people try to do too much in the SHOW_TOOLBAR_CALLBACK when all they should to is just configure a simple version according to their preferences.

I don't think passing service name in is a solution here. I also didn't manage to get built in detection to work, but this does the job just fine:

Exactly.

The thing is that we don't recommend using DEBUG directly because the DEBUG value in the settings file may not have the same value as settings.DEBUG, because other code such as the test runner may override the latter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants