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 release/4.3.x. |
4.4.x |
release/4.4.x |
LTS line until December 2027; updates on every commit to the branch. |
4.3.x |
release/4.3.x |
Maintenance line, end of life three months after the 4.5.0 release; updates on every commit to the branch. |
4.2.x |
release/4.2.x |
Maintenance line, end of life three months after the 4.5.0 release; updates on every commit to the branch. |
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. |
4.4.1, 4.3.5 |
Tagged release | Pin to a specific version, for example 4.4.1 on the LTS line - recommended for production. Image tags carry no v prefix: the v4.4.1 git tag is published as 4.4.1 (plus 4.4 and 4). |
Note: The
nexttag was removed when the release branch structure changed. The per-version tags (4.4.1) are the safest choices for production; the branch tags (4.2.x,4.3.x,4.4.x) update on every push and may include unreleased fixes. For moving an existing container to a newer release, see Upgrading a Docker deployment.
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) |
DB_WAIT_TIMEOUT |
30 | Seconds to wait for the MySQL/PostgreSQL server before schema initialization is skipped |
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 |
PA_PASSWORD_SPECIAL_CHARACTERS |
!@#$%^&*()+-=[]{}\|;:,.<>? |
Characters that count as special for the rule above (v4.6.0+) |
See Password Policies for the full policy. Before 4.6.0 the special-character set 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 |
PA_LOCKOUT_WHITELIST_IPS |
- | Comma-separated IPs, CIDRs or wildcards that are never locked out; wins over the blacklist (v4.6.0+) |
PA_LOCKOUT_BLACKLIST_IPS |
- | Comma-separated IPs, CIDRs or wildcards that are always blocked (v4.6.0+) |
Before 4.6.0 the IP whitelist and blacklist have no environment variables and must be set 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 |
PA_APPLICATION_URL |
(empty) | Public base URL. Required when OIDC or SAML is enabled, and for password reset and emailed links |
PA_BASE_URL |
(empty) | Legacy base URL. Accepted as a URL source for SAML only |
From 4.2.6, 4.3.5, 4.4.1 and 4.5.0 the container refuses to start when OIDC or SAML is enabled
without a URL source, because the redirect and SP URLs can no longer be derived from the request.
OIDC accepts only PA_APPLICATION_URL; SAML also accepts PA_BASE_URL, or all three of
PA_SAML_SP_ENTITY_ID, PA_SAML_SP_ACS_URL and PA_SAML_SP_SLS_URL. The check runs only when the
entrypoint generates the configuration - if you mount your own settings.php (see above), these
variables are ignored and the check is skipped.
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 (web server authentication is listed below). They are listed in
DOCKER.md; the settings behind
them are explained in OIDC and SAML.
Web server authentication (REMOTE_USER)¶
Signs users in as the user name an authenticating web server or reverse proxy supplies. Added in 4.6.0. See Web Server Authentication for how it works and the security requirements.
| Variable | Default | Description |
|---|---|---|
PA_REMOTE_USER_ENABLED |
false | Enable web server authentication |
PA_REMOTE_USER_SERVER_VARIABLE |
REMOTE_USER | Server variable that holds the user name |
PA_REMOTE_USER_HEADER |
(empty) | Read the user name from this request header instead (header mode), e.g. Remote-User |
PA_REMOTE_USER_TRUSTED_PROXIES |
(empty) | Comma-separated proxy IPs or CIDRs allowed to send the header. Empty means the header is ignored. Required in header mode |
PA_REMOTE_USER_STRIP_REALM |
false | Turn user@REALM and DOMAIN\user into user |
PA_REMOTE_USER_EMAIL_ATTRIBUTE |
(empty) | Variable or header holding the email address, e.g. Remote-Email |
PA_REMOTE_USER_NAME_ATTRIBUTE |
(empty) | Variable or header holding the full name, e.g. Remote-Name |
PA_REMOTE_USER_GROUPS_ATTRIBUTE |
(empty) | Variable or header holding the groups, e.g. Remote-Groups |
PA_REMOTE_USER_GROUPS_SEPARATOR |
, |
Separator between groups in the groups attribute |
PA_REMOTE_USER_LOGOUT_URL |
(empty) | Where to send users after logout, to end the proxy's own session |
PA_REMOTE_USER_HIDE_LOGIN_FORM |
false | Hide the password form while the web server signs users in |
PA_REMOTE_USER_AUTO_PROVISION |
true | Create an account on first sign-in |
PA_REMOTE_USER_ALLOW_SUPERUSER_PROVISIONING |
false | Let group mappings grant the superuser flag |
PA_REMOTE_USER_SYNC_USER_INFO |
true | Update name and email from the attributes on each sign-in |
PA_REMOTE_USER_DEFAULT_PERMISSION_TEMPLATE |
Guest | Permission template for new accounts when no mapping matches |
PA_REMOTE_USER_PERMISSION_TEMPLATE_MAPPING |
(empty) | Map groups to permission templates (format: group1=Template1,group2=Template2) |
PA_REMOTE_USER_GROUP_MAPPING |
(empty) | Map groups to Poweradmin groups (format: group1=PaGroup1,group2=PaGroup2) |
Notes:
- The image's Caddy server does not authenticate users itself. In Docker this is normally used in header mode, behind an authenticating proxy (Authelia, oauth2-proxy, Authentik outpost) that sets
Remote-User. PA_REMOTE_USER_TRUSTED_PROXIESmust list the proxy's address as Poweradmin sees it. It is not taken fromTRUSTED_PROXIES. With a header set but no trusted proxies the header is ignored, and the container logs a warning at startup.- The container must not be reachable except through that proxy, and the proxy must overwrite or strip any
Remote-Userheader the client sends.
services:
poweradmin:
image: poweradmin/poweradmin:latest
environment:
PA_REMOTE_USER_ENABLED: "true"
PA_REMOTE_USER_HEADER: Remote-User
PA_REMOTE_USER_TRUSTED_PROXIES: 172.20.0.5
PA_REMOTE_USER_EMAIL_ATTRIBUTE: Remote-Email
PA_REMOTE_USER_NAME_ATTRIBUTE: Remote-Name
PA_REMOTE_USER_GROUPS_ATTRIBUTE: Remote-Groups
PA_REMOTE_USER_GROUP_MAPPING: dns-admins=Administrators
PA_REMOTE_USER_PERMISSION_TEMPLATE_MAPPING: dns-admins=Administrator
PA_REMOTE_USER_LOGOUT_URL: https://auth.example.com/logout
networks:
app:
ipv4_address: 172.20.0.10 # reachable only from the proxy network, no published ports
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 /ping when PA_HEALTH_PING_ENABLED=true (so a
headless container with PA_WEB_ENABLED=false still reports healthy) and / otherwise.
Both succeed 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.
Change approval (v4.6.0+)¶
| Variable | Default | Description |
|---|---|---|
PA_APPROVAL_ENABLED |
false | Route zone changes of request-only users through review |
PA_APPROVAL_REQUIRE_REVIEW_FOR_ALL |
false | Every zone change becomes a change request, even for editors and admins |
PA_APPROVAL_ALLOW_SELF_APPROVAL |
true | Whether a requester may approve their own change request |
PA_NOTIFICATION_CHANGE_REQUEST |
false | Mail reviewers on new change requests and requesters on decisions; needs PA_MAIL_ENABLED |
PA_NOTIFICATION_CHANGE_REQUEST_SOA_CONTACT |
false | Also mail the zone's SOA contact about new change requests |
See Change Requests.
API and CORS¶
| Variable | Default | Description |
|---|---|---|
CORS_ALLOW_ORIGIN |
* |
Access-Control-Allow-Origin sent on /api/* responses and preflight requests. The web interface never sends CORS headers |
PHP settings¶
PHP runs on php.ini-production with these overrides:
| Setting | Value |
|---|---|
memory_limit |
256M |
upload_max_filesize / post_max_size |
16M / 20M |
expose_php |
Off |
opcache.validate_timestamps |
0 (code is cached until the container restarts) |
To change any of them, mount an ini file into /usr/local/etc/php/conf.d/. The image's
own overrides load first, so any file you mount there wins.
PHP warnings go to the container log (docker logs), not into the page (v4.4.2+).
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) |
Customizing the interface¶
Mount only the files you customize, never the whole /app/templates directory or /app.
Templates change together with the code that renders them, so a templates directory from
another Poweradmin version keeps serving outdated pages after an upgrade. That breaks forms in
ways that are hard to trace, for example saving records failing with a PHP max_input_vars
warning.
Custom CSS files are not shipped with the image, so mount them one by one:
docker run -d --name poweradmin -p 80:80 \
-v ./custom_light.css:/app/templates/default/style/custom_light.css:ro \
-v ./custom_dark.css:/app/templates/default/style/custom_dark.css:ro \
poweradmin/poweradmin
Use modern instead of default in the path when PA_THEME is modern. Do not mount the
whole style/ directory: it hides the theme's own light.css and dark.css.
A custom header or footer (/app/templates/<theme>/custom/header.html and footer.html)
starts as a copy of the theme's own file, so compare it with the new version after each
upgrade.
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