Admin password setup
TruePPM ships a create_admin Django management command that bootstraps a superuser on first run. The default writes a securely-generated password to a file with 0o600 permissions so the credential never appears in container logs or log aggregators (CloudWatch, Datadog, etc.).
First-run setup
Section titled “First-run setup”The api container runs create_admin automatically on startup (both in docker compose and in the Helm chart). On first run it:
- Checks whether any superuser already exists. If yes, it exits silently — re-deploys never overwrite a production password.
- Generates a 16-character URL-safe random password (or honours
DJANGO_SUPERUSER_PASSWORDif set). - Creates the superuser with email
admin@trueppm.dev(orDJANGO_SUPERUSER_EMAILif set), usernameadmin(or the local part of the email). - Writes the password to
/tmp/trueppm_admin_passwordwith mode0o600.
Retrieve the first-run password
Section titled “Retrieve the first-run password”docker compose
Section titled “docker compose”docker compose exec api cat /tmp/trueppm_admin_passwordThen delete the file — the command writes it once for retrieval, but a long-lived file on a shared /tmp is bad operational hygiene.
docker compose exec api rm /tmp/trueppm_admin_passwordKubernetes / Helm
Section titled “Kubernetes / Helm”The default values write to /tmp/trueppm_admin_password inside the pod. Override via the env var:
api: env: TRUEPPM_ADMIN_PASSWORD_FILE: /var/run/secrets/trueppm/admin_passwordMount that path as an emptyDir (lost when the pod restarts — fine for first-run-only retrieval) or use a Secret volume. Then:
kubectl exec -it <api-pod> -- cat /var/run/secrets/trueppm/admin_passwordSet a known password at startup
Section titled “Set a known password at startup”Pass DJANGO_SUPERUSER_PASSWORD to the api container:
services: api: environment: DJANGO_SUPERUSER_EMAIL: admin@example.com DJANGO_SUPERUSER_USERNAME: admin DJANGO_SUPERUSER_PASSWORD: <your password>This is convenient for local development but do not use this pattern in production — env vars in compose files are versioned and visible in process listings.
Rotate the password (after first run)
Section titled “Rotate the password (after first run)”The create_admin command is intentionally a no-op when a superuser already exists, so you cannot use it to rotate. Use Django’s standard changepassword command instead:
docker compose
Section titled “docker compose”docker compose exec api python manage.py changepassword adminYou’ll be prompted for the new password twice, interactively.
Kubernetes
Section titled “Kubernetes”kubectl exec -it <api-pod> -- python manage.py changepassword adminProgrammatic rotation
Section titled “Programmatic rotation”If you need to rotate non-interactively (e.g. from a CI job or rotation script):
docker compose exec -T api python manage.py shell <<'EOF'from django.contrib.auth import get_user_modelUser = get_user_model()admin = User.objects.get(username='admin')admin.set_password('<new password>')admin.save()EOFPass the new password via stdin/env from a secret manager — never inline.
End-user password reset
Section titled “End-user password reset”End users reset their password via the standard Django auth flow:
POST /api/v1/auth/password/reset/with{"email": "user@example.com"}triggers a reset email- The email contains a link with a one-time token
POST /api/v1/auth/password/reset/confirm/with{"uid", "token", "new_password1", "new_password2"}completes the reset
The reset email template lives at packages/api/src/trueppm_api/templates/registration/password_reset_email.html. SMTP is configured via standard Django EMAIL_* environment variables.
Forgot the admin password (no email configured)
Section titled “Forgot the admin password (no email configured)”If you have lost the admin password and SMTP is not configured (common in self-hosted dev), shell into the container and reset directly:
docker compose exec api python manage.py changepassword adminIf you cannot recall the username, list superusers:
docker compose exec -T api python manage.py shell <<'EOF'from django.contrib.auth import get_user_modelfor u in get_user_model().objects.filter(is_superuser=True): print(u.username, u.email)EOFSecurity notes
Section titled “Security notes”- The default password file path (
/tmp/trueppm_admin_password) usesO_NOFOLLOWto defeat symlink attacks on the world-writable/tmpdirectory (Linux and macOS). - The file is created with mode
0o600atomically viaos.open(..., 0o600)— there is no TOCTOU window between create and chmod. - If the file write fails, the password falls back to management-command stdout only — it is never sent through
logger.warning()or higher because log aggregators forward those lines downstream. - For production deployments, override
TRUEPPM_ADMIN_PASSWORD_FILEto a non-world-writable location (anemptyDirvolume, aSecretmount, or a host-bind to a 0700 directory).
Related
Section titled “Related”- Installation — how the api container is started
- Configuration — environment variables reference
- Security — broader hardening guide