Deploy Django on Koyeb
Deploy a Django app on Koyeb from GitHub or a Docker image: configure Gunicorn and the PORT, add a PostgreSQL database, set secrets, health checks, and regions, run migrations safely, serve static files with WhiteNoise, and attach a custom domain.
Problem statement
You want a managed platform that builds Django from a Git repository or runs a Docker image, gives you HTTPS and global regions, and does not require managing servers. Koyeb is a serverless container platform in that space. Like other PaaS hosts, it expects your app to listen on a given port, read configuration from the environment, and keep no state on local disk, so a few Django settings and a start command decide whether the first deploy works.
- Add
gunicorn,whitenoise,dj-database-url, andpsycopg[binary]to requirements. - Start command:
gunicorn config.wsgi --bind 0.0.0.0:$PORT --workers 2, with the service port set to the same value (8000 by default). - Create a PostgreSQL database (Koyeb's managed Postgres or an external provider) and set
DATABASE_URLas a secret. - Set
SECRET_KEY,ALLOWED_HOSTS(including.koyeb.app), andCSRF_TRUSTED_ORIGINS. - Run migrations in a separate one-off step or a release command, not in the web start command of every instance.
Step-by-step solution
1. Prepare settings for a container platform
import os
import dj_database_url
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", ".koyeb.app").split(",")
CSRF_TRUSTED_ORIGINS = [f"https://{h.lstrip('.')}" for h in ALLOWED_HOSTS if h and not h.startswith(".")]
DATABASES = {"default": dj_database_url.config(conn_max_age=60, conn_health_checks=True, ssl_require=True)}
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
Add whitenoise.middleware.WhiteNoiseMiddleware after SecurityMiddleware. Koyeb terminates TLS at its edge and forwards X-Forwarded-Proto.
2. Choose how Koyeb builds the app
- Buildpack: connect the GitHub repository; Koyeb detects Python from
requirements.txtorpyproject.toml. Set the run command explicitly and add a build step forcollectstatic(for example, in aProcfileor build command). - Dockerfile: Koyeb builds it, or you push an image to a registry and deploy that. A Dockerfile makes the build reproducible and lets you run
collectstaticat build time.
3. Configure the service
In the service settings:
- Run command:
gunicorn config.wsgi --bind 0.0.0.0:$PORT --workers 2 --timeout 60 - Port: the same value as
PORT(8000 unless you change it), exposed on path/. - Health check: HTTP on
/healthz/, a view that returns 200 without redirects or host problems. - Instance type and regions: start with one region close to your database.
Binding to 127.0.0.1 or a different port than the one configured makes health checks fail and the deploy never becomes healthy.
4. Add the database and secrets
Create a PostgreSQL database in the same region, or use Neon, Supabase, or another managed provider. Store DATABASE_URL, SECRET_KEY, and API keys as Koyeb secrets and reference them in the service's environment variables. Keep the database in the same region as the service; cross-region queries add latency to every request.
5. Run migrations
Running migrate in the start command makes every instance attempt it on every restart. Prefer a separate step: deploy a one-off job or run the command from CI against the database before switching the service to the new version. For small single-instance apps, a start command of python manage.py migrate --noinput && gunicorn ... works but delays readiness.
Explanation
Koyeb runs your app as containers behind its global edge network, which terminates TLS and routes requests to healthy instances. Each deploy builds an image, starts new instances, waits for health checks, and then shifts traffic. That model rewards the same practices as every container platform: listen on the provided port, read settings from the environment, collect static files at build time, and store uploads and data in external services.
Edge cases / notes
- Scale to zero. If enabled for a service, the first request after idle starts an instance and is slower; keep at least one instance for production traffic.
- Workers. Run Celery as a separate worker service with the same image and a different command, without a public port.
- Media files. Use S3, R2, or another object store with django-storages.
Next steps
- Compare platforms in Alternatives to Heroku for Hosting Django.
- Fix port and health problems with Fix No Open Ports and Port Binding on PaaS Hosts.
- Serve assets with Serve Static Files With WhiteNoise.
FAQ
Does Koyeb need a Dockerfile for Django?
No. It can build from Git with buildpacks. A Dockerfile gives more control.
Which port should Gunicorn use?
The port configured for the service, available as PORT. Bind to 0.0.0.0, not localhost.
Where should media uploads go?
To object storage such as S3 or Cloudflare R2. Instance disks are not persistent.
How do I run Celery on Koyeb?
As a separate worker service using the same image with a celery command and no exposed port.