Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker

Debugging a Django application running inside a Docker container can feel like trying to fix a car engine while it’s still running—challenging, but definitely not impossible. If you’ve ever found yourself staring at cryptic error messages with no stack…


This content originally appeared on DEV Community and was authored by Maksym

Debugging a Django application running inside a Docker container can feel like trying to fix a car engine while it's still running—challenging, but definitely not impossible. If you've ever found yourself staring at cryptic error messages with no stack trace, or wondering why your breakpoints never trigger, you're not alone. This guide will equip you with the tools and techniques to effectively debug Dockerized Django applications.

Why Docker Makes Debugging Tricky

Docker containers are designed to be isolated, lightweight, and production-like. While this is great for deployment, it creates some unique challenges for debugging:

  • Limited direct access to the running process
  • Logs can be scattered or hard to follow
  • Interactive debuggers require special configuration
  • File system isolation makes it harder to inspect state
  • Network isolation can complicate remote debugging

But don't worry—once you understand how to work with these constraints, debugging becomes straightforward.

Setting Up Your Environment for Success

Before diving into specific debugging techniques, let's ensure your Docker setup is debug-friendly.

Optimize Your Dockerfile for Development

Create a separate Dockerfile.dev for development that includes debugging tools:

FROM python:3.11-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
    postgresql-client \
    netcat-traditional \
    vim \
    curl \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Install Python dependencies
COPY requirements.txt requirements-dev.txt ./
RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt

# Copy application code
COPY . .

# Don't run as root
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Configure docker-compose.yml for Development

Your docker-compose.yml should make debugging easier:

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      # Mount code for live reloading
      - .:/app
      # Preserve Python packages
      - python-packages:/usr/local/lib/python3.11/site-packages
    ports:
      - "8000:8000"
      # Expose debugger port
      - "5678:5678"
    environment:
      - DEBUG=True
      - PYTHONUNBUFFERED=1
      - DJANGO_SETTINGS_MODULE=myproject.settings.dev
    depends_on:
      - db
    stdin_open: true
    tty: true

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres-data:
  python-packages:

Key settings explained:

  • stdin_open: true and tty: true enable interactive debugging
  • PYTHONUNBUFFERED=1 ensures logs appear in real-time
  • Volume mounting allows code changes without rebuilding
  • Exposed ports enable remote debugging

Technique 1: Mastering Docker Logs

The first line of defense is always logs. Docker provides powerful logging capabilities.

View Real-Time Logs

# Follow logs from all services
docker-compose logs -f

# Follow logs from specific service
docker-compose logs -f web

# Show last 100 lines and follow
docker-compose logs -f --tail=100 web

# View logs with timestamps
docker-compose logs -f -t web

Enhance Django Logging

Configure comprehensive logging in your Django settings:

# settings/dev.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '[{levelname}] {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '[{levelname}] {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': '/app/logs/debug.log',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['console', 'file'],
        'level': 'INFO',
    },
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': False,
        },
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG',  # Shows SQL queries
            'propagate': False,
        },
        'myapp': {
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}

Use logging strategically in your code:

import logging

logger = logging.getLogger(__name__)

def my_view(request):
    logger.info(f"Processing request: {request.method} {request.path}")
    logger.debug(f"Request data: {request.POST}")

    try:
        # Your code here
        result = perform_operation()
        logger.info(f"Operation successful: {result}")
    except Exception as e:
        logger.error(f"Operation failed: {str(e)}", exc_info=True)
        raise

    return response

Technique 2: Interactive Shell Access

Sometimes you need to poke around inside the container.

Access the Container Shell

# Start a bash shell in running container
docker-compose exec web bash

# Or use sh if bash isn't available
docker-compose exec web sh

# Run as root for system-level debugging
docker-compose exec -u root web bash

Use Django Shell Inside Container

# Django shell
docker-compose exec web python manage.py shell

# Django shell with IPython (if installed)
docker-compose exec web python manage.py shell -i ipython

# Run specific Django commands
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py createsuperuser

One-Liner Debugging Commands

# Check database connectivity
docker-compose exec web python manage.py dbshell

# Inspect database tables
docker-compose exec db psql -U myuser -d mydb -c "\dt"

# Check environment variables
docker-compose exec web env

# Test a specific view
docker-compose exec web python manage.py shell -c "
from django.test import Client
client = Client()
response = client.get('/api/endpoint/')
print(response.status_code)
print(response.content)
"

Technique 3: Remote Debugging with debugpy

For complex issues, nothing beats a proper debugger with breakpoints.

Install debugpy

Add to requirements-dev.txt:

debugpy==1.8.0

Configure Remote Debugging

Create a debugging wrapper script debug_server.py:

