On this page 10 sections
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 a way to run migrations before code that needs them takes traffic. Ownkube covers all four without a cloud account or hand-written YAML.
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 as a job.
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 one-off job, so it runs once per release instead of on every container start.
1. Prepare settings for production
Most of this is standard Django. Point the database and static file settings at environment variables instead of hardcoded values:
import osimport dj_database_url
DEBUG = FalseSECRET_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 lets a small app serve its own CSS and JS without a separate static file host or CDN.
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 runtimeRUN SECRET_KEY=build-only DATABASE_URL=sqlite:///build.db \ python manage.py collectstatic --noinput
ENV PYTHONUNBUFFERED=1EXPOSE 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) to deploy on every push, or deploy the current directory with the okctl CLI:
okctl loginokctl up --name django-app --port 8000Ownkube builds the image from your Dockerfile, allocates a hostname on *.ownkube.app, and issues TLS automatically, so there is no load balancer or certificate to configure by hand. Running okctl up again ships the next version.
4. Add a managed Postgres database
Create a database box from the dashboard: New > Database > Postgres. It provisions a private (in-region) Postgres box starting at $4 a month plus storage at $0.25 per GB, with backups and point-in-time recovery available on Personal and Team.
The database runs as a single private instance by default, which is the normal shape for a small to mid-size Django app; opt-in public access and, on Team, high availability are per-database options when you need them.
Link the database to the app. Ownkube writes its connection string into the app as a secret DATABASE_URL variable and redeploys it:
okctl deploy link django-app <database-id>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 and keep them out of the repository:
okctl vars set django-app SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(50))')" --secretokctl vars set django-app DJANGO_SETTINGS_MODULE=myproject.settings ALLOWED_HOSTS="django-app.ownkube.app"Environment variables set through Ownkube are encrypted at rest and only injected into the running container.
6. Run migrations as a job
Run migrate as a one-off job instead of on every container start. Create a Job deployment from the same repository, set its command to python manage.py migrate --noinput, and leave the schedule empty. Variables are set per deployment, so give the job the same SECRET_KEY and DJANGO_SETTINGS_MODULE and link the same database to it.
Then trigger a run whenever a release includes new migrations:
okctl deploy link django-migrate <database-id>okctl deploy job-runs trigger django-migrateokctl deploy job-runs list django-migrateThe run’s output is on the job’s Logs tab. The web app and the job deploy independently, so keep each migration safe for the version already running: add columns and tables in one release, and drop old ones in a later one.
7. Scale and watch it run
A fresh Django app usually starts fine on a small metered ceiling, for example a 500m CPU limit and 1 GiB of memory, set on the sliders under Settings → Resources.
Turn on horizontal autoscaling if traffic is uneven, so Ownkube adds Gunicorn instances under load and scales back down when it is quiet. On Ownkube Compute you turn it on through your coding agent (the update_deployment MCP tool) or the API, with a minimum and maximum replica count and a CPU target such as 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 a client’s security review asks for the app to run in a named AWS account, your own or theirs, the same Dockerfile and Django app move there with no rewrite. Ownkube provisions the instance, wires up the same deploy flow, and brings the logs, metrics, and health checks with it.
Railway, by contrast, only runs your workload on its own infrastructure. Our Railway alternatives guide compares Railway with the other options.
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 run as a one-off job, started from the job’s Runs tab or with okctl deploy job-runs trigger, instead of inside 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 credit from a quiet month carries into the next.
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 broken release or settings change does not require a manual recovery. A rollback restores the previous image, and a migration that already ran stays applied, which is one more reason to keep migrations additive.
For the always-current version of this walkthrough, see the Deploy Django guide in the docs.
Where Ownkube fits
Django wants a real database, a way to run 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 run migrations as a job. See Ownkube pricing for the per-minute wallet model, from $5 a month. Deploy my first app.