Skip to content

Preparing an AlmaLinux 10 server

For whoever is running the desk. Ten minutes, then go on to Install.

Written against AlmaLinux 10.2. Rocky Linux and RHEL 10 are the same throughout; Debian and Ubuntu differ only in the package manager and the Docker repository path.

The machine

Two cores and 2 GB of memory will run a small desk; four cores and 8 GB is comfortable. Allow 20 GB of disk to start, the images are about 700 MB, and after that it is your attachments and your database that grow.

Two things must be true before you begin, and neither is something the installer can do for you:

  • A name points at this machine. An A record, and an AAAA as well only if IPv6 genuinely works here, certificate authorities prefer IPv6 when a record exists, so an AAAA pointing somewhere that does not answer fails the certificate while IPv4 looks perfect.
  • Ports 80 and 443 are open to the internet. Both. The certificate authority reaches you on 80, from outside; a firewall that only allows your own address will not do.

Check the name first, from somewhere that is not the server:

Terminal window
dig +short A your-desk.example.com
dig +short AAAA your-desk.example.com

1. Bring it up to date

Terminal window
dnf -y update
dnf -y install curl chrony
systemctl enable --now chronyd

Reboot if that updated the kernel, and it usually will on a machine that has been sitting:

Terminal window
uname -r; rpm -q kernel | tail -1 # different? reboot
reboot

This is not tidiness. Docker’s packages bring in kernel-modules-extra built for the newest installed kernel, and the modules it needs (overlay, br_netfilter) will not load into a kernel that is still the old one. The symptom is docker.service failing to start straight after a clean install, with nothing obviously wrong, which is a miserable thing to debug on a machine you have owned for ten minutes.

The clock matters more than it looks, too: certificate validation, signed links and two-factor codes all depend on it.

2. Docker

Terminal window
dnf -y install dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable --now docker

The docker-compose-plugin is the part the desk needs: every command in the guide is docker compose something.

If docker.service fails to start anyway, the journal says why in one line:

Terminal window
journalctl -xeu docker.service --no-pager | tail -30

The one to expect looks like a firewall problem and is not:

failed to register "bridge" driver: failed to add jump rules to ipv4 NAT table
iptables v1.8.11 (nf_tables): RULE_APPEND failed (No such file or directory): rule in chain PREROUTING

That is the kernel mismatch above, seen from Docker’s side: it is asking netfilter for the NAT table, and the module that provides it will not load into the kernel that is running. Reboot. If it survives a reboot, find out which of the two it really is:

Terminal window
uname -r; rpm -q kernel
modprobe iptable_nat; echo "exit: $?"
lsmod | grep -E 'nf_nat|iptable_nat|br_netfilter|overlay'

A modprobe that fails with “module not found” is still a mismatch. A modprobe that succeeds, after which Docker starts, means the modules were merely not loaded, put nf_nat and iptable_nat in /etc/modules-load.d/docker.conf so the next reboot does not repeat it.

3. The firewall

Terminal window
dnf -y install firewalld
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
firewall-cmd --list-services

Nothing else needs opening. The database, the cache and the desk itself are reached only from inside the container network or over the machine’s own loopback.

4. SELinux

Leave it enforcing. The desk’s compose file already carries what it needs: the one file mounted from the host (the Caddyfile) is mounted :ro,z, which is the difference between Caddy starting and Caddy restarting for ever on “permission denied”.

Terminal window
getenforce # Enforcing

If you are adding bind mounts of your own, z on each of them is what you will need.

5. Check it

Terminal window
docker run --rm alpine echo "containers work"
curl -s -o /dev/null -w '%{http_code}\n' http://your-desk.example.com/ # anything but a timeout is fine

The second one is checking that port 80 reaches this machine from outside, a timeout here means the certificate will fail later, and it is far easier to find now.

Then

Go on to Install. You will need a Sixnix registry login before the first step.