Fix DigitalOcean App Platform Health Check Failed for Django
Fix DigitalOcean App Platform deploys that fail with health check failed or container exited for Django: bind Gunicorn to the right port, configure the HTTP health check path, allow the internal host, give migrations a pre-deploy job, and size the instance.
Problem statement
The build finishes, then the deploy fails and rolls back:
ERROR failed health checks after 6 attempts with error
Readiness probe failed: dial tcp 10.244.12.7:8080: connect: connection refused
or:
Deploy Error: Health Checks
Your deploy failed because your container exited / health check failed.
App Platform starts your container and checks that it answers on the configured HTTP port (8080 unless you change it). If the check fails a number of times in a row, the new version is never put in front of traffic. For Django, the usual causes are Gunicorn listening on a different port or address, an HTTP health check that gets a redirect or 400 from Django, a start command that runs slow migrations, or a container that runs out of memory.
- Bind Gunicorn to
0.0.0.0:$PORT; App Platform setsPORTto the component's HTTP port. - Set the health check to a dedicated path such as
/healthz/that returns 200 without redirects. - Exempt that path from
SECURE_SSL_REDIRECT, and allow the platform's host inALLOWED_HOSTS. - Move
migrateinto a pre-deploy job instead of the run command. - If logs show the container being killed, raise the instance size or lower Gunicorn workers.
Step-by-step solution
1. Read the runtime and deploy logs
In the app's Activity tab, open the failed deployment and view both the build and the runtime logs. From the CLI:
doctl apps logs <app-id> web --type run
doctl apps logs <app-id> --type deploy
connection refused means nothing listened on the port. A Django traceback or DisallowedHost means it listened but answered badly. No output at all after start usually means the process exited, often out of memory.
2. Listen on the right port and address
gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 60
The component's HTTP Port setting and the port Gunicorn binds must match. Binding to 127.0.0.1 makes the app unreachable from the health checker, which connects over the pod network.
3. Give the check a path Django answers simply
Configure the health check in the app spec:
services:
- name: web
http_port: 8080
run_command: gunicorn config.wsgi --bind 0.0.0.0:$PORT --workers 2
health_check:
http_path: /healthz/
initial_delay_seconds: 20
period_seconds: 10
failure_threshold: 6
And make the view cheap, with no redirect:
# urls.py
from django.http import HttpResponse
urlpatterns = [path("healthz/", lambda r: HttpResponse("ok")), ...]
# settings
SECURE_REDIRECT_EXEMPT = [r"^healthz/$"]
If Django logs DisallowedHost for the checker's internal address, either add that host pattern or answer /healthz/ in a small middleware placed before host validation. Do not set ALLOWED_HOSTS = ["*"].
4. Run migrations in a pre-deploy job
A run command such as python manage.py migrate && gunicorn ... delays the listener until migrations finish, and with several instances every one of them tries to migrate. Use a job component instead:
jobs:
- name: migrate
kind: PRE_DEPLOY
run_command: python manage.py migrate --noinput
The job runs once before the new version starts, and a failed migration stops the deploy cleanly.
5. Check memory
Basic instances have little RAM. Three or four Gunicorn workers of 200 MB each exceed a 512 MB instance, and the container is killed during startup. Reduce workers or move up an instance size; the runtime metrics show memory per instance.
Explanation
App Platform runs components on Kubernetes under the hood. The health check is a readiness probe: until it passes, the new pods receive no traffic, and if it keeps failing, the deployment is marked failed and the previous version keeps serving. Probes come from inside the cluster, connect to the pod's IP on the HTTP port, and follow the same rules as any HTTP client. Django's host validation and HTTPS redirect, both correct for public traffic, can turn a healthy app into a failing probe.
Edge cases / notes
- TCP checks. If you configure no HTTP path, App Platform checks only that the port accepts connections. That avoids host problems but hides a broken app.
- Workers and jobs. Worker components have no HTTP port and no HTTP health check; they fail only if the process exits.
- Dockerfile EXPOSE.
EXPOSEdoes not set the port. The component's HTTP port setting does.
Next steps
- Review the full setup with Deploy Django on DigitalOcean App Platform.
- For port errors on other platforms, see Fix No Open Ports and Port Binding on PaaS Hosts.
- Design a useful health endpoint with Django Health Check Endpoint Guide.
FAQ
What port does App Platform expect?
The component's HTTP port, 8080 by default, exposed to your process as the PORT variable.
Why does the health check get a 301?
SECURE_SSL_REDIRECT redirects the plain HTTP probe to HTTPS. Exempt the health path.
Should migrations run in the run command?
No. Use a PRE_DEPLOY job so they run once and do not delay the health check.
How long does App Platform wait before failing?
It depends on the initial delay, period, and failure threshold in your health check settings. Raise the initial delay if the app needs time to warm up.