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: trueandtty: trueenable interactive debugging -
PYTHONUNBUFFERED=1ensures 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
- Go to Run → Edit Configurations
- Click + → Python Remote Debug
- Set Host:
localhost, Port:5678 - Set Path mappings:
<Project root>=/app - 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:
- ✅ Are all containers running? (
docker-compose ps) - ✅ Any errors in logs? (
docker-compose logs) - ✅ Can containers communicate? (
nc -zv service port) - ✅ Are environment variables set correctly? (
env) - ✅ Are migrations applied? (
showmigrations) - ✅ Is database accessible? (
dbshell) - ✅ Are static files collected? (
findstatic) - ✅ Is DEBUG=True in development? (
show_settings) - ✅ Can you reproduce outside Docker? (Local testing)
- ✅ 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
dbnotlocalhost - 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.0notlocalhostin 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:
- Set up proper logging from day one
- Use remote debugging tools like debugpy for complex issues
- Master Docker commands for container inspection
- Create debugging shortcuts to save time
- 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
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/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.