[GUIDE] Running Podman Containers on OpenWRT 24.10.2

I recently went through the process of getting Podman running properly on OpenWrt 24.10.2 and configuring it to behave well with OpenWrt’s native networking, firewall setup and daemon startup and configuration logic.
Since some of the information at https://openwrt.org/docs/guide-user/virtualization/docker_host was outdated, I’ve updated parts of it there, and I'm sharing this more complete guide here as well, for those who want a more practical walkthrough.


Installing Podman

First, install Podman using opkg:

opkg install podman

It will pull in a number of dependencies, including networking tools and container runtimes.


Basic Configuration

Storage Setup

Update Podman’s storage path to point to a disk with enough space, this folder, depending how careful you'll select containers, will tend to take quite some space:

/etc/containers/storage.conf

graphroot = "/home/podman/storage"

Then create the directory:

mkdir -p /home/podman/storage

Regular Cleanup

Add cleanup tasks to cron:

crontab -e

Add:

# Podman cleanup
10 0 * * 0 /usr/bin/podman system prune --volumes -f > /dev/null 2>&1
20 0 * * 0 /usr/bin/podman image prune -a -f > /dev/null 2>&1

Networking Setup

We want Podman to use a static bridge and be fully manageable by OpenWrt’s network config and firewall.
Here’s how to set it up:

/etc/containers/networks/podman.json

{
     "name": "podman",
     "driver": "bridge",
     "network_interface": "podman0",
     "subnets": [
          {
               "subnet": "192.168.11.0/24",
               "gateway": "192.168.11.1"
          }
     ],
     "ipv6_enabled": false,
     "internal": false,
     "dns_enabled": true,
     "ipam_options": {
          "driver": "host-local"
     }
}

/etc/containers/containers.conf

[network]
network_backend = "netavark"
firewall_driver = "none"
network_config_dir = "/etc/containers/networks/"
default_network = "podman"
default_subnet = "192.168.11.0/24"
default_rootless_network_cmd = "slirp4netns"

/etc/config/network

config device
        option type 'bridge'
        option name 'podman0'
        option bridge_empty '1'
        option ipv6 '0'

config interface 'podman0'
        option proto 'static'
        option device 'podman0'
        option ipaddr '192.168.11.1'
        option netmask '255.255.255.0'

/etc/config/firewall

Be careful that zone names for other zones must match your configuration

config zone
        option name 'Podman'
        option input 'DROP'
        option output 'ACCEPT'
        option forward 'REJECT'
        list network 'podman0'

config forwarding
        option src 'Podman'
        option dest 'Internet'

config forwarding
        option src 'lan'
        option dest 'Podman'

config rule
        option name 'DNS to Podman'
        option src 'Podman'
        option dest_port '53'
        option target 'ACCEPT'

Giving Access to Container Ports

Since we're using firewall_driver = "none", Podman won't open ports automatically.
If a container needs to be reachable, you'll need to manually create rules.
For this reason I am explicitely adding an IP to each container, more of this later.

Example:

config rule
        option src 'VPN'
        option name 'NRPE to Nagios'
        option dest_port '5666'
        option target 'ACCEPT'
        list proto 'tcp'
        list src_ip '192.168.0.5'
        option dest 'Podman'

config redirect
        option dest 'Podman'
        option target 'DNAT'
        option name 'Serve NRPE from container'
        option family 'ipv4'
        list proto 'tcp'
        option src 'VPN'
        option src_dport '5666'
        option dest_ip '192.168.11.2'
        option dest_port '5666'
        option src_ip '192.168.0.5'

Podman Init Script

Here’s how I run containers at boot using an init script.
The set of parameters it understands is basic, and it's not smart when it comes to enforce containers to have name and images, but it's good enough for me and easy to mod in case needed.
It is configurable via /etc/config/containers and is pretty flexible.

/etc/init.d/containers

START=90
STOP=20
USE_PROCD=1

NAME=containers
PROG=/usr/bin/podman

. /lib/functions.sh

start_service() {
    # At boot time, wait longer for dependencies
    local max_wait=60
    local count=0

    logger -t "$NAME" "Waiting for system readiness"

    # Wait for basic system services
    while [ $count -lt $max_wait ]; do
        # Check if essential services are ready
        if [ -S /var/run/ubus/ubus.sock ] && pgrep -f "ubusd" >/dev/null && \
           [ -d /sys/class/net ] && $PROG system info >/dev/null 2>&1; then
            break
        fi
        sleep 1
        count=$((count + 1))
    done

    if [ $count -ge $max_wait ]; then
        logger -t "$NAME" "Timeout waiting for system services and podman to be ready"
        return 1
    fi

    logger -t "$NAME" "Starting containers service"

    config_load containers
    config_foreach start_container container
}

