Podman Quadlets Arr-Stack And Networking
from Excaliburr@lemmy.dbzer0.com to selfhosted@lemmy.world on 21 Jan 11:30
https://lemmy.dbzer0.com/post/62083240

I need some help if anyone could take the time and has the knowledge:

I’m basically new to podman and namespaces, relatively new to linux and a noob at networking. So figuring this out and getting it to work took many more hours than I would like to admit, but I still have a few problems. I have all my current Quadlets below in the spoiler (seperated by “—”, assume user123 = UID 1000). I am on Bazzite, rootless Podman, which probably makes this even harder.

Spoiler with the Quadlets

[Unit] Description=Arr-stack pod [Pod] PodName=arr-stack # Network # Network=vpn-only # User mapping / I don’t fully understand this yet, but the pod does not work without this (maps user id to specified ID inside the containers? So the containers have UID:GID 1000:1000?) UserNS=keep-id:uid=1000,gid=1000 # # Homepage Port Mapping PublishPort=3000:3000 # Jellyfin Port Mapping PublishPort=8096:8096/tcp # qBittorrent Port Mapping PublishPort=8080:8080 #PublishPort=6881:6881 #PublishPort=6881:6881/udp # Prowlarr Port Mapping PublishPort=9696:9696 # Flaresolverr Port Mapping PublishPort=8191:8191 # Radarr Port Mapping PublishPort=7878:7878 # Sonarr Port Mapping PublishPort=8989:8989 # Jellyseerr Port Mapping #PublishPort=8055:5055 #[Install] # WantedBy=default.target — [Unit] Description=Gluetun Container # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod [Container] ContainerName=gluetun Pod=arr-stack.pod Image=docker.io/qmcgaw/gluetun:v3 AutoUpdate=registry # Network # Network=vpn-only # UID/GID permissions / root + privileged for networking? PodmanArgs=–privileged User=0 Group=0 # Equivalent to cap_add: - NET_ADMIN # one wrong? AddCapability=NET_ADMIN AddCapability=CAP_NET_ADMIN # Required for Gluetun to delete the bridge’s default route, but does not work AddCapability=NET_RAW AddCapability=CAP_NET_RAW # Equivalent to “devices: - /dev/net/tun:/dev/net/tun” AddDevice=/dev/net/tun:/dev/net/tun # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=gluetun.env # Environment=FIREWALL_OUTBOUND_SUBNETS=10.90.0.0/24 / test from a specific podman network Environment=FIREWALL_INPUT_PORTS=8080 # Environment=VPN_SERVICE_PROVIDER= <123> Environment=VPN_TYPE=wireguard Environment=WIREGUARD_PRIVATE_KEY= <key> Environment=SERVER_COUNTRIES= <country> # for now: Environment=VPN_PORT_FORWARDING=off #Secret=openvpn_user,type=env,target=OPENVPN_USER #Secret=openvpn_password,type=env,target=OPENVPN_PASSWORD #Volume Volume=/var/home/user123/.config/arr-configs/gluetun:/gluetun:Z # SecurityLabel=disable [Service] Restart=always #[Install] #WantedBy=default.target — [Unit] Description=qBittorrent Container # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container [Container] ContainerName=qbittorrent Pod=arr-stack.pod Image=lscr.io/linuxserver/qbittorrent:latest AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions / linuxserver images require UID:GID 0:0 at the start; they won’t start without it User=0 Group=0 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=qbittorrent.env Environment=WEBUI_PORT=8080 # Environtment=TORRENTING_PORT=6881 # Volume :Z (> :z) probably works as well and is saver for configs? Volume=/var/home/user123/.config/arr-configs/qbittorrent:/config:z Volume=/var/home/user123/Videos/Downloads:/downloads:z # Volume=/var/home/user123/Videos/Downloads/completed:/downloads:z,U # Volume=/var/home/user123/Videos/Downloads/incomplete:/incomplete:z,U # Volume=/var/home/user123/Videos/Downloads/torrents:/torrents:z,U [Service] Restart=always #[Install] #WantedBy=default.target — [Unit] Description=Prowlarr Container # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container [Container] ContainerName=prowlarr Pod=arr-stack.pod Image=lscr.io/linuxserver/prowlarr:latest AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions / linuxserver images require UID:GID 0:0 at the start; they won’t start without it User=0 Group=0 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=prowlarr.env Environment=WEBUI_PORT=9696 # Volume Volume=/var/home/user123/.config/arr-configs/prowlarr:/config:z,U [Service] Restart=always #[Install] #WantedBy=default.target — [Unit] Description=Sonarr Container # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container [Container] ContainerName=sonarr Pod=arr-stack.pod Image=lscr.io/linuxserver/sonarr:latest AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions / linuxserver images require UID:GID 0:0 at the start; they won’t start without it User=0 Group=0 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=sonarr.env Environment=WEBUI_PORT=8989 # Volume / Disable SecurityLabels due to SMB share, need to look this up SecurityLabelDisable=true Volume=/var/home/user123/.config/arr-configs/sonarr:/config:z Volume=/var/home/user123/Videos/Shows:/tv:z Volume=/var/home/user123/Videos/Downloads:/downloads:z [Service] Restart=always #[Install] #WantedBy=default.target — [Unit] Description=Radarr Container # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container [Container] ContainerName=radarr Pod=arr-stack.pod Image=lscr.io/linuxserver/radarr:latest AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions / linuxserver images require UID:GID 0:0 at the start; they won’t start without it User=0 Group=0 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=radarr.env Environment=WEBUI_PORT=7878 # Volume / Disable SecurityLabels due to SMB share SecurityLabelDisable=true Volume=/var/home/user123/.config/arr-configs/radarr:/config:z Volume=/var/home/user123/Videos/Movies:/movies:z Volume=/var/home/user123/Videos/Downloads:/downloads:z [Service] Restart=always #[Install] #WantedBy=default.target — [Unit] Description=Flaresolverr Container # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container [Container] ContainerName=flaresolverr Pod=arr-stack.pod Image=ghcr.io/flaresolverr/flaresolverr:latest AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions User=0 Group=0 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=flaresolverr.env Environment=WEBUI_PORT=8191 Environment=LOG_LEVEL=info Environment=LOG_HTML=false Environment=CAPTCHA_SOLVER=none # Volume=flaresolverr:/app/ [Service] Restart=always #[Install] #WantedBy=default.target — [Unit] Description=Podman - Jellyfin # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container [Container] ContainerName=jellyfin Pod=arr-stack.pod Image=ghcr.io/jellyfin/jellyfin AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions / 1000:1000 might work? User=0 Group=0 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvironmentFile=jellyfin.env Environment=WEBUI_PORT=8096:8096/tcp #PublishPort=8096:8096/tcp #PublishPort=8920:8920 #PublishPort=7359:7359/udp #PublishPort=1900:1900/udp # Volume Volume=/var/home/user123/.config/arr-configs/jellyfin:/config:z Volume=/var/home/user123/Videos/jellyfin-cache:/cache:z Volume=/var/home/user123/Videos/Movies:/data/movies:z Volume=/var/home/user123/Videos/Shows:/data/shows:z [Service] # Inform systemd of additional exit status # SuccessExitStatus=0 143a Restart=always TimeoutStartSec=900 #[Install] # Start by default on boot #WantedBy=default.target — [Unit] Description=Homepage Dashboard # Dependencies # pod Wants=arr-stack-pod.service After=arr-stack-pod.service Requires=arr-stack-pod.service PartOf=arr-stack-pod.service # .pod is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=arr-stack.pod After=arr-stack.pod Requires=arr-stack.pod PartOf=arr-stack.pod # gluetun Wants=gluetun.service After=gluetun.service Requires=gluetun.service BindsTo=gluetun.service # .container is probably not quite what I want, but it works and I might as well keep it, in case they change the syntax Wants=gluetun.container After=gluetun.container Requires=gluetun.container BindsTo=gluetun.container # idk about this?: After=network-online.target Wants=network-online.target # Socket Wants=podman.socket After=podman.socket Requires=podman.socket [Container] ContainerName=homepage Pod=arr-stack.pod Image=ghcr.io/gethomepage/homepage:latest AutoUpdate=registry # Network Network=container:gluetun # UID/GID permissions User=1000 Group=1000 Environment=PUID=1000 Environment=PGID=1000 # EnvironmentFile=global.env Timezone=UTC Environment=TZ=Etc/UTC # EnvirontmentFile=homepage.env #Environment=LOG_LEVEL=debug Environment=HOMEPAGE_ALLOWED_HOSTS=gethomepage.dev #PublishPort=3000:3000 # Podman socket (recommended on Bazzite) Volume=%t/podman/podman.sock:/var/run/docker.sock:ro #Volume=/var/run/docker.sock:/run/user/1000/podman/podman.sock:ro #Volume=/%t/podman/podman.sock:/run/user/1000/podman/podman.sock:ro # Volume / Config directory SecurityLabelDisable=true Volume=%h/apps/homepage:/app/config:Z Volume=%h/apps/homepage/icons:/app/public/icons:Z [Service] Restart=on-failure TimeoutStartSec=300 #[Install] #WantedBy=default.target

