Running a non-root container on Fedora with podman and systemd (Home Assistant example)

Similar to my post about running Home Assistant on Fedora in Docker, this is about using podman instead and integrating the container as a service with systemd. One of the major advantages to me is the removal of Docker daemon and integration with the rest of the system including management of dependencies like regular services.

This assumes you’ve just installed Fedora server and have a local user with sudo privileges. Let’s also install some SELinux tools.

sudo dnf install -y /usr/sbin/semanage

Create non-root user

Let’s create a specific user to run the Home Assistant service.

We could create a regular user (and remove password expiry settings), but as this is a service let’s create a system account even though it’s a bit more tricky.

sudo useradd -r -m -d /var/lib/hass hass

As this is a system account, we’ll need to manually specify sub user and group ids that the account is allowed to use inside the container. We work out what range is available by looking at /etc/subuid and /etc/subgid files on the host, ideally UID and GID should be the same.

NEW_SUBUID=$(($(tail -1 /etc/subuid |awk -F ":" '{print $2}')+65536))
NEW_SUBGID=$(($(tail -1 /etc/subgid |awk -F ":" '{print $2}')+65536))
sudo usermod \
--add-subuids  ${NEW_SUBUID}-$((${NEW_SUBUID}+65535)) \
--add-subgids  ${NEW_SUBGID}-$((${NEW_SUBGID}+65535)) \
hass

Inside the hass user’s home directory, create a config directory to store configuration files and ssl to store SSL certificates. These will be mapped into the container as /config and /ssl respectively. We will also set the appropriate SELinux context so that the directories can be accessed in the container.

sudo -H -u hass bash -c "mkdir ~/{config,ssl}"
sudo semanage fcontext -a -t user_home_dir_t "/var/lib/hass(/.+)?"
sudo semanage fcontext -a -t svirt_sandbox_file_t \
"/var/lib/hass/((config)|(ssl))(/.+)?"
sudo restorecon -Frv /var/lib/hass

Pull the container image

Now that we have the basic home directly in place, we can switch to the hass user with sudo.

sudo su - hass

As the hass user, let’s use podman to download and run the official Home Assistant image in a container.

First, pull the container which is stored under the non-root user’s ~/.local/share/containers/ directory. Note the latest tag on the end of the image name specifies the version to run. While this is not necessary if you’re getting the latest (as it’s the default), if you want a specific release simply replace latest with the version you want (see their Docker hub page for available releases). Specifying latest means we’ll get the latest release of the container at the time.

podman pull \
docker.io/homeassistant/home-assistant:latest

Manually start the container

Now we can spin up a container using that image. Note that we’re passing in the config and ssl (as read only) directories we created earlier and using host networking to open the required ports on the host.

podman run -dt \
--name=hass \
-v /var/lib/hass/config:/config \
-v /var/lib/hass/ssl:/ssl:ro \
-v /etc/localtime:/etc/localtime:ro \
--net=host \
docker.io/homeassistant/home-assistant:latest

Similar to Docker, you can look at the status of the container and manage it with podman, including getting the logs if you require them.

podman ps -a
podman logs hass
podman restart hass

To get a temporary shell on the container, execute bash.

podman exec -it hass /bin/bash

Inside the container, take a look at the passed-in /config directory (do anything else you want) and then exit when you’re done.

ls -l /config
id
echo "I am root in the container"
exit

Once the container is up and running, the Home Assistant port should be listening on the host.

ss -ltn |grep 8123

Manually destroy the container

Next we’ll create a service to manage this, so for now you can stop and delete this container (this does not delete the image we downloaded). Do this as the hass user still, then exit to return to your regular user.

podman stop hass
podman rm hass
podman ps -a
exit

Configuring the firewall

Home Assistant runs on port 8123, so we will need to open this port on the firewall (back as your regular user).

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=FedoraServer --add-port=8123/tcp

You can test this by using your web browser to connect to the IP address of your machine on port 8123 from another machine on your network.

If that works, make the firewall change permanent.

sudo firewall-cmd --runtime-to-permanent

Create service for the container

Now that we have the container that works, let’s create a systemd service to manage it. This will auto start the container on boot and allow us to manage it as a regular service, including any dependencies. This service stops, removes and starts a new container every time.

Note the Exec lines which will delete and restart the container from the image. As per the manual command above, to run a specific version replace latest with an available tagged release.