start_container() {
    local cfg="$1" enabled name

    config_get enabled "$cfg" enabled 0
    config_get name "$cfg" name "$cfg"
    config_get image "$cfg" image "$cfg"

    config_get dns "$cfg" dns ""
    config_get hostname "$cfg" hostname ""
    config_get image "$cfg" image ""
    config_get ip "$cfg" ip ""
    config_get memory "$cfg" memory ""
    config_get pid "$cfg" pid ""
    config_get pull "$cfg" pull "missing"
    config_get restart "$cfg" restart ""

    envs=""
    append_env() {
        envs="$envs -e $1"
    }
    config_list_foreach "$cfg" env append_env

    vols=""
    append_vol() {
        vols="$vols -v $1"
    }
    config_list_foreach "$cfg" volume append_vol

    [ "$enabled" -eq 0 ] && return 0

    logger -t "$NAME" "Starting container $name"
    logger -t "$NAME" "Pulling latest version for $name - $image"

    $PROG pull $image  >/dev/null 2>&1 || logger -t "$NAME" "Pulling failed for $image"

    # Build the Podman command
    podman_cmd="$PROG run -d"
    [ -n "$dns" ] && podman_cmd="$podman_cmd --dns $dns"
    [ -n "$hostname" ] && podman_cmd="$podman_cmd --hostname $hostname"
    [ -n "$ip" ] && podman_cmd="$podman_cmd --ip $ip"
    [ -n "$memory" ] && podman_cmd="$podman_cmd --memory $memory"
    [ -n "$pid" ] && podman_cmd="$podman_cmd --pid $pid"
    [ -n "$restart" ] && podman_cmd="$podman_cmd --restart $restart"
    podman_cmd="$podman_cmd $envs $vols --name $name $image"

    logger -t "$NAME" "Running '$podman_cmd'"

    procd_open_instance "$name"
    procd_set_param command sh -c "
        $podman_cmd || exit 1
        exec $PROG wait '$name'
    "
    procd_set_param respawn
    procd_close_instance
}

stop_service() {
    config_load containers
    config_foreach stop_container container
}

stop_container() {
    local cfg="$1" name
    config_get name "$cfg" name "$cfg"
    $PROG stop "$name" 2>/dev/null
    $PROG rm "$name" 2>/dev/null
}

# Standard init handlers
start() { start_service; }
stop() { stop_service; }
restart() { stop; start; }
reload() { stop; start; }

Make it executable:

chmod +x /etc/init.d/containers

And enable it for next boot

/etc/init.d/containers enable

Example Container Config

/etc/config/containers

config container 'test'
    option enabled '1'
    option name 'test'
    option dns '9.9.9.9'
    option image 'quay.io/podman/hello'
    option memory '64m'
    option hostname 'hello'
    option ip '192.168.11.2'
    option pid 'host'
    option restart 'unless-stopped'
    list env 'TZ=Europe/Amsterdam'
    list volume '/etc/openwrt_release:/etc/openwrt_release:ro'
    list volume '/home/test_container/etc/:/etc/test'
    list cmd 'ping'
    list cmd '-c'
    list cmd '4'
    list cmd '192.168.11.1'

Preserving Configs During Sysupgrade

Add this to /etc/sysupgrade.conf:

/etc/containers
/etc/config/containers
/etc/init.d/containers
/home/

Hope this helps someone!

3 Likes

Great work, would you consider adding this to the official wiki?

Wiki is a good first step I am wondering though whether we should clean up some of the scripts and integrate them into the package.

It's a good idea but reviewing the changes might be a lenghty process.

I spent some time during holidays to make podman work and write this document, I am happy to bring this further but I will need some help if this is an involved task.
Let me know what’s your suggestion and if someone can help :slight_smile:

My suggestion is again to do it on the wiki. Forum is indeedindexed by searched engine and most likely LLMs but not the best place to share colaborative documentation.

The current guide on the wiki (https://openwrt.org/docs/guide-user/virtualization/docker_host?s[]=podman) is a bit different from the approach I have taken.
I would have to either spend time understanding overlaps, edit and merge, or delete what’s there and replace, but I am not comfortable enough doing so.
What now?

add a new topic https://openwrt.org/docs/guide-user/virtualization/podman

Done. Cheers :slight_smile:

1 Like

Great work - on behalf of this community- thank you!

I’ve updated the howto in order to support IPv6 and aardvark-dns

2 Likes

After fiddling around with this approach I spotted major weaknesses though.

Managing the podman0 interface via UCI AND via podman at the same time is really hacky and unstable. You may not use and podman network create or podman network rm commands as they will break things.

You cannot podman network create as this will fail, because the podman command realizes, that the same subnet is already being used. That is why the interface had already been created by UCI. I assume the original author of the guide decided to manually create the network json exactly because of this.

You should not use the podman network rm subcommand, because this will not only remove the network within the podman realm but it will also be gone in general.

Another problem is that if you for some reason change the network configuration in UCI and apply that configuration, your podman containers will loose connectivity. Restarting the container fixes things again, but well…

Also: For IPv6 to work you also need to set sourcefilter to 0 and enable IPv6 masquerading on the wan6 interface. I will add this to the docs later.

For now I will try to search another way as I believe this approach is inherently broken and should not be used. I assume another network driver could do the trick but I will have to tinker around more. Until then.

Happy xmas

FYI my testings were on 25.12rc1.

I updated the docs again because sourcefilter=0 for wan6 interface is necessary as is masq6 for WAN firewall zone.

Also I added a workaround via iface hotplug.d for when OpenWrt restarts network. Enjoy!

Update:
Not trying to be a solo-entertainer here but I wanted to give some updates.

I’ve been deploying plenty of containers onto my x86 based development OpenWrt device. I do that fully via ansible so no docker-compose.yaml.

Then I vibe-coded a LUCI plugin, just for the lolz

So far containers run robustly. I will keep tinkering around, next will be an ARM based device with a dedicated NVME for the persistent containerdata. Backup of this very containerdata normally I do via the borgmatic borg wrapper. This borg setup is also fully containerized and deployed using the same ManageMyNetwork podman role as for the regular workload containers.

Full Infrastructure as Code OpenWrt podman container setup.

1 Like