import os
import sys
import debugpy

# Allow other computers to attach to debugpy
debugpy.listen(("0.0.0.0", 5678))

# Pause execution until debugger attaches
if os.environ.get('WAIT_FOR_DEBUGGER', 'False') == 'True':
    print("⏳ Waiting for debugger to attach...")
    debugpy.wait_for_client()
    print("✅ Debugger attached!")

# Start Django
if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

Update your docker-compose.yml command:

services:
  web:
    command: python debug_server.py runserver 0.0.0.0:8000
    environment:
      - WAIT_FOR_DEBUGGER=False  # Set to True to wait for debugger

Configure VS Code

Create .vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Django: Remote Attach",
            "type": "python",
            "request": "attach",
            "connect": {
                "host": "localhost",
                "port": 5678
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "/app"
                }
            ],
            "django": true,
            "justMyCode": false
        }
    ]
}

Configure PyCharm

  1. Go to Run → Edit Configurations
  2. Click + → Python Remote Debug
  3. Set Host: localhost, Port: 5678
  4. Set Path mappings: <Project root>=/app
  5. Click OK

Using the Debugger

# Start your containers
docker-compose up

# In VS Code: Press F5 or click "Run and Debug"
# Set breakpoints in your code
# Make a request to trigger your code

The debugger will pause at breakpoints, allowing you to:

  • Inspect variables
  • Step through code
  • Evaluate expressions
  • Examine the call stack

Technique 4: Using pdb for Quick Debugging

For quick debugging sessions, Python's built-in pdb works well.

Insert Breakpoints

def my_view(request):
    data = request.POST.get('data')

    # Insert breakpoint
    import pdb; pdb.set_trace()

    # Or use the modern way (Python 3.7+)
    breakpoint()

    result = process_data(data)
    return JsonResponse({'result': result})

Attach to Container with TTY

# Stop current containers
docker-compose down

# Start with attached terminal
docker-compose run --service-ports web

# Now when pdb breakpoint hits, you'll get an interactive prompt

Common pdb commands:

  • n (next) - Execute next line
  • s (step) - Step into function
  • c (continue) - Continue execution
  • l (list) - Show code context
  • p variable - Print variable value
  • pp variable - Pretty-print variable
  • w (where) - Show stack trace
  • q (quit) - Quit debugger

Technique 5: Debugging Database Issues

Database problems are common in Django applications.

Inspect Database State

# Access PostgreSQL directly
docker-compose exec db psql -U myuser -d mydb

# Run SQL queries
docker-compose exec db psql -U myuser -d mydb -c "
SELECT * FROM django_migrations ORDER BY applied DESC LIMIT 5;
"

# Check database size and connections
docker-compose exec db psql -U myuser -d mydb -c "
SELECT 
    datname,
    pg_size_pretty(pg_database_size(datname)) as size,
    numbackends as connections
FROM pg_stat_database
WHERE datname = 'mydb';
"

Debug SQL Queries

Enable SQL logging in Django settings:

LOGGING['loggers']['django.db.backends'] = {
    'handlers': ['console'],
    'level': 'DEBUG',
}

Or use Django Debug Toolbar:

# requirements-dev.txt
django-debug-toolbar==4.2.0

# settings/dev.py
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INTERNAL_IPS = ['127.0.0.1', 'host.docker.internal']

# For Docker, you need this:
import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS += [ip[:-1] + '1' for ip in ips]

Use Django Shell Plus

# Install
pip install django-extensions ipython

# Add to settings
INSTALLED_APPS += ['django_extensions']

# Use it
docker-compose exec web python manage.py shell_plus --print-sql

This auto-imports models and shows SQL for all queries.

Technique 6: Network and Service Debugging

Container networking can be tricky.

Test Inter-Container Connectivity

# From web container, test database connection
docker-compose exec web nc -zv db 5432

# Test HTTP endpoint from another service
docker-compose exec web curl http://another_service:8000/health

# Check DNS resolution
docker-compose exec web nslookup db
docker-compose exec web ping -c 3 db

Inspect Docker Networks

# List networks
docker network ls

# Inspect specific network
docker network inspect myproject_default

# Check which containers are on network
docker network inspect myproject_default | grep -A 5 "Containers"

Debug External Network Issues

# Check if container can reach external services
docker-compose exec web curl -I https://api.example.com

# Check DNS resolution
docker-compose exec web nslookup google.com

# Test with specific DNS server
docker-compose exec web nslookup google.com 8.8.8.8

Technique 7: Performance Profiling

Identify performance bottlenecks in your containerized app.

Use Django Silk

# requirements-dev.txt
django-silk==5.0.4

