So if you’re anything like me, you’re aware that tools like Docker and Podman exist, which allow you to run containers on your host.
Simple enough.
You might also be aware of things like Docker Compose and Quadlets for automated management and bring up of those containers.
Problem is, I had a pretty stupid simple little container that I wanted to bring together, and have running on a server. And for the life of me, I couldn’t find a relatively simple “How-To” online to do what I wanted to. All of the offerings that I found were dealing with big pre-built containers for things like Wordpress that have multiple containers, and setup pods, and such. All overkill and more complicated than I needed.
So I figured that I’d write a blog about it, both for my own future personal reference, and in case it helps others out.
So what is it that I want this thing to do? I want it to run a Matterbridge, which bridges chat between two different chat protocols. I’ve set this up before in the traditional way, installing it on the host, and running it that way. It’s pretty basic, and the actual matterbridge configuration is beyond the scope of this blog post.
So we basically need three things to set this up:
econoline ~ % tree matterbridge
matterbridge
├── Containerfile # This is where we define our Container
├── matterbridge-1.26.0-linux-64-bit # The binary we want to run in the container
├── matterbridge.toml # Configuration file for the bridge
My Containerfile consists of:
FROM alpine:latest AS matterbridge
ADD matterbridge-1.26.0-linux-64bit /opt
ADD matterbridge.toml /opt
WORKDIR /opt
CMD ["./matterbridge-1.26.0-linux-64bit", "-conf", "/opt/matterbridge.toml"]
So this is going to grab the latest Alpine Linux image, add my matterbridge bits to /opt inside the container, and when the container starts, it’s going to switch to /opt, then run ./matterbridge-1.26.0-linux-64bit -conf /opt/matterbridge.toml
Build the container with podman build -t matterbridge . which is going to create a local container, tagged as localhost/matterbridge:latest
To test that my container works, I run podman run -d localhost/matterbridge:latest and verify that the bridge comes up, and routes chats between the two different chat protocols (IRC and Discord, in this case) and it does. Shiny.
Now, I have a container, that does what I want, but as it sits, anytime the system reboots, I would have to go in, and manually start the container. Which isn’t great for a hands-off service, that I just want to work
This is where Quadlets come into play, which leverage SystemD to bring your containers up, and do whatever you need to do.
I want to run this as an unprivleged user, so we need to enable user lingering in systemd, which is done with loginctl enable-linger $user
And then we create the file ~/.config/containers/systemd/matterbridge.container which consists of:
[Unit]
Description=Matterbridge container
[Container]
Image=localhost/matterbridge:latest
AutoUpdate=registry
ContainerName=matterbridge
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target default.target
Now, run systemctl --user daemon-reload and your SystemD user session is going to pick up on the .container file, and generate a service based from it.
You can verify that it got done, by running systemctl --user status matterbridge.service and you you should see a service, but it won’t be running. Keep in mind, you can’t “enable” this service like you normally would other systemd services, because it’s being automatically generated. On the other hand, you can start it, with systemctl --user start matterbridge.service and verify that your container gets started up.
Or, you can just yolo it, and restart the machine, like I did, and figure you did things right. Which I did. Yay Me.
[sfalken@econoline ~]$ systemctl --user status matterbridge.service
● matterbridge.service - Matterbridge container
Loaded: loaded (/home/sfalken/.config/containers/systemd/matterbridge.container; generated)
Active: active (running) since Fri 2024-02-23 04:30:13 PST; 9h ago
Main PID: 1110 (conmon)
Tasks: 6 (limit: 2326)
Memory: 35.8M
CPU: 10.670s
CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/matterbridge.service
├─libpod-payload-20232ff412d2e92efff501d379175d20f4ed2999b2794c8ef3a74f35ae94133c
│ └─1113 ./matterbridge-1.26.0-linux-64bit -conf /opt/matterbridge.toml
└─runtime
├─1105 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp --enable>
└─1110 /usr/bin/conmon --api-version 1 -c 20232ff412d2e92efff501d379175d20f4ed2999b2794c8ef3a74f35ae94133c>
Feb 23 04:30:13 econoline matterbridge[1110]: time="2024-02-23T12:30:13Z" level=info msg="Starting bridge: irc.example >
Feb 23 04:30:13 econoline matterbridge[1110]: time="2024-02-23T12:30:13Z" level=info msg="Connecting irc.example:779>
Feb 23 04:30:24 econoline matterbridge[1110]: time="2024-02-23T12:30:24Z" level=info msg="Connection succeeded" prefix=irc
Feb 23 04:30:24 econoline matterbridge[1110]: time="2024-02-23T12:30:24Z" level=info msg="irc.example: joining #channel>
Feb 23 04:30:24 econoline matterbridge[1110]: time="2024-02-23T12:30:24Z" level=info msg="Starting bridge: discord.foo " >
Feb 23 04:30:24 econoline matterbridge[1110]: time="2024-02-23T12:30:24Z" level=info msg=Connecting prefix=discord
Feb 23 04:30:24 econoline matterbridge[1110]: time="2024-02-23T12:30:24Z" level=info msg="Connection succeeded" prefix=di>
Feb 23 04:30:27 econoline matterbridge[1110]: time="2024-02-23T12:30:27Z" level=info msg="Picking up webhook" channel=858>
Feb 23 04:30:27 econoline matterbridge[1110]: time="2024-02-23T12:30:27Z" level=info msg="discord.foo: joining irc-bridge>
Feb 23 04:30:27 econoline matterbridge[1110]: time="2024-02-23T12:30:27Z" level=info msg="Gateway(s) started succesfully.>
Hope folks find this helpful, or at least help with the headache that comes with looking for easy to follow things on containers in many places on the interwebs.