LXD now runs my WordPress

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.

Run 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.

Use 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'
flush privileges

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

The 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 \
--to-destination 10.102.22.71:80

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT \
--to-destination 10.102.22.71:443

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 \
listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:80
kevin@vps1:~$ lxc config device add susanet-wp https proxy \
listen=tcp:0.0.0.0:443 connect=tcp:127.0.0.1:443

would connect port 80 on all host interfaces to port 80 on the container’s localhost interface.

A note on UFW

If using 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

DEFAULT_FORWARD_POLICY="ACCEPT"

Run 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 stretch to 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).

Leave a Reply

Your email address will not be published. Required fields are marked *