Docker Installation¶
Poweradmin provides official Docker images for easy deployment with FrankenPHP.
Docker Images¶
Official images are available at:
- Docker Hub:
poweradmin/poweradmin - GitHub Container Registry:
ghcr.io/poweradmin/poweradmin
Image Tags¶
The image tags published for production and development use are:
| Tag | Source branch | Description |
|---|---|---|
stable |
release/4.3.x |
Tracks the latest tagged release on the stable line - recommended for production. |
4.3.x |
release/4.3.x |
Stable line; updates on every commit to the branch (more frequent than stable). |
4.4 |
Version tags on master |
Newest release line (4.4.0) - fresh, still hardening; no dedicated branch yet. |
4.2.x |
release/4.2.x |
Maintenance line, winding down - security fixes only. |
latest |
master |
Tracks master, which carries the newest release line between patch releases. |
dev |
develop |
Development tip - not for production. |
lts |
release/3.x |
Long-term support for the 3.x series. |
v* |
Tagged release | Pin to a specific version (e.g. v4.3.4). |
Note: The
nexttag was removed when the release branch structure changed. Thestableand per-version (v*) tags are the safest choices for production; the branch tags (4.2.x,4.3.x) update on every push and may include unreleased fixes.
Quick Start¶
SQLite¶
docker run -d --name poweradmin -p 80:80 \
-e DB_TYPE=sqlite \
-e PA_CREATE_ADMIN=1 \
-v poweradmin-db:/db \
poweradmin/poweradmin:stable
Warning:
DB_TYPEis required. There is no default. A container started without it logsERROR: DB_TYPE environment variable is requiredand exits immediately.
Check logs for the generated admin password:
MySQL¶
docker run -d --name poweradmin -p 80:80 \
-e PA_CREATE_ADMIN=1 \
-e DB_TYPE=mysql \
-e DB_HOST=mysql-server \
-e DB_USER=poweradmin \
-e DB_PASS=your-password \
-e DB_NAME=poweradmin \
-e DNS_NS1=ns1.example.com \
-e DNS_NS2=ns2.example.com \
-e DNS_HOSTMASTER=hostmaster.example.com \
poweradmin/poweradmin:stable
PostgreSQL¶
docker run -d --name poweradmin -p 80:80 \
-e PA_CREATE_ADMIN=1 \
-e DB_TYPE=pgsql \
-e DB_HOST=postgres-server \
-e DB_USER=poweradmin \
-e DB_PASS=your-password \
-e DB_NAME=poweradmin \
-e DNS_NS1=ns1.example.com \
-e DNS_NS2=ns2.example.com \
-e DNS_HOSTMASTER=hostmaster.example.com \
poweradmin/poweradmin:stable
Note: The
DB_NAMEdatabase and theDB_USER(with privileges on it) must already exist - the container does not create them. With the officialmysql/postgresimages, setMYSQL_DATABASE/MYSQL_USER/MYSQL_PASSWORD(orPOSTGRES_DB/POSTGRES_USER/POSTGRES_PASSWORD) so they are provisioned on first start. Setting only the root password leaves the server with no Poweradmin database or user and fails withERROR 1045 Access denied.
On startup the container loads the Poweradmin schema into an empty DB_NAME database automatically; an already-populated database is left untouched. PowerDNS stores its own zones and records in a separate database that PowerDNS (not Poweradmin) creates and initializes. If you instead keep both in one shared database, set PA_INIT_PDNS_SCHEMA=true to have the container load the PowerDNS schema into an empty DB_NAME as well; it is off by default and skipped when PA_PDNS_DB_NAME is set.
Docker Compose¶
Basic Setup with MySQL¶
version: '3.8'
services:
poweradmin:
image: poweradmin/poweradmin:stable
ports:
- "80:80"
environment:
PA_CREATE_ADMIN: "true"
PA_ADMIN_PASSWORD: "change-me"
DB_TYPE: mysql
DB_HOST: mysql
DB_USER: poweradmin
DB_PASS: poweradmin-password
DB_NAME: poweradmin
DNS_NS1: ns1.example.com
DNS_NS2: ns2.example.com
DNS_HOSTMASTER: hostmaster.example.com
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root-password
MYSQL_DATABASE: poweradmin
MYSQL_USER: poweradmin
MYSQL_PASSWORD: poweradmin-password
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
With PowerDNS¶
version: '3.8'
services:
poweradmin:
image: poweradmin/poweradmin:stable
ports:
- "8080:80"
environment:
PA_CREATE_ADMIN: "true"
DB_TYPE: mysql
DB_HOST: mysql
DB_USER: poweradmin
DB_PASS: poweradmin-password
DB_NAME: poweradmin
PA_PDNS_DB_NAME: pdns
DNS_NS1: ns1.example.com
DNS_NS2: ns2.example.com
DNS_HOSTMASTER: hostmaster.example.com
PA_PDNS_API_URL: http://powerdns:8081
PA_PDNS_API_KEY: your-api-key
depends_on:
- mysql
- powerdns
powerdns:
image: powerdns/pdns-auth-49
ports:
- "53:53/udp"
- "53:53/tcp"
volumes:
- ./pdns.conf:/etc/powerdns/pdns.conf:ro
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root-password
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
mysql-data:
The official powerdns/pdns-auth-* images do not read PDNS_* environment
variables — that convention belongs to third-party images, and the official
image silently ignores them (see
PowerDNS/pdns#14951). The only
configuration variable it honors is PDNS_AUTH_API_KEY; everything else has to
come from a config file or command-line arguments. The example above mounts a
pdns.conf next to the compose file:
launch=gmysql
gmysql-host=mysql
gmysql-user=pdns
gmysql-password=pdns-password
gmysql-dbname=pdns
api=yes
api-key=your-api-key
webserver=yes
webserver-address=0.0.0.0
webserver-allow-from=0.0.0.0/0
The example also relies on ./init.sql to prepare MySQL on first startup: it
must create both databases and users, and load the PowerDNS schema into the
pdns database. Start with:
CREATE DATABASE poweradmin;
CREATE USER 'poweradmin'@'%' IDENTIFIED BY 'poweradmin-password';
GRANT ALL PRIVILEGES ON poweradmin.* TO 'poweradmin'@'%';
CREATE DATABASE pdns;
CREATE USER 'pdns'@'%' IDENTIFIED BY 'pdns-password';
GRANT ALL PRIVILEGES ON pdns.* TO 'pdns'@'%';
-- Poweradmin manages zones in the PowerDNS database (PA_PDNS_DB_NAME)
GRANT ALL PRIVILEGES ON pdns.* TO 'poweradmin'@'%';
USE pdns;
then append the official PowerDNS MySQL schema matching your PowerDNS version:
curl https://raw.githubusercontent.com/PowerDNS/pdns/rel/auth-5.1.x/modules/gmysqlbackend/schema.mysql.sql >> init.sql
Admin User Creation¶
The container can automatically create an admin user on first startup:
| Variable | Default | Description |
|---|---|---|
PA_CREATE_ADMIN |
false | Enable admin creation (true/1/yes) |
PA_ADMIN_USERNAME |
admin | Admin username |
PA_ADMIN_PASSWORD |
(auto) | Admin password (auto-generated if not set) |
PA_ADMIN_EMAIL |
admin@example.com | Admin email |
PA_ADMIN_FULLNAME |
Administrator | Admin display name |
If PA_ADMIN_PASSWORD is not set, a secure password is generated and logged:
Note: The admin user is only created if it doesn't already exist.
Key Environment Variables¶
This page covers the variables you need to stand a deployment up and secure it. It is deliberately a subset: the container accepts roughly 330 variables, and the complete reference, versioned alongside the code, is DOCKER.md in the source repository. Check there for anything not listed below, especially the interface, mail, DNS validation and per-provider OIDC and SAML settings.
Every variable here can also be supplied from a file by appending __FILE to its
name; see Docker Secrets.
Note: These variables only take effect when the container generates its own configuration. If
config/settings.php(orPA_CONFIG_PATH) exists and is not empty, that file is used and the variables below are ignored. See Configuration Priority.
Database¶
| Variable | Default | Description |
|---|---|---|
DB_TYPE |
(required) | Database type: sqlite, mysql, pgsql. The container exits at startup if this is unset |
DB_HOST |
- | Database hostname |
DB_PORT |
- | Database port (3306 for MySQL, 5432 for PostgreSQL) |
DB_USER |
- | Database username |
DB_PASS |
- | Database password |
DB_NAME |
- | Database name |
PA_PDNS_DB_NAME |
- | Separate PowerDNS database (MySQL only) |
PA_INIT_PDNS_SCHEMA |
false | Load PowerDNS schema into an empty DB_NAME on startup (MySQL/PostgreSQL; skipped when PA_PDNS_DB_NAME is set) |
DNS¶
| Variable | Default | Description |
|---|---|---|
DNS_NS1 |
ns1.example.com | Primary nameserver |
DNS_NS2 |
ns2.example.com | Secondary nameserver |
DNS_HOSTMASTER |
hostmaster.example.com | Hostmaster email |
PA_DNS_BACKEND |
sql | DNS data backend: sql (direct database) or api (PowerDNS REST API, v4.3.0+). See PowerDNS API |
Security¶
Session and password hashing¶
| Variable | Default | Description |
|---|---|---|
PA_SESSION_KEY |
(auto) | Session encryption key. Set this explicitly in production so sessions survive a container restart |
PA_PASSWORD_ENCRYPTION |
bcrypt | Password hashing: bcrypt, argon2i, argon2id. The legacy md5 and md5salt options were removed in 4.3.0 |
PA_PASSWORD_COST |
12 | Cost factor for bcrypt hashing |
PA_LOGIN_TOKEN_VALIDATION |
true | Enable CSRF token validation for login |
PA_GLOBAL_TOKEN_VALIDATION |
true | Enable CSRF token validation for all forms |
Password policy¶
| Variable | Default | Description |
|---|---|---|
PA_PASSWORD_RULES_ENABLED |
true | Enable password policy enforcement |
PA_PASSWORD_MIN_LENGTH |
6 | Minimum password length |
PA_PASSWORD_REQUIRE_UPPERCASE |
true | Require at least one uppercase letter |
PA_PASSWORD_REQUIRE_LOWERCASE |
true | Require at least one lowercase letter |
PA_PASSWORD_REQUIRE_NUMBERS |
true | Require at least one number |
PA_PASSWORD_REQUIRE_SPECIAL |
false | Require at least one special character |
See Password Policies for the full policy, including the allowed special-character set, which has no environment variable and must be set in settings.php.
Account lockout¶
| Variable | Default | Description |
|---|---|---|
PA_LOCKOUT_ENABLED |
false | Enable account lockout after failed logins |
PA_LOCKOUT_ATTEMPTS |
5 | Failed attempts before lockout |
PA_LOCKOUT_DURATION |
15 | Lockout duration in minutes |
PA_LOCKOUT_TRACK_IP |
true | Lock accounts based on IP address |
PA_LOCKOUT_CLEAR_ON_SUCCESS |
true | Clear failed attempts after a successful login |
The IP whitelist and blacklist have no environment variables; set them in settings.php. See Security Policies.
Multi-factor authentication¶
| Variable | Default | Description |
|---|---|---|
PA_MFA_ENABLED |
false | Enable multi-factor authentication |
PA_MFA_ENFORCED |
false | Enforce MFA for users holding user_enforce_mfa |
PA_MFA_APP_ENABLED |
true | Offer the authenticator app method. Ignored while email verification is unusable, so the last remaining method stays available |
PA_MFA_EMAIL_ENABLED |
true | Offer the email verification method |
PA_MFA_RECOVERY_CODES |
8 | Number of recovery codes generated |
PA_MFA_RECOVERY_CODE_LENGTH |
10 | Length of each recovery code |
PA_MFA_SKIP_FOR_EXTERNAL_AUTH |
false | Skip enforcement for LDAP, OIDC and SAML logins, trusting the identity provider (v4.5.0+) |
PA_MFA_MAX_VERIFY_ATTEMPTS |
5 | Failed second-factor guesses before the code is refused (v4.5.0+) |
PA_MFA_VERIFY_LOCKOUT_DURATION |
15 | Minutes to refuse further attempts once the limit is hit (v4.5.0+) |
Email-based MFA needs a working mail configuration; see Mail.
Password reset¶
| Variable | Default | Description |
|---|---|---|
PA_PASSWORD_RESET_ENABLED |
false | Enable password reset functionality |
PA_PASSWORD_RESET_TOKEN_LIFETIME |
3600 | Token validity in seconds |
PA_PASSWORD_RESET_RATE_LIMIT_ATTEMPTS |
5 | Maximum reset attempts per time window |
PA_PASSWORD_RESET_RATE_LIMIT_WINDOW |
3600 | Rate limit window in seconds |
PA_PASSWORD_RESET_MIN_TIME_BETWEEN |
60 | Minimum seconds between requests |
Username recovery¶
| Variable | Default | Description |
|---|---|---|
PA_USERNAME_RECOVERY_ENABLED |
false | Enable username recovery functionality |
PA_USERNAME_RECOVERY_RATE_LIMIT_ATTEMPTS |
5 | Maximum recovery attempts per time window |
PA_USERNAME_RECOVERY_RATE_LIMIT_WINDOW |
3600 | Rate limit window in seconds |
PA_USERNAME_RECOVERY_MIN_TIME_BETWEEN |
60 | Minimum seconds between requests |
reCAPTCHA¶
| Variable | Default | Description |
|---|---|---|
PA_RECAPTCHA_ENABLED |
false | Enable reCAPTCHA on login |
PA_RECAPTCHA_SITE_KEY |
(empty) | Site key (public) |
PA_RECAPTCHA_SECRET_KEY |
(empty) | Secret key (private) |
PA_RECAPTCHA_VERSION |
v3 | reCAPTCHA version: v2 or v3 |
PA_RECAPTCHA_V3_THRESHOLD |
0.5 | Score threshold for v3, from 0.0 to 1.0 |
Proxies¶
| Variable | Default | Description |
|---|---|---|
TRUSTED_PROXIES |
- | Comma-separated proxy CIDRs, or private_ranges, allowed to set forwarded-IP headers. See Reverse Proxy |
Account lockout (with PA_LOCKOUT_TRACK_IP) and the reset and recovery rate limits all throttle by client IP as well as by account. Behind a proxy they need TRUSTED_PROXIES set, or every request appears to come from the proxy's address.
PowerDNS API¶
| Variable | Default | Description |
|---|---|---|
PA_PDNS_API_URL |
- | PowerDNS API URL |
PA_PDNS_API_KEY |
- | PowerDNS API key |
Modules¶
| Variable | Default | Description |
|---|---|---|
PA_MODULE_CSV_EXPORT_ENABLED |
true | Enable CSV export module |
PA_MODULE_ZONE_IMPORT_EXPORT_ENABLED |
false | Enable zone import/export module |
PA_MODULE_ZONE_IMPORT_EXPORT_AUTO_TTL |
300 | Default TTL for imported records (seconds) |
PA_MODULE_ZONE_IMPORT_EXPORT_MAX_FILE_SIZE |
1048576 | Max upload file size in bytes |
PA_MODULE_WHOIS_ENABLED |
false | Enable WHOIS lookup module |
PA_MODULE_WHOIS_RESTRICT_TO_ADMIN |
true | Restrict WHOIS to administrators |
PA_MODULE_WHOIS_DEFAULT_SERVER |
(empty) | Default WHOIS server |
PA_MODULE_WHOIS_CUSTOM_SERVERS |
(empty) | Custom TLD-to-server mapping, e.g. za=whois.registry.net.za |
PA_MODULE_WHOIS_SOCKET_TIMEOUT |
10 | WHOIS socket timeout in seconds |
PA_MODULE_RDAP_ENABLED |
false | Enable RDAP lookup module |
PA_MODULE_RDAP_RESTRICT_TO_ADMIN |
true | Restrict RDAP to administrators |
PA_MODULE_RDAP_DEFAULT_SERVER |
(empty) | Default RDAP server |
PA_MODULE_RDAP_CUSTOM_SERVERS |
(empty) | Custom TLD-to-server mapping, e.g. za=https://rdap.example.com/ |
PA_MODULE_RDAP_REQUEST_TIMEOUT |
10 | RDAP request timeout in seconds |
PA_MODULE_EMAIL_PREVIEWS_ENABLED |
false | Enable the email template previews module |
PA_MODULE_EMAIL_PREVIEWS_RESTRICT_TO_ADMIN |
true | Restrict email previews to administrators |
PA_MODULE_DNS_WIZARDS_ENABLED |
false | Enable the DNS record wizards module |
PA_MODULE_DNS_WIZARDS_TYPES |
DMARC,SPF,DKIM,CAA,TLSA,SRV | Comma-separated list of DNS wizard types |
This table covers every module variable the container accepts. Two module settings have no
environment variable and must be set in settings.php: modules.secondary_zone_import.enabled
and the modules.dns_wizards.caa_providers list.
For detailed module configuration, see the Configuration section.
Authentication¶
| Variable | Default | Description |
|---|---|---|
PA_LDAP_ENABLED |
false | Enable LDAP authentication |
PA_LDAP_URI |
(empty) | LDAP server URI, e.g. ldaps://ldap.example.com:636 |
PA_LDAP_BASE_DN |
(empty) | Base DN where users are stored |
PA_LDAP_BIND_DN |
(empty) | Bind DN used to search the directory |
PA_LDAP_BIND_PASSWORD |
(empty) | Password for the bind DN |
PA_LDAP_SEARCH_FILTER |
(empty) | Additional LDAP search filter |
PA_LDAP_PROTOCOL_VERSION |
3 | LDAP protocol version |
PA_LDAP_USER_ATTRIBUTE |
uid | User attribute (uid for OpenLDAP, sAMAccountName for AD) |
PA_LDAP_SYNC_USER_INFO |
false | Sync fullname/email from LDAP on login (v4.5.0+) |
PA_LDAP_AUTO_PROVISION |
false | Create missing users on first LDAP login (v4.5.0+) |
PA_LDAP_PERMISSION_TEMPLATE_MAPPING |
- | LDAP group to permission template mapping, group:Template comma-separated (v4.5.0+) |
PA_LDAP_GROUP_MAPPING |
- | LDAP group to Poweradmin group mapping, group:PAGroup comma-separated (v4.5.0+) |
PA_OIDC_ENABLED |
false | Enable OpenID Connect |
PA_SAML_ENABLED |
false | Enable SAML authentication |
The LDAP rows above are enough for a working LDAP setup. OIDC and SAML are not - each provider
needs its own block of variables (PA_OIDC_AZURE_*, PA_SAML_OKTA_* and so on), and there are
137 authentication variables in total across LDAP, OIDC and SAML. They are listed in
DOCKER.md; the settings behind
them are explained in OIDC and SAML.
Custom CA Certificate¶
| Variable | Default | Description |
|---|---|---|
TRUSTED_CA_FILE |
- | Path to a custom CA certificate file inside the container |
Use this when connecting to services (OIDC, SAML, LDAP, PowerDNS API) that use self-signed or internal CA certificates:
docker run -d --name poweradmin -p 80:80 \
-e TRUSTED_CA_FILE=/certs/my-ca.crt \
-v /path/to/my-ca.crt:/certs/my-ca.crt:ro \
poweradmin/poweradmin
Interface and miscellaneous¶
| Variable | Default | Description |
|---|---|---|
PA_CONFIG_PATH |
/app/config/settings.php | Path to the configuration file. When this file exists and is not empty it replaces the generated one, and the other PA_* variables are ignored |
PA_APP_TITLE |
Poweradmin | Application title |
PA_DEFAULT_LANGUAGE |
en_EN | Default language |
PA_STYLE |
light | UI style: light or dark |
PA_SESSION_TIMEOUT |
1800 | Session timeout in seconds |
PA_TIMEZONE |
UTC | Default timezone |
PA_EDIT_CONFLICT_RESOLUTION |
last_writer_wins | Edit conflict resolution strategy |
PA_DNS_CUSTOM_TLDS |
- | Comma-separated custom TLDs (e.g., dn42,home) |
The interface has many more settings than the four above; see
UI Overview for what is available and
DOCKER.md for their variables.
Logging¶
| Variable | Default | Description |
|---|---|---|
PA_LOGGING_TYPE |
null | Logger type: null or native |
PA_LOGGING_LEVEL |
info | Log level: debug, info, notice, warning, error, critical, alert, emergency |
PA_LOGGING_DATABASE_ENABLED |
false | Log zone/record changes to database |
PA_LOGGING_SYSLOG_ENABLED |
false | Log auth attempts to syslog |
PA_LOGGING_SYSLOG_IDENTITY |
poweradmin | Syslog program identity |
PA_LOGGING_SYSLOG_FACILITY |
LOG_USER | Syslog facility (LOG_USER, LOG_LOCAL0-LOG_LOCAL7) |
Health endpoints (v4.5.0+)¶
| Variable | Default | Description |
|---|---|---|
PA_HEALTH_ENABLED |
false | Unauthenticated readiness endpoint at /api/health |
PA_HEALTH_PING_ENABLED |
false | Unauthenticated liveness endpoint at /ping |
PA_HEALTH_DB_TIMEOUT |
2 | Database connect timeout in seconds used by the health check |
PA_HEALTH_PDNS_TIMEOUT |
2 | PowerDNS API timeout in seconds used by the health check |
The image's HEALTHCHECK requests /, which succeeds even with a dead database. To have
container status track real readiness, enable PA_HEALTH_ENABLED and override the
healthcheck to request /api/health. See Health Checks.
Anything not listed on this page is in DOCKER.md, which tracks the code and is tagged with each release.
Volumes¶
| Path | Description |
|---|---|
/db |
SQLite database directory |
/app/config |
Configuration files (optional) |
Secrets¶
For production, use Docker secrets instead of environment variables for sensitive data. See Docker Secrets for details.
secrets:
db_password:
file: ./secrets/db_password.txt
services:
poweradmin:
environment:
DB_PASS__FILE: /run/secrets/db_password
secrets:
- db_password
Non-Root / Rootless Deployment¶
The Poweradmin image supports running as a non-root user for restricted Kubernetes clusters and OpenShift. No separate image variant is needed - the entrypoint adapts automatically.
Behavior¶
| Start mode | Port | Privileges | Use case |
|---|---|---|---|
| Root (default) | 80 | Drops to www-data after setup | Standard Docker, unrestricted K8s |
| Non-root | 8080 (auto) | No chown/chmod/CA install | Restricted K8s, OpenShift |
Docker (Non-Root)¶
Kubernetes (Restricted)¶
spec:
securityContext:
runAsNonRoot: true
runAsUser: 82
runAsGroup: 82
fsGroup: 82
containers:
- name: poweradmin
image: poweradmin/poweradmin:stable
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
env:
- name: DB_TYPE
value: sqlite
fsGroup: 82 ensures volumes are group-writable for www-data (GID 82).
Custom Port¶
Override the auto-detected port with SERVER_PORT:
Limitations (Non-Root)¶
TRUSTED_CA_FILErequires root - a warning is logged if set in non-root mode- Volumes must be pre-configured as writable (use
fsGroupor host permissions)
Troubleshooting¶
Check container logs¶
Access container shell¶
Database connection issues¶
- Verify database is accessible from container
- Check credentials are correct
- Ensure database exists and user has permissions