cat << EOF | sudo tee /etc/systemd/system/hass.service
[Unit]
Description=Home Assistant in Container
After=network.target

[Service]
User=hass
Group=hass
Type=simple
TimeoutStartSec=5m
ExecStartPre=-/usr/bin/podman rm -f "hass"
ExecStart=podman run --name=hass -v /var/lib/hass/ssl:/ssl:ro -v /var/lib/hass/config:/config -v /etc/localtime:/etc/localtime:ro --net=host docker.io/homeassistant/home-assistant:latest
ExecReload=-/usr/bin/podman stop "hass"
ExecReload=-/usr/bin/podman rm "hass"
ExecStop=-/usr/bin/podman stop "hass"
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
EOF

Reload the systemd daemon is required to pick up the new file.

sudo systemctl daemon-reload

Manage the container with systemd

Let’s see if we can restart the container and check its status. Because it is now managed by systemd, we can check the log with journalctl.

sudo systemctl restart hass
sudo systemctl status hass
sudo journalctl -u hass

Once you’re happy, we can enable the service.

sudo systemctl enable hass

Now is probably a good time to reboot your machine and make sure that the service comes up fine on boot.

Configuring Home Assistant

After rebooting, you should be able to browse to the Home Assistant port on your machine.

Now that you have Home Assistant running, modify the configuration as you please by editing the configuration file under the hass user home directory.

If you make a change, you can simply restart the service.

Updating the container

To update the container, switch to the hass user again and pull a newer version of the container. We can see the newer version of the image with podman and if you want to you can inspect the image for more details.

podman pull docker.io/homeassistant/home-assistant:latest
podman images -a
podman inspect docker.io/homeassistant/home-assistant:latest

Now you can restart the container as your regular user.

sudo systemctl restart hass
sudo journalctl -uf hass.service

Conclusion

Anyway, that’s an example of how you could do it with something like Home Assistant. It can be modified accordingly for any other container you might want to run.

