My Blog
How to Dockerize your Virtualmin Like a Pro (Without Losing Your Mind)
So, you want to dockerize Virtualmin? Stop right there! Trying to shove the entire Virtualmin system into a Docker container is a surefire way to fail. Instead, let’s do something smarter—dockerize only the databases. This approach offers a bulletproof setup with isolated database instances, automatic Virtualmin backups, and a disaster recovery plan that actually works.
In this guide, we’ll show you how to run each database in its own MariaDB instance, reducing the risk of total database failure in case of InnoDB corruption or cyberattacks. Bonus: You’ll still have Virtualmin’s UI to manage DNS, email, and everything else.
Why Combine Docker with Virtualmin?
Because, my dear reader, you get the best of both worlds:
- Isolated databases: Each database runs in its own container with its own MariaDB instance.
- Scalability: Easily move containers between servers.
- Security: If one database is hacked, it doesn’t affect others.
- Virtualmin UI stays intact: Clients can still manage their hosting environment while databases live inside Docker.
Step 1: Install Docker, Docker-Compose, and Portainer
First things first, install the required software:
apt update && apt install -y docker.io docker-compose
Then, deploy Portainer for a user-friendly container management experience:
docker run -d -p 9000:9000 --name=portainer --restart=unless-stopped -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer-ce
Access Portainer via http://yourdomain:9000
to easily manage your containers.
Step 2: Prepare the Virtualmin Server for Docker
Navigate to your Virtualmin server’s home directory and create a Docker workspace:
cd /home/SERVER_NAME mkdir docker && cd docker mkdir mysql database_backups
Step 3: Create a Docker Compose File
Inside the docker
folder, create a docker-compose.yml
file:
version: '3.8' services: mariadb: build: . container_name: SERVER_NAME-db environment: MYSQL_ROOT_PASSWORD: "YOUR_ROOT_PASSWORD" MYSQL_DATABASE: "YOUR_DATABASE_NAME" MYSQL_USER: "YOUR_DATABASE_USER" MYSQL_PASSWORD: "YOUR_DATABASE_PASSWORD" volumes: - /home/SERVER_NAME/docker/mysql:/var/lib/mysql restart: unless-stopped networks: - SERVER_NAME-db_network networks: SERVER_NAME-db_network: driver: bridge
Step 4: Create a Dockerfile
Inside the docker
folder, create a Dockerfile
:
FROM mariadb:latest COPY my.cnf /etc/mysql/mariadb.conf.d/50-server.cnf
Step 5: Configure MariaDB
Create a my.cnf
file inside the docker
folder:
[client] default-character-set = utf8mb4 [mysqld] user = mysql bind-address = 0.0.0.0 character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci max_allowed_packet = 64M innodb_buffer_pool_size = 2G innodb_log_file_size = 512M tmp_table_size = 256M max_heap_table_size = 256M table_open_cache = 8000 log_error = /var/log/mysql/error.log slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow-queries.log long_query_time = 2
Step 6: Deploy the MariaDB Container
Navigate to your Docker directory and launch the container:
cd /home/SERVER_NAME/docker docker-compose -f docker-compose.yml -p SERVER_NAME-db up -d
Step 7: Connect Your Application to the Dockerized Database
Get the container’s IP by checking Portainer or running:
docker inspect SERVER_NAME-db | grep "IPAddress"
Edit your WordPress wp-config.php
or application database config to use the container’s IP:
define('DB_HOST', '172.XX.XX.XX:3306');
Step 8 (Optional): Set Up Automatic Backups
To ensure your database is safe, use Virtualmin’s built-in backup system:
- Visit:
YOUR_DOMAIN:10000/virtual-server/list_sched.cgi?xnavigation=1
- Schedule backups to a remote storage.
For an additional backup solution, create a database export script:
nano /home/SERVER_NAME/docker/export_db.sh
Paste the following script:
#!/bin/bash set -e BASE_DIR="/home/SERVER_NAME/docker" BACKUP_DIR="$BASE_DIR/database_backups" DATE=$(date +"%Y-%m-%d_%H-%M-%S") RETENTION_DAYS=30 LOG_FILE="$BASE_DIR/cron_log.txt" mkdir -p "$BACKUP_DIR" BACKUP_FILE="$BACKUP_DIR/db_backup_$DATE.sql" CONTAINER_NAME=$(grep -E '^\s*container_name:' "$BASE_DIR/docker-compose.yml" | awk -F': ' '{print $2}' | xargs) DB_NAME=$(grep -E '^\s*MYSQL_DATABASE:' "$BASE_DIR/docker-compose.yml" | awk -F': ' '{print $2}' | xargs) DB_USER=$(grep -E '^\s*MYSQL_USER:' "$BASE_DIR/docker-compose.yml" | awk -F': ' '{print $2}' | xargs) DB_PASSWORD=$(grep -E '^\s*MYSQL_PASSWORD:' "$BASE_DIR/docker-compose.yml" | awk -F': ' '{print $2}' | xargs) cd "$BASE_DIR" || exit 1 docker exec "$CONTAINER_NAME" mariadb-dump -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "$BACKUP_FILE" tar -czf "$BACKUP_FILE.tar.gz" -C "$BACKUP_DIR" "$(basename "$BACKUP_FILE")" rm "$BACKUP_FILE" find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete echo "Backup completed: $BACKUP_FILE.tar.gz" >> "$LOG_FILE"
Make it executable:
chmod +x /home/SERVER_NAME/docker/export_db.sh
To automate daily backups, create a cron job in Virtualmin:
- Go to
YOUR_DOMAIN:10000/cron/?xnavigation=1
- Add this command:
/home/SERVER_NAME/docker/export_db.sh >> /home/SERVER_NAME/docker/db_backup_log.txt 2>&1
Extra Tip: Want phpMyAdmin?
Download phpMyAdmin, unzip it in your website directory, and modify config.inc.php
to set the container’s IP as the database host.
Conclusion
Congratulations, you’ve successfully dockerized Virtualmin’s databases while keeping the UI intact! This setup enhances security, performance, and scalability while ensuring that every database runs in an isolated container. Now, go enjoy your fully dockerized Virtualmin experience! 🚀