Create a Django Superuser in Production Without a Shell
Create the first Django admin user in production non-interactively: createsuperuser --noinput with DJANGO_SUPERUSER_ environment variables, one-off containers and Jobs, PaaS consoles, an idempotent management command, and securing the account afterwards.
Problem statement
The site is deployed and the admin login page works, but there is no user to log in with. python manage.py createsuperuser asks for input interactively, which is awkward or impossible in CI pipelines, Docker builds, PaaS deploy hooks, and Kubernetes. Common workarounds (hard-coding a password in a data migration, or creating users with a default password in an entrypoint) leave weak credentials in production. Django supports creating a superuser non-interactively, and there are clean patterns for doing it once.
- Set
DJANGO_SUPERUSER_USERNAME,DJANGO_SUPERUSER_EMAIL, andDJANGO_SUPERUSER_PASSWORDfor a single run. - Run
python manage.py createsuperuser --noinputin a one-off container, Job, or PaaS console. - Do not put this in the image build or every container start; run it once.
- For repeatable automation, write a small management command that creates the user only if it does not exist.
- Log in, change the password, and enable two-factor authentication; remove the temporary variables.
Step-by-step solution
1. Use createsuperuser --noinput
Since Django 3.0, createsuperuser --noinput reads the password from DJANGO_SUPERUSER_PASSWORD (and other required fields from DJANGO_SUPERUSER_<FIELD>):
DJANGO_SUPERUSER_USERNAME=admin \
DJANGO_SUPERUSER_EMAIL=ops@example.com \
DJANGO_SUPERUSER_PASSWORD="$(openssl rand -base64 24)" \
python manage.py createsuperuser --noinput
Generate the password, store it in your password manager, and pass it only for this command. With a custom user model that uses email as USERNAME_FIELD, set DJANGO_SUPERUSER_EMAIL and any other REQUIRED_FIELDS.
2. Run it once in your platform
- Docker Compose:
docker compose run --rm -e DJANGO_SUPERUSER_PASSWORD=... web python manage.py createsuperuser --noinput --username admin --email ops@example.com. - Kubernetes: a one-off Job with the password from a temporary Secret, deleted afterwards.
- Heroku:
heroku run python manage.py createsuperuser(interactive one-off dyno works). - Render, Railway, Fly.io: use the platform's shell or console (
fly ssh console -C "python manage.py createsuperuser").
3. An idempotent command for automation
For preview environments or infrastructure-as-code flows that create environments repeatedly, a command that does nothing if the user exists is safer than createsuperuser, which fails on duplicates:
# accounts/management/commands/ensure_superuser.py
import os
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Create a superuser from environment variables if it does not exist."
def handle(self, *args, **opts):
User = get_user_model()
email = os.environ["DJANGO_SUPERUSER_EMAIL"]
if User.objects.filter(email=email).exists():
self.stdout.write("superuser exists, nothing to do")
return
User.objects.create_superuser(
username=os.environ.get("DJANGO_SUPERUSER_USERNAME", email),
email=email,
password=os.environ["DJANGO_SUPERUSER_PASSWORD"],
)
self.stdout.write(self.style.SUCCESS(f"created superuser {email}"))
Run it from the deploy pipeline with the password from the secrets store.
4. Secure the account immediately
- Log in and change the password if it passed through any shared system.
- Enable two-factor authentication for admin users.
- Remove
DJANGO_SUPERUSER_PASSWORDfrom the environment and delete temporary Secrets. - Create named accounts for each person instead of sharing
admin, and give staff users only the permissions they need.
Verification check: log in to /admin/ with the new account, confirm User.objects.filter(is_superuser=True).count() is what you expect, and confirm the temporary password variable no longer exists in the running environment.
Explanation
createsuperuser prompts for values by default to avoid passwords in shell history and process lists. The --noinput mode reads required fields from command-line options and the password from an environment variable, which suits automation while keeping the password off the command line. Because the command fails if the user already exists, putting it in container startup creates errors or, worse, encourages default passwords. Running it once, or using an idempotent command with per-environment secrets, keeps bootstrap credentials short-lived.
Edge cases / notes
- Custom user models must define
create_superusercorrectly and listREQUIRED_FIELDS; the env variables follow those field names. - SSO-only admin setups may not need local superusers; grant
is_staffand permissions to SSO users instead. - Password validators apply in interactive mode;
--noinputskips some validation, so generate strong passwords yourself. - Recovery. If the only superuser is locked out, run
changepasswordor create another superuser the same way.
Next steps
- Protect the admin with the Django Admin Security Checklist.
- Add a second factor with Add Two-Factor Authentication to the Django Admin.
FAQ
How do I run createsuperuser without prompts?
Use createsuperuser --noinput with DJANGO_SUPERUSER_PASSWORD and the username or email fields set.
Can I create the superuser in the Dockerfile?
No. The database is not available at build time, and the password would end up in the image.
Should a data migration create the admin user?
No. It would put credentials in version control and in every environment.
What if the superuser already exists?
createsuperuser fails. Use an idempotent command that checks first.