# settings/dev.py
INSTALLED_APPS += ['silk']
MIDDLEWARE.insert(0, 'silk.middleware.SilkyMiddleware')

# urls.py
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]

Access profiling data at http://localhost:8000/silk/

Use cProfile

# Create a management command: myapp/management/commands/profile_view.py
from django.core.management.base import BaseCommand
import cProfile
import pstats
from django.test import Client

class Command(BaseCommand):
    def handle(self, *args, **options):
        client = Client()
        profiler = cProfile.Profile()

        profiler.enable()
        response = client.get('/api/slow-endpoint/')
        profiler.disable()

        stats = pstats.Stats(profiler)
        stats.sort_stats('cumulative')
        stats.print_stats(20)  # Top 20 slowest functions

Run it:

docker-compose exec web python manage.py profile_view

Monitor Container Resources

# Real-time container stats
docker stats

# Stats for specific container
docker stats myproject_web_1

# Check container processes
docker-compose exec web ps aux

# Check memory usage inside container
docker-compose exec web free -h

# Check disk usage
docker-compose exec web df -h

Technique 8: Debugging Static Files and Media

Static file issues are common during development.

Verify Static File Configuration

# Collect static files
docker-compose exec web python manage.py collectstatic --noinput

# Check static files location
docker-compose exec web python manage.py findstatic admin/css/base.css

# List all static file locations
docker-compose exec web python manage.py findstatic --verbosity=2 admin/css/base.css

Debug Media File Uploads

# settings/dev.py
MEDIA_ROOT = '/app/media'
MEDIA_URL = '/media/'

# Check permissions
import os
import logging
logger = logging.getLogger(__name__)

def check_media_permissions():
    media_root = settings.MEDIA_ROOT
    if not os.path.exists(media_root):
        logger.warning(f"MEDIA_ROOT does not exist: {media_root}")
    elif not os.access(media_root, os.W_OK):
        logger.error(f"MEDIA_ROOT is not writable: {media_root}")
    else:
        logger.info(f"MEDIA_ROOT is configured correctly: {media_root}")

Technique 9: Environment and Configuration Debugging

Configuration issues are hard to spot but easy to fix once found.

Dump All Django Settings

# Create management command: myapp/management/commands/show_settings.py
from django.core.management.base import BaseCommand
from django.conf import settings
import pprint

class Command(BaseCommand):
    def handle(self, *args, **options):
        # Get all settings
        settings_dict = {
            key: getattr(settings, key)
            for key in dir(settings)
            if key.isupper()
        }
        pprint.pprint(settings_dict)
docker-compose exec web python manage.py show_settings

Check Environment Variables

# View all env vars
docker-compose exec web env

# Check specific variable
docker-compose exec web bash -c 'echo $DATABASE_URL'

# Verify Django can access them
docker-compose exec web python -c "
import os
from django.conf import settings
print('DEBUG:', settings.DEBUG)
print('DATABASE:', settings.DATABASES)
print('SECRET_KEY:', settings.SECRET_KEY[:10] + '...')
"

Validate Docker Compose Configuration

# Validate compose file syntax
docker-compose config

# View merged configuration
docker-compose config --services

# Check resolved environment variables
docker-compose config --resolve-image-digests

Technique 10: Debugging Migrations

Migration issues can break your entire application.

Check Migration Status

# Show migration status
docker-compose exec web python manage.py showmigrations

# Show unapplied migrations
docker-compose exec web python manage.py showmigrations --plan

# Check for conflicts
docker-compose exec web python manage.py makemigrations --dry-run --check

Debug Failed Migrations

# Run migrations with verbose output
docker-compose exec web python manage.py migrate --verbosity=3

# Fake a specific migration (if needed)
docker-compose exec web python manage.py migrate myapp 0004 --fake

# Roll back to specific migration
docker-compose exec web python manage.py migrate myapp 0003

# Start fresh (DANGEROUS - dev only!)
docker-compose down -v
docker-compose up -d db
docker-compose exec db psql -U myuser -d mydb -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
docker-compose exec web python manage.py migrate

Generate SQL for Migration

# See SQL that will be executed
docker-compose exec web python manage.py sqlmigrate myapp 0005

# Show SQL for all pending migrations
docker-compose exec web python manage.py sqlmigrate --backwards myapp 0005

Pro Tips and Best Practices

Use Docker Compose Override Files

Create docker-compose.override.yml for personal debugging settings:

version: '3.8'

services:
  web:
    environment:
      - DEBUG=True
      - DJANGO_LOG_LEVEL=DEBUG
    volumes:
      - ./my_local_debugging:/app/debug
    ports:
      - "5678:5678"  # Debugger
      - "8001:8000"  # Alternative port

