Run Django Management Commands in Docker and Kubernetes
Run Django manage.py commands against containerised deployments: docker compose exec versus run, kubectl exec versus one-off Jobs, loading the right settings and secrets, interactive shells, and keeping one-off commands out of web containers.
Problem statement
On a classic server you SSH in, activate the virtualenv, and run python manage.py shell. In containers it is less obvious: the code lives in an image, settings and secrets come from the container environment, and there may be several replicas. Running a command in the wrong way leads to errors such as missing environment variables, commands that run on a stale container, or a one-off data fix accidentally killing a production web container when it ends.
- Docker Compose, quick commands:
docker compose exec web python manage.py <cmd>(uses the running container). - Docker Compose, one-off tasks:
docker compose run --rm web python manage.py <cmd>(fresh container, same image and env). - Kubernetes, interactive:
kubectl exec -it deploy/web -- python manage.py shell. - Kubernetes, anything that changes data: a one-off Job from the same image and secrets, with logs and an exit status.
- Never run long or heavy commands inside a web pod serving traffic.
- Make sure
DJANGO_SETTINGS_MODULEand secrets are present; they come from the container environment.
Step-by-step solution
1. Docker Compose: exec versus run
docker compose exec web python manage.py showmigrations
docker compose run --rm web python manage.py migrate --noinput
docker compose run --rm web python manage.py shell
execstarts a process inside the runningwebcontainer. It is fast and shares that container's environment, but it competes with web traffic and dies if the container is replaced.run --rmstarts a new container from the service definition (same image, env files, networks, volumes), runs the command, and removes it. Use it for migrations, data fixes, and anything long.
run also starts dependencies like db unless you pass --no-deps.
2. Kubernetes: exec for quick looks
kubectl exec -it deploy/web -- python manage.py shell
kubectl exec deploy/web -- python manage.py check --deploy
deploy/web picks one pod of the Deployment. The pod already has the right image, environment, and secret mounts. Keep exec sessions for read-only inspection and short tasks.
3. Kubernetes: Jobs for real work
apiVersion: batch/v1
kind: Job
metadata: { name: backfill-slugs-20261002 }
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: Never
containers:
- name: task
image: ghcr.io/you/myapp:SHA
command: ["python", "manage.py", "backfill_slugs", "--batch-size", "500"]
envFrom: [{ secretRef: { name: django-env } }]
resources: { requests: { cpu: 250m, memory: 512Mi } }
kubectl apply -f backfill-job.yaml
kubectl logs -f job/backfill-slugs-20261002
kubectl wait --for=condition=complete job/backfill-slugs-20261002 --timeout=3600s
A Job has its own resources, logs, and a success or failure status, and does not disturb web pods. You can also clone the Deployment's pod spec quickly with kubectl create job --from=cronjob/<name> if you keep a suspended CronJob as a template.
4. Get the environment right
Commands fail with ImproperlyConfigured or connect to the wrong database when the environment differs from the web service. With Compose, run and exec both use the service's env_file and environment. A plain docker run myapp python manage.py ... does not; pass --env-file and the network explicitly, or use Compose. In Kubernetes, Jobs need the same envFrom and volume mounts as the Deployment.
5. Interactive shells safely
Production shells can change data instantly. Prefer read-only checks, wrap changes in transaction.atomic() so you can inspect before committing, and record what you ran. For repeated fixes, write a management command, review it, and run it as a Job.
Verification check: docker compose run --rm web python manage.py check --deploy and kubectl exec deploy/web -- python manage.py check --deploy both pass, proving the environment loads; a sample Job completes and its logs are retrievable with kubectl logs job/<name>.
Explanation
A container image contains code and dependencies; the runtime supplies configuration through environment variables, mounted secrets, and networks. Running a management command correctly means reproducing that runtime context. Compose and Kubernetes both can do this for you if you start the command through the same service or pod definition. Separating one-off work into its own container keeps it from competing with request handling and from being killed when a rolling update replaces the pod it was running in.
Edge cases / notes
- Distroless images have no shell; run
python manage.pydirectly as the command, withoutsh -c. - Non-root users in the image may lack write access to paths that commands assume, such as
/tmpin read-only root filesystems; mount anemptyDir. - TTY. Use
-itonly for interactive commands; scripts in CI should not allocate a TTY. - Celery beat images may share the same image; commands can run from any container built from it.
Next steps
- Migrations as Jobs in detail: Run Django Migrations as a Kubernetes Job.
- Creating the first admin user: Create a Django Superuser in Production Without a Shell.
FAQ
What is the difference between docker compose exec and run?
exec runs inside the running container; run --rm starts a fresh container from the same service definition.
How do I open a Django shell in Kubernetes?
kubectl exec -it deploy/web -- python manage.py shell.
Where should data migrations or backfills run?
In a one-off container or Kubernetes Job with the production image and environment.
Why does manage.py fail with missing settings in my container?
The command does not have the service's environment. Run it through Compose or the pod spec, not a bare docker run.