Questions:

Thank you in advance for the answers, in case I don’t reply to your comment specifically.

#selfhosted

threaded - newest

just_another_person@lemmy.world on 21 Jan 11:52 next collapse

  1. No, you can’t “remove” your local networking interfaces from a container and expect it to use networking, anymore than you can remove the engine from a car, and expect it to drive. Set the default route of that container to some VPN tunnel interface, and you should be fine.
  2. I’m not seeing a link to any config
  3. 1000:1000 is usually the default user that is created for you when you setup a Linux system, so yes it’s reasonable for them to run as your user. It is NOT reasonable to run them as root, which is 0:0. Don’t do that.
Excaliburr@lemmy.dbzer0.com on 21 Jan 12:04 collapse

Thanks for the answer.

To

  1. Maybe I worded that poorly, I do understand that I can’t take out the engine haha (good analogy). I thought gluetun was supposed to set the default route (but it seems it either doesn’t or can’t), I’ll dig deeper into manually setting a default route for containers. My goal was to only have gluetun see my computer’s network and have the containers only see local network and gluetun’s tun0 network (with default routing through tun0). AFAIK pods share network namespaces, though, so that might not be possible? (even without pods?)

  2. The quadlets are in the spoiler at the bottom of the post. I’ll move the spoiler up a bit

  3. So they would be rootless containers, but have root access as 0:0, if I understand that correctly? linuxserver images require 0:0 or they won’t start, do you happen to know a workaround?