This file is automatically merged and git-ignored by default.

Create Debugging Shortcuts

Add aliases to your .bashrc or .zshrc:

# Docker compose shortcuts
alias dc='docker-compose'
alias dcup='docker-compose up -d'
alias dcdown='docker-compose down'
alias dclogs='docker-compose logs -f'
alias dcweb='docker-compose exec web'
alias dcdb='docker-compose exec db psql -U myuser -d mydb'
alias dcshell='docker-compose exec web python manage.py shell_plus'
alias dcbash='docker-compose exec web bash'

# Django shortcuts
alias djmigrate='docker-compose exec web python manage.py migrate'
alias djmakemigrations='docker-compose exec web python manage.py makemigrations'
alias djtest='docker-compose exec web python manage.py test'
alias djcollectstatic='docker-compose exec web python manage.py collectstatic --noinput'

Use Watchdog for Auto-Restart

# requirements-dev.txt
watchdog==3.0.0

# Start server with auto-reload
docker-compose exec web watchmedo auto-restart \
    --directory=/app \
    --pattern="*.py" \
    --recursive \
    -- python manage.py runserver 0.0.0.0:8000

Build a Debugging Checklist

When something goes wrong, systematically check:

  1. ✅ Are all containers running? (docker-compose ps)
  2. ✅ Any errors in logs? (docker-compose logs)
  3. ✅ Can containers communicate? (nc -zv service port)
  4. ✅ Are environment variables set correctly? (env)
  5. ✅ Are migrations applied? (showmigrations)
  6. ✅ Is database accessible? (dbshell)
  7. ✅ Are static files collected? (findstatic)
  8. ✅ Is DEBUG=True in development? (show_settings)
  9. ✅ Can you reproduce outside Docker? (Local testing)
  10. ✅ Is it a volume/permission issue? (ls -la)

Common Pitfalls and Solutions

Issue: Changes Not Reflected

Problem: Code changes don't appear when refreshing.

Solutions:

  • Check volume mounting: docker-compose config | grep volumes
  • Ensure PYTHONUNBUFFERED=1
  • Restart with fresh build: docker-compose up --build
  • Clear Python cache: docker-compose exec web find . -name "*.pyc" -delete

Issue: Database Connection Refused

Problem: Django can't connect to PostgreSQL.

Solutions:

  • Check if DB is ready: docker-compose exec db pg_isready
  • Verify service name matches: Use db not localhost
  • Add healthcheck to wait for DB:
services:
  web:
    depends_on:
      db:
        condition: service_healthy
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser"]
      interval: 5s
      timeout: 5s
      retries: 5

Issue: Permission Denied Errors

Problem: Can't write to mounted volumes.

Solutions:

  • Match container user to host user UID
  • Use named volumes for sensitive data
  • Run container as root temporarily: docker-compose exec -u root web bash

Issue: Debugger Won't Attach

Problem: Remote debugger fails to connect.

Solutions:

  • Ensure port 5678 is exposed and mapped
  • Use 0.0.0.0 not localhost in debugpy.listen()
  • Check firewall settings
  • Verify path mappings match exactly

Conclusion

Debugging Django in Docker doesn't have to be painful. With the right setup and tools, you can have a debugging experience that rivals local development while enjoying Docker's benefits. The key is to:

  1. Set up proper logging from day one
  2. Use remote debugging tools like debugpy for complex issues
  3. Master Docker commands for container inspection
  4. Create debugging shortcuts to save time
  5. Build systematic debugging habits with checklists

Remember that debugging is a skill that improves with practice. Start with simple techniques like logging and shell access, then graduate to more advanced methods like remote debugging as needed. Your future self will thank you for investing time in proper debugging infrastructure.

Happy debugging! 🐛🔍🐳


This content originally appeared on DEV Community and was authored by Maksym


Print Share Comment Cite Upload Translate Updates
APA

Maksym | Sciencx (2025-12-03T09:18:49+00:00) Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker. Retrieved from https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/

MLA
" » Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker." Maksym | Sciencx - Wednesday December 3, 2025, https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/
HARVARD
Maksym | Sciencx Wednesday December 3, 2025 » Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker., viewed ,<https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/>
VANCOUVER
Maksym | Sciencx - » Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/
CHICAGO
" » Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker." Maksym | Sciencx - Accessed . https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/
IEEE
" » Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker." Maksym | Sciencx [Online]. Available: https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/. [Accessed: ]
rf:citation
» Taming the Container Beast: A Developer’s Guide to Debugging Django in Docker | Maksym | Sciencx | https://www.scien.cx/2025/12/03/taming-the-container-beast-a-developers-guide-to-debugging-django-in-docker/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.