Skip to content

Admin password setup

Every fresh TruePPM install needs one administrator account to sign in with before anyone else can. This page covers getting that first password (during initial setup), finding it again if you missed it, and resetting any user’s password later — including your own, if you lose it.

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 (readable only by its owner) so the credential never appears in container logs or log aggregators (CloudWatch, Datadog, etc.).

The api container runs create_admin automatically on startup (both in docker compose and in the Helm chart). On first run it:

  1. Checks whether any superuser already exists. If yes, it exits silently — re-deploys never overwrite a production password.

  2. Generates a URL-safe random password (16 bytes of entropy, about 22 characters), or honors DJANGO_SUPERUSER_PASSWORD if set.

  3. Creates the superuser with email admin@example.com (or DJANGO_SUPERUSER_EMAIL if set), username admin (or the local part of the email). The default is an RFC 2606 reserved domain and cannot receive mail, so set DJANGO_SUPERUSER_EMAIL before the first deploy or password reset for this account will not work.

    If a non-superuser account already holds that address, it is promoted to superuser and its password is reset. The command prints a warning when it does this. Point DJANGO_SUPERUSER_EMAIL at an unused address if that is not what you want.

  4. Writes the password to /tmp/trueppm_admin_password with mode 0o600.

Terminal window
docker compose exec api cat /tmp/trueppm_admin_password

Then delete the file — the command writes it once for retrieval, but a long-lived file on a shared /tmp is bad operational hygiene.

Terminal window
docker compose exec api rm /tmp/trueppm_admin_password

The chart writes the one-time password to /run/trueppm/admin_password, an emptyDir mount the chart provides. The path is controlled by the admin.passwordFile value, which the chart renders into the TRUEPPM_ADMIN_PASSWORD_FILE env var:

admin:
passwordFile: /run/trueppm/admin_password # chart default

Retrieve it by checking each API pod — the file exists on exactly one of them (see the note on replicas below):

Terminal window
for pod in $(kubectl get pods -n <namespace> -o name \
-l app.kubernetes.io/instance=<release>,app.kubernetes.io/component=api); do
kubectl exec -n <namespace> "$pod" -c api -- cat /run/trueppm/admin_password 2>/dev/null \
&& echo "(from $pod)"
done

At one replica this is the same as kubectl exec deployment/<release>-trueppm-api; at two or more, deployment/… picks an arbitrary pod and can miss the file.

Get the Deployment name right. The chart’s fullname helper yields <release>-trueppm-api, not <release>-api — unless the release name already contains “trueppm”, in which case the duplicate segment is dropped and a release called trueppm gives plain trueppm-api. When in doubt:

Terminal window
kubectl get deploy -n <namespace> -l app.kubernetes.io/component=api

Pass DJANGO_SUPERUSER_PASSWORD to the api container:

docker-compose.override.yml
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.

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:

Terminal window
docker compose exec api python manage.py changepassword admin

You’ll be prompted for the new password twice, interactively.

Terminal window
kubectl exec -it <api-pod> -- python manage.py changepassword admin

If you need to rotate non-interactively (e.g. from a CI job or rotation script):

Terminal window
docker compose exec -T api python manage.py shell <<'EOF'
from django.contrib.auth import get_user_model
User = get_user_model()
admin = User.objects.get(username='admin')
admin.set_password('<new password>')
admin.save()
EOF

Pass the new password via stdin/env from a secret manager — never inline.

The community edition includes a self-service password reset flow. A user who has forgotten their password clicks Forgot password? on the sign-in page and follows the flow:

  1. /forgot-password — the user enters their work email and requests a reset link. The response is the same whether or not the address has an account, so the page never reveals which emails are registered (no account enumeration).
  2. Reset email — if the address belongs to an account, TruePPM emails a single-use link that is valid for 30 minutes.
  3. /reset-password/confirm — the link opens a page where the user sets a new password (minimum 10 characters, at least one number or symbol, and different from their current password). The link’s single-use credential is carried in the URL fragment (after the #), which browsers never transmit to any server — so it stays out of your reverse-proxy access logs and out of the Referer header sent to any site the user navigates to next.
  4. All other sessions are signed out — a successful reset revokes every other active session for that account, so a leaked-but-forgotten session on another device cannot outlive the reset.

The reset endpoints are rate limited to blunt abuse (email-bombing a victim and probing for registered addresses).

  • Outbound email must be configured. The reset link can only be delivered once the EMAIL_* transport is set (see Configuration). Until then the request still returns success — it never leaks that email is unconfigured — but no message is delivered, so use the changepassword fallback below.
  • SSO accounts. A user who signs in through single sign-on has no local password to reset; the request screen shows a hint that SSO sign-in is unaffected. Their password is managed by their identity provider.

When email is not configured, or a user cannot complete the flow, an administrator resets a password directly with the same changepassword command used for the admin account:

Terminal window
docker compose exec api python manage.py changepassword <username>

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:

Terminal window
docker compose exec api python manage.py changepassword admin

If you cannot recall the username, list superusers:

Terminal window
docker compose exec -T api python manage.py shell <<'EOF'
from django.contrib.auth import get_user_model
for u in get_user_model().objects.filter(is_superuser=True):
print(u.username, u.email)
EOF
  • The default password file path (/tmp/trueppm_admin_password) uses O_NOFOLLOW to defeat symlink attacks on the world-writable /tmp directory (Linux and macOS).
  • The file is created with mode 0o600 atomically via os.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_FILE to a non-world-writable location (an emptyDir volume, a Secret mount, or a host-bind to a 0700 directory).