This project was built with AI assistance — see AI_DISCLAIMER.md for details.
Dockerised Znuny 7.2 based on Debian 12 (Bookworm).
Images are published to the GitHub Container Registry on every version tag push.
ghcr.io/ckbaker10/znuny:<version>
# 1. Copy and edit the environment file
cp .env.example .env
# Edit .env — at minimum change the passwords
# 2. Create volume directories
mkdir -p volumes/{config,article,backups,addons,mysql}
# 3. Start the stack
docker compose up -d
# 4. Open Znuny in your browser
open http://localhost:8080/znunyDefault admin credentials: root@localhost / value of ZNUNY_ROOT_PASSWORD (default: changeme).
znuny-docker/
├── .github/workflows/build-push.yml # CI/CD — builds & pushes images on v* tags
├── znuny/
│ ├── Dockerfile # Main Znuny image (Debian 12)
│ ├── entrypoint.sh # Container startup logic
│ ├── functions.sh # Helper functions
│ ├── util_functions.sh # Logging utilities
│ ├── znuny_backup.sh # Automated backup script (called by cron)
│ └── etc/supervisord/znuny.conf # Supervisord program definitions
├── docker-compose.yml # Main stack
├── .env.example # All supported variables with descriptions
└── Planning.md # Architecture decisions and implementation plan
Set the ZNUNY_INSTALL environment variable to choose the startup mode.
ZNUNY_INSTALL |
Behaviour |
|---|---|
no (default) |
Auto-configure, initialise the database, start all services |
yes |
Launch the web installer — daemon does not start until mode is switched to no |
restore |
Restore the backup specified by ZNUNY_BACKUP_DATE |
Use this mode when you want full control over the initial configuration via the browser UI.
# 1. Set installer mode in .env
ZNUNY_INSTALL=yes
# 2. Start the stack
docker compose up -d
# 3. Open the installer in your browser and complete all steps
# https://<hostname>/znuny/installer.pl
# 4. After the installer finishes, switch to normal mode
# Edit .env:
ZNUNY_INSTALL=no
# 5. Restart the container — daemon and cron will now start
docker compose up -dNote: The Znuny daemon does not start while
ZNUNY_INSTALL=yesbecauseConfig.pmhas no database configuration until the installer writes it. Always restart withZNUNY_INSTALL=noafter completing the web installer.
See .env.example for the full reference with descriptions and defaults.
| Variable | Description |
|---|---|
ZNUNY_ROOT_PASSWORD |
Znuny admin (root@localhost) password |
ZNUNY_HOSTNAME |
Fully-qualified hostname shown in Znuny (e.g. znuny.example.com) |
ZNUNY_DB_PASSWORD |
Znuny application database password |
MYSQL_ROOT_PASSWORD |
MariaDB root password |
GITHUB_REPOSITORY_OWNER |
Your GitHub username/org (used in image tags) |
| Host path | Container path | Purpose |
|---|---|---|
./volumes/config |
/opt/znuny/Kernel |
Znuny configuration (Config.pm etc.) |
./volumes/article |
/opt/znuny/var/article |
Article attachments (if using ArticleStorageFS) |
./volumes/backups |
/var/znuny/backups |
Automated backup output |
./volumes/addons |
/opt/znuny/addons |
Drop .opm addon files here for auto-install |
./volumes/mysql |
/var/lib/mysql |
MariaDB data directory |
Backups run automatically via cron at the schedule defined by ZNUNY_BACKUP_TIME
(default: 0 4 * * * — daily at 04:00). Backups are written to ./volumes/backups
and files older than ZNUNY_BACKUP_ROTATION days (default: 30) are pruned automatically.
To disable backups:
ZNUNY_BACKUP_TIME=disable# 1. Place the backup archive in ./volumes/backups/
# 2. Set the restore variables in .env:
ZNUNY_INSTALL=restore
ZNUNY_BACKUP_DATE=2024-01-15_04-00 # filename without extension
ZNUNY_DROP_DATABASE=yes # required if DB already exists
# 3. Start the stack
docker compose upIf the root@localhost password is unknown (e.g. after running the web installer),
reset it directly inside the running container:
docker exec -it znuny-docker-znuny-1 \
su -c "/opt/znuny/bin/znuny.Console.pl Admin::User::SetPassword root@localhost 'YourNewPassword'" \
-s /bin/bash znunyThe change takes effect immediately — no restart required.
Place .opm addon files in ./volumes/addons/. They are automatically installed
at container startup. Successfully installed addons are moved to
./volumes/addons/installed/.
ZNUNY_SENDMAIL_MODULE=SMTP
ZNUNY_SMTP_SERVER=smtp.example.com
ZNUNY_SMTP_PORT=587
ZNUNY_SMTP_USERNAME=user@example.com
ZNUNY_SMTP_PASSWORD=secret# Build both images
docker compose build
# Build with a specific Znuny version
docker compose build --build-arg ZNUNY_VERSION=7.2.1Push a version tag to trigger the build-and-push workflow:
git tag v7.2.1
git push origin v7.2.1This builds multi-arch images (linux/amd64 + linux/arm64) and pushes:
ghcr.io/<owner>/znuny:7.2.1ghcr.io/<owner>/znuny:latest
The workflow requires no additional secrets — it uses the built-in GITHUB_TOKEN.
Make sure the repository has "Read and write permissions" enabled for Actions under:
Settings → Actions → General → Workflow permissions.
Example host-side Apache virtual host with SSL termination and security headers.
Adjust ServerName and certificate paths to match your environment.
<VirtualHost *:80>
ServerName znuny.example.com
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName znuny.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/your-cert.pem
SSLCertificateKeyFile /etc/ssl/private/your-key.key
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Proxy to Znuny container (adjust port to match ZNUNY_HTTP_PORT)
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
ProxyTimeout 300
ErrorLog ${APACHE_LOG_DIR}/znuny_error.log
CustomLog ${APACHE_LOG_DIR}/znuny_access.log combined
</VirtualHost>Required Apache modules: mod_rewrite, mod_ssl, mod_proxy, mod_proxy_http, mod_headers.
a2enmod rewrite ssl proxy proxy_http headersUpdating to a new patch release only requires bumping the version and restarting. All data is preserved in volumes — the image is replaced, not the data.
# 1. Update the version in .env
ZNUNY_VERSION=7.2.3
# 2. Pull the new pre-built image
docker compose pull
# 3. Restart the stack
docker compose up -dOn startup the container automatically:
- Rebuilds the Znuny configuration (
Maint::Config::Rebuild) - Clears the application cache (
Maint::Cache::Delete) - Reinstalls any addons found in
./volumes/addons
For patch level releases these steps are sufficient. If a release explicitly requires a schema migration or package reinstall, run these commands after the container is up:
docker exec -it znuny-docker-znuny-1 \
su -c "scripts/MigrateToZnuny7_2.pl --verbose" -s /bin/bash znuny
docker exec -it znuny-docker-znuny-1 \
su -c "bin/znuny.Console.pl Admin::Package::ReinstallAll" -s /bin/bash znunyUse this process to move a bare-metal or VM-based Znuny 7.1 installation into this Docker setup. The migration upgrades Znuny to 7.2 at the same time.
On the source system, create a full backup using the bundled script:
su -c "scripts/backup.pl -d /path/to/backup --backup-type fullbackup" - znunyCopy the resulting archive to ./volumes/backups/ on the Docker host.
Set the restore variables in .env:
ZNUNY_INSTALL=restore
ZNUNY_BACKUP_DATE=2024-01-15_04-00 # filename without extension
ZNUNY_DROP_DATABASE=yesStart the stack — the entrypoint will restore the database and files automatically:
docker compose up -dOnce the container is running, execute the migration script:
docker exec -it znuny-docker-znuny-1 \
su -c "scripts/MigrateToZnuny7_2.pl --verbose" -s /bin/bash znunydocker exec -it znuny-docker-znuny-1 \
su -c "bin/znuny.Console.pl Admin::Package::ReinstallAll" -s /bin/bash znunyEdit .env:
ZNUNY_INSTALL=nodocker compose up -dThe system is now running Znuny 7.2 in Docker with all data from the original installation.
Enable debug mode by setting ZNUNY_DEBUG=yes in .env, then:
docker compose up
docker compose logs -f