A Django app in production needs four things from a host: a real WSGI server in front of it, a Postgres database it can reach, static files served correctly, and migrations that run before the new code takes traffic. Ownkube covers all four from a git push, with no cloud account and no YAML to hand-write.
This walks through deploying a standard Django app on Ownkube Compute: swapping the dev server for Gunicorn, wiring up a managed Postgres, handling static files with WhiteNoise, and running migrate on release.
TL;DR
- Push a Django app with a
Dockerfile, and Ownkube builds it, deploys it, and issues TLS automatically on your*.ownkube.apphostname. - Add an Ownkube-managed Postgres database and point
DATABASE_URLat it withdj-database-urlor the equivalent. - Serve static files with WhiteNoise instead of standing up a separate static host, and run
collectstaticin the Docker build. - Set
SECRET_KEY,DJANGO_SETTINGS_MODULE, andALLOWED_HOSTSas environment variables in the dashboard, not in the repo. - Run
migrateas a release step so it happens once, before Gunicorn starts serving the new version.
1. Prepare settings for production
Most of this is standard Django, not anything Ownkube-specific. Point the database and static file settings at environment variables instead of hardcoded values:
# settings.py
import os
import dj_database_url
DEBUG = False
SECRET_KEY = os.environ["SECRET_KEY"]
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",")
DATABASES = {
"default": dj_database_url.config(env="DATABASE_URL", conn_max_age=600)
}
STATIC_URL = "/static/"
STATIC_ROOT = "/app/staticfiles"
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
# ...the rest of your middleware
]
Add whitenoise, gunicorn, psycopg[binary], and dj-database-url to requirements.txt. WhiteNoise means you do not need a separate static file host or CDN just to serve CSS and JS in front of a small app.
2. Write a Dockerfile
Ownkube deploys from a Dockerfile when the repo has one, so you get full control over the Python version and the build steps:
FROM python:3.12-slim
RUN apt-get update -qq && apt-get install -y libpq-dev build-essential \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Collect static files at build time, before SECRET_KEY exists at runtime
RUN SECRET_KEY=build-only DATABASE_URL=sqlite:///build.db \
python manage.py collectstatic --noinput
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]
Gunicorn’s worker count is a starting point. A CPU-bound app usually wants workers close to (2 x vCPU) + 1; an I/O-bound app that mostly waits on the database can run more workers per vCPU than that rule suggests.
3. Push the repo and create the app
Connect the repository from the Ownkube dashboard (app.ownkube.io) or push directly with the okctl CLI:
okctl login
okctl apps create django-app --dockerfile Dockerfile
okctl deploy
Ownkube builds the image, allocates a hostname on *.ownkube.app, and issues TLS automatically. No load balancer or certificate to configure by hand.
4. Add a managed Postgres database
Create a database box from the dashboard: New > Database > Postgres. It provisions a single-instance, private (in-region) Postgres box starting at $4 a month plus storage at $0.15 per GB, with backups included. Multi-instance HA, public access, and point-in-time recovery are on the roadmap and not shipped yet, so plan around a single private instance for now, which is the normal shape for a small to mid-size Django app anyway.
Attach the connection string as an environment variable:
okctl env set DATABASE_URL="postgres://user:password@db-host:5432/django_production" --app django-app
dj_database_url.config() in settings.py reads it directly, so there is no further Django-side configuration.
5. Set your secrets
Django needs SECRET_KEY to sign sessions and cookies, and it will refuse to run with DEBUG=False and an empty ALLOWED_HOSTS. Set these as environment variables in the dashboard rather than committing them:
okctl env set SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(50))')" --app django-app
okctl env set DJANGO_SETTINGS_MODULE=myproject.settings --app django-app
okctl env set ALLOWED_HOSTS="django-app.ownkube.app" --app django-app
Never commit SECRET_KEY to the repository. Environment variables set through Ownkube are encrypted at rest and only injected into the running container.
6. Run migrations on release
Migrations need to run before Gunicorn starts serving requests against the new code, not after. Configure migrate as a release command so Ownkube runs it once per deploy, ahead of the rollout:
okctl apps update django-app --release-command "python manage.py migrate --noinput"
With a release command set, every okctl deploy runs the migration against the connected Postgres box first, and only rolls Gunicorn workers over to the new image once it succeeds. If the migration fails, the previous version keeps serving traffic, which is the zero-downtime rollout behavior working in your favor rather than a race between old code and a half-migrated schema.
7. Scale and watch it run
A fresh Django app usually starts fine on a small metered ceiling:
okctl deploy --cpu-limit 500m --memory-limit 1Gi
Turn on horizontal autoscaling if traffic is uneven, so Ownkube adds Gunicorn instances under load and scales back down when it is quiet:
okctl apps update django-app --autoscale-min 1 --autoscale-max 4 --autoscale-cpu-target 70
Metered billing fits a typical Django app well: it charges CPU and memory on actual per-minute usage, and a Django app sitting between requests barely registers on the CPU line. Logs and metrics for the app and the database box both live in the dashboard, so a slow migration or a worker that keeps restarting shows up in one place.
Moving to your own AWS later
If the app outgrows the shared Compute tier, or a client’s security review asks where the app runs, the same Dockerfile and Django app move to your own AWS account with no rewrite. Ownkube provisions the instance, wires up the same deploy flow, and brings the logs, metrics, and health checks with it. That portability is one of the things that separates Ownkube from a platform like Railway, which only runs your workload on its own infrastructure. Our Railway alternative comparison covers the tradeoffs in more depth.
FAQ
Does Ownkube support Django’s admin site and management commands?
Yes. The admin works like any other Django view once ALLOWED_HOSTS and static files are configured. One-off management commands can run as a scheduled job triggered manually from the dashboard’s Runs tab, rather than exec’ing into a running web container.
Do I need a Dockerfile, or can Ownkube detect Django automatically?
A Dockerfile gives you control over the Python version, system packages like libpq-dev, and the collectstatic build step, and is the most reliable path for Django. Simpler apps without native dependencies can also deploy straight from source using Ownkube’s automatic build.
Can I run Celery or another background worker alongside the web app?
Yes. Deploy the worker as a second Ownkube app pointed at the same Postgres connection string, and add a Valkey cache box from $2 a month if you need Redis as the broker, so the web app and worker scale independently.
What happens if my wallet balance runs out mid-month?
The app pauses and its data, including the Postgres box, is kept. Adding credit or the next monthly plan load resumes it. Unused wallet credit rolls over and never expires, so a quiet month is not credit lost.
Can I roll back a bad deploy?
Yes. Ownkube keeps prior image versions and supports zero-downtime rollback from the dashboard or okctl, so a bad migration or a broken settings change does not require a manual recovery.
Where Ownkube fits
Django wants a real database, a release step for migrations, and a place to serve static files without standing up a separate CDN, and Ownkube gives you all three without provisioning a cloud account first. Push the Dockerfile, attach a managed Postgres, set your secrets, and the release command handles the rest. Deploy your first app.