This is how I got Silverbullet (v1/v2) to run nicely on Rocky Linux using rootless Podman Quadlets, and how you can do the same!
Software featured:
- Rocky Linux
- Podman
- firewalld
- systemd
- Caddy
- Silverbullet
Prerequisites
- A Rocky Linux server or VM
- Secure access to said server if it’s remote (e.g. SSH access via SSH key only)
- No dots in your server’s hostname, or NetworkManager’s DNS management disabled (see the Troubleshooting section at the bottom for how to do that)
Getting Started
The first step is to install a recent version of podman and associated container tools. The way to do that depends on the version of Rocky Linux you have installed. If you have Rocky Linux version 10.x or newer, you can just use this command:
sudo dnf install -y container-tools
If you have Rocky Linux 9.x or older, you’ll need to use this series of commands to install a recent enough version of container tools that has good support for quadlets:
sudo dnf copr enable -y rhcontainerbot/podman-next
sudo dnf install -y podman
Since we plan to set up our podman quadlets to be rootless, they can by default only expose ports 1024 to 65535. However, HTTP(S) traffic uses ports 80 and 443, which means we need to give Caddy access to incoming connections to those ports.
A really clean way to do that is by forwarding incoming connections to port 80 and 443 to internal ports 8080 and 8443, respectively. Rocky Linux comes with firewalld, which manages port forwarding. We can set up the forwarding of the aforementioned ports using this series of commands:
note Before pasting a series of
sudo
commands into the terminal, make sure you won’t be prompted for your password by usingsudo true
.
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 forward-port port=80 to-port=8080 protocol=tcp'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 forward-port port=443 to-port=8443 protocol=tcp'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 forward-port port=443 to-port=8443 protocol=udp'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv6 forward-port port=80 to-port=8080 protocol=tcp'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv6 forward-port port=443 to-port=8443 protocol=tcp'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv6 forward-port port=443 to-port=8443 protocol=udp'
Now we have set up port forwarding for IPv4 and IPv6 and both TCP and UDP, but we still need to open ports 80 and 443. This series of commands will take care of that:
sudo firewall-cmd --permanent --zone=public --family="ipv4 ipv6" --add-port=80/tcp
sudo firewall-cmd --permanent --zone=public --family="ipv4 ipv6" --add-port=443/tcp
sudo firewall-cmd --permanent --zone=public --family="ipv4 ipv6" --add-port=443/udp
note You might have noticed that port 80 with protocol UDP has been excluded from these configurations. That’s because Caddy will always try to redirect HTTP (port 80) requests to HTTPS (port 443), and for that to happen, you only need to have port 80 open for TCP connections.
To make it easier to refer to a container when configuring Caddy, we’ll want to set up a (DNS-enabled) Podman network later. This feature requires IP forwarding, sometimes also called IP masquerading, to be enabled on the operating system. This series of commands will enable IP forwarding on Rocky Linux:
sudo firewall-cmd --permanent --zone=public --add-masquerade
sudo sh -c 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.d/01_enable_ip_forwarding.conf'
sudo sh -c 'echo "net.ipv6.conf.all.forwarding=1 >> /etc/sysctl.d/01_enable_ip_forwarding.conf'
All these firewall and networking changes will be applied the next time the server is booted. Now’s a good time to reboot it:
sudo reboot
Once the server is back up about a minute later, you will be able to reconnect, allowing us to start working on those quadlets! They will be stored in .config/containers/systemd/
, so let’s set up the directory structure and then create the quadlet files:
mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd
touch default.network
touch caddy.container
touch silverbullet.container
Let’s configure the default.network
first. You can use whichever text editor you prefer, but I will be using nano for ease of demonstration:
nano default.network
note If you’re new to nano, know that you can save a file and exit the editor using
Ctrl
+X
followed byY
followed byEnter
.
Feel free to copy and paste this basic configuration for default.network
:
[Unit]
Description=Default (DNS-enabled) Podman network
[Network]
NetworkDeleteOnStop=true
[Install]
WantedBy=default.target
Now let’s do the same for caddy.container
:
[Unit]
Description=Caddy Quadlet
[Container]
ContainerName=caddy
Image=docker.io/library/caddy:2-alpine
AutoUpdate=registry
Network=podman
Network=default.network
Volume=%h/.config/caddy/Caddyfile:/etc/caddy/Caddyfile:Z
Volume=%h/.config/caddy:/config:Z
Volume=%h/.local/share/caddy:/data:Z
PublishPort=8080:80/tcp
PublishPort=8443:443/tcp
PublishPort=8443:443/udp
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target
note
%h
refers to the HOME directory. We will create the necessary directory structures and files in just a moment.
And let’s also add silverbullet.container
:
[Unit]
Description=Silverbullet Quadlet
[Container]
ContainerName=silverbullet
Image=docker.io/zefhemel/silverbullet:v2
AutoUpdate=registry
Network=default.network
EnvironmentFile=%h/.env.d/silverbullet.env
Volume=%h/space:/space:z
[Service]
Restart=unless-stopped
[Install]
WantedBy=default.target
note I’m using the new, not-yet-considered-stable v2 of Silverbullet here; if you’d rather stick to v1 for now, replace the
:v2
part of the line that starts withImage=
with:latest
. (This will eventually update your Silverbullet instance to v2 once it’s stable.)
note We’re setting Silverbullet’s space directory to be potentially shared with other containers by ending its
Volume=
setting with:z
(lowercase z rather than uppercase Z).
Let’s create all the necessary directory structures and files:
cd
mkdir .config/caddy
touch .config/caddy/Caddyfile
mkdir .env.d
touch .env.d/silverbullet.env
mkdir -p .local/share/caddy
mkdir space
Two more files to be filled in:
~/.config/caddy/Caddyfile
:
# Silverbullet
sb.yourdomain.com {
reverse_proxy silverbullet:3000
}
Make sure to replace sb.yourdomain.com
with the (sub)domain on which you want to host Silverbullet.
~/.env.d/silverbullet.env
:
SB_USER=username:passwordchangethis
SB_AUTH_TOKEN=APIPasswordChangeThis
SB_LOCKOUT_LIMIT=3
SB_LOCKOUT_TIME=300
Make sure to change the username and both passwords in this file.
If you haven’t already set up your DNS server by adding an A and AAAA record that point the (sub)domain you want to use for Silverbullet at your server’s IPv4/IPv6 address respectively, then now’s a great time to do that to make sure Caddy can set up HTTPS for you. Setting an AAAA record for IPv6 is optional but recommended.
note If you use Cloudflare’s proxy service, make sure you set its SSL/TLS setting for your site to
Full (Strict)
to avoid an infinite redirect loop issue.
Once you have DNS configured, you can finally load your quadlets and start them, like this:
systemctl --user daemon-reload
systemctl --user start default-network
systemctl --user start caddy silverbullet
Now Caddy and Silverbullet should be running and reachable at the (sub)domain you specified.
There’s only one issue now: Since we are using rootless Podman Quadlets, they are tied to your user login, so there needs to be an ongoing login session for them to be active. However, there is a way to ensure that, and that’s by enabling user lingering for your user. This will automatically start a user session when the server is (re)booted, keeping associated services running. This command enables user lingering for your current user:
sudo loginctl enable-linger $USER
That should do the trick! From now on, Caddy and Silverbullet will start automatically whenever your server is booted (because we set WantedBy=default.target
and enabled user lingering), and they will also automatically be kept up-to-date (because we set AutoUpdate=registry
).
I hope you had fun setting this up, and I hope the instructions I gave were easy to understand and follow. Please feel invited to ask questions and/or provide feedback.
Have a wonderful rest of your day!
—luci@monospacedmagic
Troubleshooting
I have set up everything according to the instructions, but when trying to access my Silverbullet instance, I get a Gateway error (code 502).
A likely cause for this is your server having a hostname that includes a dot. If that is the case for you, then Rocky Linux’ NetworkManager automatically sets up hostname lookups using the part of the hostname after the first dot, and they take priority over Podman container names as hostnames.
You can fix this by disabling NetworkManager’s DNS management and updating your DNS settings manually. Here’s how:
sudo sh -c 'echo "[main]
dns=none" >> /etc/NetworkManager/conf.d/90-dns-none.conf'
sudo systemctl reload NetworkManager
sudo sed -i '0,/^search/{/^search/d}' /etc/resolv.conf
sudo reboot
License
This post is published into the public domain and licensed under the Creative Commons Zero 1.0 license.