just_another_person@lemmy.world on 21 Jan 12:17 collapse

If they require root at start, it’s more than likely they need to access devices or sockets on the host on startup. If it’s then transitioning to another uid/gid for the actual runtime in the container - which looks to be happening - its not quite rootLESS because it obviously requires root.

I’m unfamiliar with the linuxserver images, so don’t understand the need for root here.

Excaliburr@lemmy.dbzer0.com on 21 Jan 12:40 collapse

I see, that makes sense. Thank you.

db_geek@norden.social on 21 Jan 12:19 next collapse

@Excaliburr 3. Are we talking here about rootless podman? If yes, containers are running on the host with the uid of current user, in your example with UID 1000.
Processes IN started containers are often running with UID=0, but this belongs to definition of used container and is only IN container.
PostgreSQL in Debian container is running with UID=999.
To avoid problems with created files in host file system, I'm using `UserNS=keep-id:uid=999,gid=999`, which maps files to the UID on host system.

Excaliburr@lemmy.dbzer0.com on 21 Jan 12:39 collapse

Yes, rootless podman.

Just to see if I understood correctly: So your container is running as 999, and UserNS=keep-id:uid=999,gid=999 maps 999 (the user used inside the container) to the host (in my case 1000). So any files the container creates have their permission set to 1000 and can be read/modified by both the host user (1000) and the container. UserNS=keep-id:uid=999,gid=999 ONLY maps the UIDs and does not set the UID of the container.

I think I understand now, thanks for the example, that helped.

db_geek@norden.social on 21 Jan 13:49 collapse

@Excaliburr Yes, you are right.

Do you have already a single container running with rootless podman?
Your given Arr-Stack seems to me relative complex when you are a beginner with podman.
Probably you could get some experience, when you start with just a single container.

In following posts are my personal quadlet settings for running Portainer, which could be helpful for getting insights of your running containers.

https://docs.portainer.io/start/install-ce/server/podman/linux

Excaliburr@lemmy.dbzer0.com on 21 Jan 21:35 collapse

I’ve been running Docker for various things on Windows before and I’m not a stranger to computers and tech in general, so that’s why I went this deep from the get go. The whole stack works and I’m like 90% sure that it works the way I want it to work, I just want to make it better (and “bullet proof”). I don’t have much experience with networking or Linux, though, so yeah, this was quite the challenge haha. Thanks again for the help, I’ll have a look at your quadlets.

theit8514@lemmy.world on 21 Jan 14:11 next collapse

Instead of a default gateway you can configure just your VPN IP address to go to your gateway. You might also need DNS servers depending on your setup.

Example: ip route add 1.1.1.1/32 via 192.168.1.1 dev eth0

Note that without a script this may be flaky if you’re using DNS to resolve the VPN. It might be better to have a script that resolves the IP(s) of the VPN and then adds routes.

That being said, your VPN software is usually designed to install routes that have higher priority so that they will get used before the local network. One such way is by adding half-internet routes (0.0.0.0/1 and 128.0.0.0/1) which get preferred over the larger default route. If you run ip route once connected you may see those routes present.

While I’m not sure if it works in rootless, take a look at binhex/arch-delugevpn project which has scripts to set up a similar network isolation environment.

Decronym@lemmy.decronym.xyz on 21 Jan 14:15 next collapse

Acronyms, initialisms, abbreviations, contractions, and other phrases which expand to something larger, that I’ve seen in this thread:

Fewer Letters More Letters
DNS Domain Name Service/System
IP Internet Protocol
VPN Virtual Private Network

3 acronyms in this thread; the most compressed thread commented on today has 6 acronyms.

[Thread #1014 for this comm, first seen 21st Jan 2026, 14:15] [FAQ] [Full list] [Contact] [Source code]

BlueAura@piefed.social on 21 Jan 23:44 collapse

You can run linuxserver.io images with their PUID and PGID environment variables set to run as root and rootless podman will remap it automatically to your host user without much trouble. I recommend splitting your Quadlet into multiple quadlets and pods based on application. Just keep in mind that network-wise sharing a pod is equivalent to being on the same machine. So, a gluetun and qbittorrent container can have the same network stack and communicate locally. Being on a pod an network allows you to use the container name for DNS and you don’t need to publish any ports. You can access different containers and ports pretty easily. This is where you’d want your arr apps. Publishing ports should probably be your last resort.