Here are some notes on how I used LXD to run a container for WordPress. This is (a lot) more convenient than using Docker, which was my original approach to getting my WordPress site into a container. The main advantage for me is that a single container runs all the components together – no need for the ‘wiring’ between containers for each process.
There is a bash script that automates this at https://github.com/Kevin-Sangeelee/lxd-wordpress, and is a more complete description of the process since it automatically configures SSL/TLS and Exim.
Getting LXD onto Debian Stretch
LXD is installed on Debian via a Snap package, so
sudo apt-get install snapd if this is not already installed. See https://docs.snapcraft.io/installing-snap-on-debian. Then run
snap install lxd (see https://stgraber.org/2017/01/18/lxd-on-debian/) and log in again to get an updated command path to the new snap-installed binaries.
lxd init to configure the default environment, the storage type I chose was simply directories, since it’s the most convenient for moving files from the Docker setup that I’m migrating from.
Create and configure our container
When lxd is installed, create a new Debian container with
lxc launch 'images:debian/9' susanet-wp. This article was written based on a debian/9 (Stretch) container, but I have since upgraded to debian/10 (Buster) with no issues. There are a couple of upgrade notes below.
lxc exec susanet-wp passwd to set a root password, then
lxc console susanet-wp to log into the console. From here we can install the required packages.
apt-get install apache2 php-curl php-gd php-intl php-mbstring php-soap
php-xml php-xmlrpc php-zip libapache2-mod-php php-mysql
libphp-phpmailer mariadb-server mariadb-client iputils-ping
exim4-daemon-light curl wget netcat
From here it’s pretty much a normal WordPress installation. Since I was migrating from another database, the commands used to get MariaDB set up were as follows: –
create database wp_db
create user 'wp_user'@'localhost' identified by '<db password>'
grant all privileges on wp_db.* to 'wp_user'@'localhost'
I used this command to install my SQL dump file taken from the old Docker setup: –
zcat kakapo_wordpress_db.gz|lxc exec susanet-wp -- mysql wp_db
Some notes on LXD
LXD creates containers from locally stored images, though these images might themselves be fetched from a remote server.
There are a number of pre-configured public repositories, which can be viewed with
lxc remote list, and if you have another LXD installation elsewhere, then this can be used as a further remote server.
The command to register a new remote server is
lxc remote add myremote 10.81.1.4, where the IP address is that of another server running LXD, and ‘myremote’ is the alias by which I want to refer to the remote server.
Note that the remote server must be exposed on port 8443 (by default) of the specified IP. A password also has to be defined – clients will be prompted for this when adding this remote server. The following commands will configure the remote server.
lxc config unset core.https_address
lxc config set core.https_address [::]:8443
lxc config set core.trust_password <my_remote's password>
A snapshot in LXD refers to the state of a container as at a specific point in time, and can be used to easily restore the state of the container.
An image can be created from a stopped container, or from a snapshot of a running container. The following commands are listed as examples of usage: –
lxc snapshot susanet-wp my-snapshot
lxc publish susanet-wp/my-snapshot --alias my-new-image
lxc delete susanet-wp/my-snapshot
snapshot command takes a snapshot of the given container. The
publish command creates a local image from this snapshot, and the
delete command removes the snapshot (assuming you no longer want it).
Putting the above together, this can be used to copy a container to a backup server. The main local server would be configured to bind to an IP address/socket and given a password, and the backup server adds this as a remote. It can then ‘launch’ this image.
Alternatively, it’s even possible to simply push a local image to a backup server: –
lxc launch my-new-image myremote:susanet-backup
In this case, my local image ‘my-new-image’ is created on a remote server I aliased as ‘myremote’, and the new container on the remote server is called ‘susanet-backup’.
Networking to the outside world
A container can be given an interface on the bridge using something like the following: –
lxc config device add susanet-wp eth0 nic name=eth0 nictype=bridged parent=lxdbr0
We can use DNAT to forward host ports (e.g. on an external IP address) to the bridged interface using something like the following: –
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT \
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT \
A possibly simpler and more convenient way to connect the container to an external IP address is to use the ‘proxy’ device. This connects an ip:port address on the host to an ip:port in the container, so: –
kevin@vps1:~$ lxc config device add susanet-wp http proxy \
kevin@vps1:~$ lxc config device add susanet-wp https proxy \
would connect port 80 on all host interfaces to port 80 on the container’s localhost interface.
A note on UFW
ufw (The Uncomplicated Firewall), then the default ‘forward’ policy is to drop packets, meaning that no outgoing traffic will be routed from the guest to the Internet.
A rule will be required to forward packets between interfaces, otherwise just allow forwarding by default. Edit
/etc/default/ufw and set
ufw reload to enable the policy. Change it back to “DROP” if you want to revoke outgoing traffic.
Upgrading to Debian/10 Buster
The upgrade process to Debian/10 (Buster) is straightforward, though it’s worth taking a snapshot of your container to revert to if anything goes wrong.
From the container, edit
/etc/apt/sources.list and change
buster as follows: –
deb http://deb.debian.org/debian buster main deb http://security.debian.org/debian-security buster/updates main
Then run the following to perform the upgrade.
apt-get update apt-get upgrade apt-get dist-upgrade a2enmod php7.3
Note that Apache will continue to use the PHP 7.0 module until you enable PHP 7.3 (the last line above). When it’s all running on PHP 7.3, you can safely uninstall all the PHP 7.0 packages to reduce your container’s size.
apt-get remove php7.0 apt-get autoremove apt-get clean
You may need to start/restart services mysql (MariaDB) or apache2 after all this, or just reboot your container. Finally, take a moment to appreciate all the amazing work done by Free Software developers that allowed a containerized operating system, an RDBMS, a PHP runtime, and a web server to be upgraded painlessly in minutes (and shame Microsoft, who rarely let us play a game on their woeful XBox without a tedious 1GB update).