Deploy Django with Coolify
Deploy Django on your own server with Coolify: Dockerfile builds from Git, managed PostgreSQL and Redis, automatic HTTPS, environment variables, migrations, and backups.
Problem statement
Coolify is an open-source, self-hosted platform that gives a VPS a Heroku-like workflow: connect a Git repository, and it builds images, runs containers behind a reverse proxy, issues certificates, and manages databases from a web dashboard. For Django developers who want push-to-deploy on their own server, it removes a lot of manual Nginx and systemd work.
It does not remove Django's production requirements. The app still needs a production server process, correct host and proxy settings, a plan for migrations, persistent storage for uploads, and backups. This guide sets those up in Coolify.
- Install Coolify on a fresh server with at least 2 GB of RAM and point a domain at it.
- Add a Dockerfile to the project that runs Gunicorn on port 8000 as a non-root user.
- Create PostgreSQL (and Redis if needed) resources in the same Coolify project.
- Create an application from your Git repository using the Dockerfile build pack, exposing port 8000.
- Set environment variables: secret key, database URL, allowed hosts, trusted origins.
- Set the domain with an
https://prefix so the proxy obtains a certificate. - Run migrations as part of each deploy, add a persistent volume for media, and schedule database backups.
Step-by-step solution
1. Install Coolify
On a fresh Ubuntu or Debian server, as root:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
Open http://<server-ip>:8000, create the admin account immediately (the first visitor becomes admin), then set the instance domain and enable HTTPS for the dashboard. Restrict the dashboard port in your firewall once it is served through its own domain.
Coolify can also manage additional servers over SSH, so the dashboard server and the application server can be separate machines.
2. Add a production Dockerfile
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN DJANGO_SETTINGS_MODULE=config.settings.build python manage.py collectstatic --noinput
RUN useradd --create-home app && mkdir -p /app/media && chown app:app /app/media
USER app
EXPOSE 8000
COPY --chmod=755 docker/start.sh /start.sh
CMD ["/start.sh"]
#!/bin/sh
# docker/start.sh
set -e
python manage.py migrate --noinput
exec gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --access-logfile -
Running migrate in the start script is acceptable for a single web container. If you scale to several replicas, move migrations to a one-off step so they do not run concurrently. curl is installed so the container health check can call the app.
Coolify's Nixpacks build pack can build Django without a Dockerfile, but an explicit Dockerfile keeps builds reproducible and portable to any other container host.
3. Create the database and Redis
In the project, add a PostgreSQL resource and, if you use Celery or caching, a Redis resource. Coolify generates credentials and shows an internal connection URL that resolves on the Docker network shared by resources in the same environment. Use that internal URL from Django; do not make the database publicly accessible unless you need external access for a specific reason.
4. Create the application
Add a new resource from a Git repository. For private repositories, connect a GitHub App or add a deploy key. Then configure:
- Build pack: Dockerfile.
- Ports Exposes:
8000(the port Gunicorn binds inside the container). - Domains:
https://example.com,https://www.example.com. Thehttps://prefix tells the proxy to obtain a Let's Encrypt certificate. - Health check: path
/healthz/, port8000. With a health check, Coolify waits for the new container to be healthy before switching traffic.
Create an A record for the domain pointing at the server before the first deploy, or certificate issuance fails.
5. Set environment variables
In the application's environment variables, add:
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_SECRET_KEY=long-random-value
DATABASE_URL=postgres://postgres:generated@<postgres-internal-host>:5432/postgres
REDIS_URL=redis://default:generated@<redis-internal-host>:6379/0
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DJANGO_CSRF_TRUSTED_ORIGINS=https://example.com,https://www.example.com
Variables are available at runtime by default. Only mark a variable as a build variable if the Docker build needs it; secrets baked into image layers can be read by anyone with the image.
6. Configure Django for the proxy
Coolify's proxy (Traefik by default) terminates TLS and forwards requests over HTTP:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split(",")
CSRF_TRUSTED_ORIGINS = os.environ["DJANGO_CSRF_TRUSTED_ORIGINS"].split(",")
Container health checks call the app directly over HTTP. Exempt the health path from the HTTPS redirect with SECURE_REDIRECT_EXEMPT = [r"^healthz/$"], and make sure the host the check uses is allowed.
7. Persist media and add workers
Container filesystems are replaced on every deploy. In the application's Storages settings, add a persistent volume mounted at /app/media, or use object storage with django-storages, which also works when you add a second server.
For Celery, create a second application from the same repository with a custom start command such as celery -A config worker --loglevel=info, no domain, and the same environment variables.
8. Deploy, verify, and back up
Enable automatic deployment on push (webhook) or deploy from the dashboard. Watch the build and runtime logs in the deployment view.
Verification check:
curl -sI https://example.com/ | head -1
curl -s https://example.com/healthz/
- The admin login works over HTTPS.
- An uploaded file is still present after a redeploy.
Configure scheduled backups on the PostgreSQL resource and send them to S3-compatible storage, so a server loss does not take the backups with it.
Explanation
Coolify combines a build system, a container runtime, a reverse proxy, and a certificate manager behind one dashboard. Each deploy builds a new image from your repository and replaces the running container, which is why anything written to the container filesystem disappears and why migrations must be part of the deploy. The proxy is what makes HTTPS automatic, and it is also why Django must be told to trust X-Forwarded-Proto.
Edge cases / notes
Rollback
Coolify keeps previous deployments and can redeploy an earlier image from the deployment history. Rolling back code does not undo migrations, so keep them backward compatible.
Resource limits
Builds happen on the server by default and can exhaust memory on small machines while the app is running. Add swap, give the server more memory, or build images in CI and deploy a prebuilt image from a registry.
Coolify updates
Coolify updates itself from the dashboard. Treat it as infrastructure: read release notes and take a server snapshot before major version updates.
When to automate this
Most of the workflow is already automated by Coolify. What remains worth scripting is the Django side: a consistent start script, a health endpoint, and a smoke test after each deploy. Teams that outgrow a single server often keep the same Dockerfile and move to a registry-based CI pipeline.
Next steps
- Build a robust image with How to Dockerize a Django App for Production.
- Compare Git-push platforms in Deploy Django with Dokku on Your Own Server and Deploy Django with CapRover.
- If a deploy never becomes healthy, see Fix a Django Docker Container That Exits Immediately.
FAQ
Does Coolify need a Dockerfile for Django?
No. Nixpacks can detect Python projects. A Dockerfile is recommended for production because it makes the build explicit and reproducible.
Why does my Coolify Django app show a Bad Gateway error?
The exposed port does not match the port Gunicorn listens on, Gunicorn binds to 127.0.0.1 instead of 0.0.0.0, or the container crashed. Check the port setting and the runtime logs.
Where should Django migrations run on Coolify?
For a single container, in the start script before Gunicorn. For multiple replicas, run them once per deploy as a separate command so they never run concurrently.
Are uploads kept between Coolify deploys?
Only if you mount a persistent volume at the media path or store uploads in object storage.