9 thoughts on “Running a non-root container on Fedora with podman and systemd (Home Assistant example)”

  1. Hey Oscar, thanks for the link. I haven’t played with portable services yet, but I will try to make some time to check it out. Cheers.

  2. Great guide. I’m trying this out on RHEL8 rather than on Fedora and I’m getting errors starting up the container image

    For debugging I tried

    podman run -it –name=hass \
    -v /var/lib/hass/config:/config \
    -v /var/lib/hass/ssl:/ssl:ro \
    -v /etc/localtime:/etc/localtime:ro \
    –net=host docker.io/homeassistant/home-assistant:latest /bin/bash

    Then

    python -m homeassistant –config /config

    Traceback (most recent call last):
    File “/usr/local/lib/python3.7/site-packages/keyring/backend.py”, line 203, in _load_plugins
    init_func = ep.load()
    File “/usr/local/lib/python3.7/site-packages/importlib_metadata/__init__.py”, line 94, in load
    module = import_module(match.group(‘module’))
    File “/usr/local/lib/python3.7/importlib/__init__.py”, line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
    File “”, line 1006, in _gcd_import
    File “”, line 983, in _find_and_load
    File “”, line 967, in _find_and_load_unlocked
    File “”, line 677, in _load_unlocked
    File “”, line 728, in exec_module
    File “”, line 219, in _call_with_frames_removed
    File “/usr/local/lib/python3.7/site-packages/keyrings/alt/Windows.py”, line 17, in
    from . import _win_crypto
    File “/usr/local/lib/python3.7/site-packages/keyrings/alt/_win_crypto.py”, line 3, in
    from ctypes import (
    File “/usr/local/lib/python3.7/ctypes/__init__.py”, line 551, in
    _reset_cache()
    File “/usr/local/lib/python3.7/ctypes/__init__.py”, line 273, in _reset_cache
    CFUNCTYPE(c_int)(lambda: None)
    MemoryError

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File “/usr/local/lib/python3.7/runpy.py”, line 193, in _run_module_as_main
    “__main__”, mod_spec)
    File “/usr/local/lib/python3.7/runpy.py”, line 85, in _run_code
    exec(code, run_globals)
    File “/usr/src/homeassistant/homeassistant/__main__.py”, line 380, in
    sys.exit(main())
    File “/usr/src/homeassistant/homeassistant/__main__.py”, line 354, in main
    args = get_arguments()
    File “/usr/src/homeassistant/homeassistant/__main__.py”, line 96, in get_arguments
    import homeassistant.config as config_util
    File “/usr/src/homeassistant/homeassistant/config.py”, line 56, in
    from homeassistant.util.yaml import SECRET_YAML, load_yaml
    File “/usr/src/homeassistant/homeassistant/util/yaml/__init__.py”, line 4, in
    from .loader import clear_secret_cache, load_yaml, secret_yaml
    File “/usr/src/homeassistant/homeassistant/util/yaml/loader.py”, line 17, in
    import keyring
    File “/usr/local/lib/python3.7/site-packages/keyring/__init__.py”, line 1, in
    from .core import (
    File “/usr/local/lib/python3.7/site-packages/keyring/core.py”, line 192, in
    init_backend()
    File “/usr/local/lib/python3.7/site-packages/keyring/core.py”, line 96, in init_backend
    filter(limit, backend.get_all_keyring()),
    File “/usr/local/lib/python3.7/site-packages/keyring/util/__init__.py”, line 22, in wrapper
    func.always_returns = func(*args, **kwargs)
    File “/usr/local/lib/python3.7/site-packages/keyring/backend.py”, line 216, in get_all_keyring
    _load_plugins()
    File “/usr/local/lib/python3.7/site-packages/keyring/backend.py”, line 207, in _load_plugins
    log.exception(“Error initializing plugin %s.” % ep)
    TypeError: not all arguments converted during string formatting

    How much memory is home-assistant using in your container as I’m running a relatively small VM as the container host with 4GB RAM.

  3. Hi I was trying with latest Centos 8.1 and , while the container running as root runs fine,
    under hass user homeassistant bombs with
    MemoryError
    even if I see no memory limit anywhere…
    anyone has some suggestion?
    thanks

  4. Hey Steve and g, sorry I missed your comments. I’ve just spun up a 1GB CentOS 8 VM with the latest updates and it seems to work fine for me… Can you try pulling the latest container and going again?

    [hass@example-centos-8 ~]$ podman logs hass |tail -5
    2020-02-16 20:53:53 INFO (MainThread) [homeassistant.setup] Setup of domain tts took 0.0 seconds.
    2020-02-16 20:53:55 INFO (MainThread) [homeassistant.bootstrap] Home Assistant initialized in 3.39s
    2020-02-16 20:53:55 INFO (MainThread) [homeassistant.core] Starting Home Assistant
    2020-02-16 20:53:55 INFO (MainThread) [homeassistant.core] Timer:starting
    2020-02-16 20:53:55 INFO (SyncWorker_2) [homeassistant.components.zeroconf] Starting Zeroconf broadcast

    [hass@example-centos-8 ~]$ ss -ltnp |grep 8123
    LISTEN 0 128 0.0.0.0:8123 0.0.0.0:* users:(("python3",pid=1454,fd=14))

    [hass@example-centos-8 ~]$ curl http://localhost:8123/onboarding.html
    Home Assistant

  5. Thanks for the blog post!

    I’ve tried above but couldn’t make it work with my hardware devices necessary for my setup.

    The devices are /dev/ttyUSB0 and /dev/ttyUSB1, owned by root/dialout. How do I pass these in with podman (and probably selinux enforcing), just doing the workarounds I use with docker (pass the directory /dev/serial/by-id and then pass the devices ttyUSB0/ttyUSB1) doesn’t work here.

  6. Hi Chris

    I’ve also got a mosquitto pod running under the hass user for mqtt services

    cat << EOF | sudo tee /etc/systemd/system/mosquitto.service
    [Unit]
    Description=Home Assistant in Container
    After=network.target

    [Service]
    User=hass
    Group=hass
    Type=simple
    TimeoutStartSec=5m
    ExecStartPre=-/usr/bin/podman rm -f "mosquitto"
    ExecStart=podman run –name mosquitto \
    –rm -p "9001:9001" -p "1883:1883" \
    eclipse-mosquitto:latest
    ExecReload=-/usr/bin/podman stop "mosquitto"
    ExecReload=-/usr/bin/podman rm "mosquitto"
    ExecStop=-/usr/bin/podman stop "mosquitto"
    Restart=always
    RestartSec=30

    [Install]
    WantedBy=multi-user.target
    EOF

Leave a Reply

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