Skip to content

Rootless podman with SELinux #1824

Open

Description

@leonidas-o

OS Rocky Linux 9.5 (Blue Onyx)
podman-compose version 1.3.0
podman version 5.2.2

I managed to deploy Wazuh on a rootless podman setup with just little modifications. I would like to share it, also in hope that these modifications will be incorporated, as they shouldn't introduce a breaking change. With further testing, this could lead to official rootless podman support.


System setup
# as root
vi /etc/sysctl.conf
# add the follwing line
# vm.max_map_count=262144
reboot
# check if updated
sysctl -a |grep vm.max_map_count
# install new packages
dnf upgrade
dnf install podman python3-pip netavark aardvark-dns
vi /usr/share/containers/containers.conf # set network_backend = "netavark"

# The systemd user instance is killed after the last session for the user is closed. 
# The systemd user instance can be started at boot and kept running even after the 
# user logs out by enabling lingering using
loginctl enable-linger myuser
# as regular user
pip3 install --user podman-compose
pip3 install --upgrade podman-compose pip
sudop mkdir -p /opt/wazuh
sudo chown -R myuser:mygroup /opt/wazuh
cd /opt/wazuh
# as root
restorecon -vv -F -r /

Unprivileged Ports

sudo su -
# if needed to bind to ports < 1024, will be reset after reboot!
echo 443 > /proc/sys/net/ipv4/ip_unprivileged_port_start
# if needed permanently
echo "net.ipv4.ip_unprivileged_port_start=443" >> /etc/sysctl.conf

ulimit -aH
ulimit -aS
vi /etc/security/limits.conf
# Exit and re-login from the terminal for the change to take effect.
#<domain>      <type>  <item>         <value>
...
myuser        hard    memlock         -1
myuser        hard    nofile          655360

Firewall on VM

sudo su -
firewall-cmd --list-all
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
firewall-cmd --list-all

Initial Deployment

podman-compose --in-pod=true up -d without file changes:

cp: cannot create regular file '/certificates/admin-key.pem': Permission denied
cp: cannot create regular file '/certificates/admin.pem': Permission denied
cp: cannot create regular file '/certificates/root-ca.key': Permission denied
cp: cannot create regular file '/certificates/root-ca.pem': Permission denied
...

Modifications

add :z to generate-indexer-certs.yml bind mounts:

...
    volumes:
      - ./config/wazuh_indexer_ssl_certs/:/certificates/:z
      - ./config/certs.yml:/config/certs.yml:z

also add :z to all bind mounts inside docker-compose.yml (for nginx bind mounts :ro,z)

Final Deployment

podman-compose --in-pod=true up -d

Everything should come up as usual.

Activity

  1. self-assigned this
    on May 20, 2025
  2. c-bordon commented on May 20, 2025

    @c-bordon
    Contributor

    Hello, thank you for your contribution. We will analyze what was proposed and try to incorporate the changes for our version 5.0.0.

  3. leonidas-o commented on May 20, 2025

    @leonidas-o
    Author

    @c-bordon no thank you! I submitted a PR: #1834
    Wazuh manager in rootless podman seems to work so far.

    FYI: I'm looking into the agent right now. The plan is to create a daemonSet for Openshifts restricted env. The idea is to have a rootless image based on rockylinux:9-ubi-micro. Will see how that works out, so far SETGID cap is causing some issues.

  4. DevDorrejo commented on Oct 18, 2025

    @DevDorrejo

    Hello, I have quadlet setup, it runs, but need some tunning with the host where is installed, because not finish run because /proc and /sys, right now I don't have time to continue until next week, but if you found the solution here is the script [rootless] (My system use selinux enforce):

    @leonidas-o @c-bordon

    #!/usr/bin/env bash
    set -euo pipefail
    
    ## ========== ##
    ##    Init    ##
    ## ========== ##
    
    # Start podman socker and auto-update timer
    if [ $(systemctl --user is-enabled podman.socket) == "disabled" ]; then
    	systemctl --user enable --now podman.socket podman-auto-update.timer
    fi
    
    ## ================== ##
    ##    Environments    ##
    ## ================== ##
    APP_NAME="wazuh"
    
    ## Rutas
    ROOTLESS_PATH="${HOME}/.config/containers"
    QUADLET_PATH="${ROOTLESS_PATH}"/systemd/${APP_NAME}
    VOLUMES_PATH="${HOME}/.volumes"
    
    # container setup for rootless
    if [ ! -d "${ROOTLESS_PATH}" ]; then
    	rsync -avx --exclude="*storage.conf*" --ignore-existing /usr/share/containers "${ROOTLESS_PATH}"
    
    	#### CHANGE IF THE USER IS NOT INSTALLED IN A BTRFS PARTTION TO overlay"
    	printf '[storage]\ndriver = "btrfs"\n' >"${ROOTLESS_PATH}"/storage.conf
    fi
    
    # Create the systemd-quadlet spaces
    [ ! -d "${QUADLET_PATH}" ] && mkdir -vp "${QUADLET_PATH}"
    
    ## ============= ##
    ##    Volumes    ##
    ## ============= ##
    VOLUMES=(
    	"wazuh_api_configuration"
    	"wazuh_etc"
    	"wazuh_logs"
    	"wazuh_queue"
    	"wazuh_var_multigroups"
    	"wazuh_active_response"
    	"wazuh_wodles"
    	"filebeat_etc"
    	"filebeat_var"
    	"wazuh-indexer-data"
    	"wazuh-dashboard-config"
    	"wazuh-dashboard-custom"
    )
    for volume in "${VOLUMES[@]}"; do
    	# Creación del directorio
    	mkdir -p "${VOLUMES_PATH}/${volume}"
    
    	# Limpieza del nombre para el archivo .volume
    	volume_file_name=$(echo "${volume}" | tr '/' '-')
    
    	cat >${QUADLET_PATH}/${volume_file_name}.volume <<EOF
    [Unit]
    Description=Volume for ${APP_NAME} ${volume^}
    
    [Quadlet]
    # Disable dependency "podman-user-wait-network-online.service"
    DefaultDependencies=false
    
    [Volume]
    Type=none
    Device=${VOLUMES_PATH}/${volume}
    Options=rbind,rw
    EOF
    done
    
    ## ================== ##
    ##    Devs configs    ##
    ## ================== ##
    
    # 1) Light Clone
    tmpdir="$(mktemp -d)"
    command -v git >/dev/null 2>&1 || zypper in -y git-core
    git clone --depth 1 --filter=blob:none --sparse --single-branch --branch v4.13.1 \
    	https://github.com/wazuh/wazuh-docker.git "$tmpdir"
    cd "$tmpdir"
    
    # 2) Ask only one folder
    git sparse-checkout set single-node/config
    
    # 3) Copy the contain to the correct volume
    mkdir -p "${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs"
    cp -a single-node/config/* "${VOLUMES_PATH}/config/"
    
    # 4) Limpiar
    cd
    rm -rf "$tmpdir"
    
    ## ============= ##
    ##    Network    ##
    ## ============= ##
    
    cat >${QUADLET_PATH}/${APP_NAME,,}.network <<EOF
    [Unit]
    Description=${APP_NAME} Network
    After=network.target
    
    [Install]
    WantedBy=default.target
    EOF
    
    ## ========= ##
    ##    Pod    ##
    ## ========= ##
    cat >${QUADLET_PATH}/${APP_NAME,,}.pod <<EOF
    [Unit]
    Description=${APP_NAME} POD
    
    [Pod]
    PodName=${APP_NAME,,}
    Network=${APP_NAME,,}.network
    
    # Wazuh Manager - Ports
    PublishPort=1514:1514
    PublishPort=1515:1515
    PublishPort=514:514/udp
    PublishPort=55000:55000
    
    # Wazuh Indexer - Ports
    PublishPort=9200:9200
    
    # Wazuh Dasshboard - Ports
    PublishPort=443:5601
    
    [Service]
    # Restart service when sleep finishes
    Restart=always
    # Extend Timeout to allow time to pull the image
    TimeoutStartSec=900
    
    [Install]
    WantedBy=default.target
    EOF
    
    ## =============== ##
    ##    Container    ##
    ## =============== ##
    
    ##
    ## Wazuh Manager
    ##
    cat >${QUADLET_PATH}/${APP_NAME,,}-manager.container <<EOF
    [Unit]
    Description="${APP_NAME} Manager"
    
    [Service]
    # Restart service when sleep finishes
    Restart=always
    # Extend Timeout to allow time to pull the image
    TimeoutStartSec=900
    
    [Container]
    Pod=${APP_NAME,,}.pod
    
    # Imagen y actualización
    Image=docker.io/wazuh/wazuh-manager:4.13.1
    AutoUpdate=registry
    Pull=newer
    
    # Ulimit
    Ulimit=memlock=-1:-1
    Ulimit=nofile=65536:65536
    
    # Variables de Entornos
    Environment="INDEXER_URL=https://localhost:9200"
    Environment="INDEXER_USERNAME=admin"
    Environment="INDEXER_PASSWORD=SecretPassword"
    Environment="FILEBEAT_SSL_VERIFICATION_MODE=full"
    Environment="SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem"
    Environment="SSL_CERTIFICATE=/etc/ssl/filebeat.pem"
    Environment="SSL_KEY=/etc/ssl/filebeat.key"
    Environment="API_USERNAME=wazuh-wui"
    Environment="API_PASSWORD=MyS3cr37P450r.*--"
    
    # Volúmenes
    Volume=wazuh_api_configuration.volume:/var/ossec/api/configuration:Z
    Volume=wazuh_etc.volume:/var/ossec/etc:Z
    Volume=wazuh_logs.volume:/var/ossec/logs:Z
    Volume=wazuh_queue.volume:/var/ossec/queue:Z
    Volume=wazuh_var_multigroups.volume:/var/ossec/var/multigroups:Z
    Volume=wazuh_active_response.volume:/var/ossec/active-response/bin:Z
    Volume=wazuh_wodles.volume:/var/ossec/wodles:Z
    Volume=filebeat_etc.volume:/etc/filebeat:Z
    Volume=filebeat_var.volume:/var/lib/filebeat:Z
    Volume=${VOLUMES_PATH}/./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem:Z
    Volume=${VOLUMES_PATH}/./config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem:Z
    Volume=${VOLUMES_PATH}/./config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key:Z
    Volume=${VOLUMES_PATH}/./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf:Z
    EOF
    
    ##
    ## Wazuh Indexer
    ##
    cat >${QUADLET_PATH}/${APP_NAME,,}-indexer.container <<EOF
    [Unit]
    Description="${APP_NAME} indexer"
    
    [Service]
    # Restart service when sleep finishes
    Restart=always
    # Extend Timeout to allow time to pull the image
    TimeoutStartSec=900
    
    [Container]
    Pod=${APP_NAME,,}.pod
    
    # Imagen y actualización
    Image=docker.io/wazuh/wazuh-indexer:4.13.1
    AutoUpdate=registry
    Pull=newer
    
    # Ulimit
    Ulimit=memlock=-1:-1
    Ulimit=nofile=65536:65536
    
    # Variables de entornor del contenedor
    Environment="OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
    
    # Volúmenes
    Volume=wazuh-indexer-data.volume:/var/lib/wazuh-indexer:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml:Z
    EOF
    
    ##
    ## Wazuh Dashboard
    ##
    
    cat >${QUADLET_PATH}/${APP_NAME,,}-dashboard.container <<EOF
    [Unit]
    Description="${APP_NAME} dashboard"
    
    [Service]
    # Restart service when sleep finishes
    Restart=always
    # Extend Timeout to allow time to pull the image
    TimeoutStartSec=900
    
    [Container]
    Pod=${APP_NAME,,}.pod
    
    # Imagen y actualización
    Image=docker.io/wazuh/wazuh-dashboard:4.13.1
    AutoUpdate=registry
    Pull=newer
    
    # Variables de entornor del contenedor
    Environment="INDEXER_USERNAME=admin"
    Environment="INDEXER_PASSWORD=SecretPassword"
    Environment="WAZUH_API_URL=https://localhost"
    Environment="DASHBOARD_USERNAME=kibanaserver"
    Environment="DASHBOARD_PASSWORD=kibanaserver"
    Environment="API_USERNAME=wazuh-wui"
    Environment="API_PASSWORD=MyS3cr37P450r.*-"
    
    # Volúmenes
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:Z
    Volume=${VOLUMES_PATH}/config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:Z
    Volume=${VOLUMES_PATH}/config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml:Z
    Volume=wazuh-dashboard-config.volume:/usr/share/wazuh-dashboard/data/wazuh/config:Z
    Volume=wazuh-dashboard-custom.volume:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom:Z
    EOF
    
    ## =========== ##
    ##    Tools    ##
    ## =========== ##
    
    podman run --rm -it --name wazuh-certs-generator \
    	-v ${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/:/certificates/:Z \
    	-v ${VOLUMES_PATH}/config/certs.yml:/config/certs.yml:Z \
    	docker.io/wazuh/wazuh-certs-generator:0.0.2
    
    ## ================== ##
    ##    Verify Units    ##
    ## ================== ##
    systemctl --user daemon-reload
    
    # Verify if the quadlet it is generated correctly
    /usr/libexec/podman/quadlet -dryrun -user
    
  5. leonidas-o commented on Oct 19, 2025

    @leonidas-o
    Author

    don't know why you are building that bash script, I simply use a systemd service file and podman-compose to utilize the already existing docker-compose.yml files (either in multi-node or single-node dir):

    [Unit]
    Description=Podman-compose wazuh.service
    Wants=network.target
    After=network-online.target
    
    [Service]
    Type=oneshot
    RemainAfterExit=true
    WorkingDirectory=/opt/wazuh/wazuh-docker/multi-node
    ExecStart=/home/nuvariant/.local/bin/podman-compose --in-pod=true up -d
    ExecStop=/home/nuvariant/.local/bin/podman-compose down
    
    [Install]
    WantedBy=default.target
    
  6. luckylinux commented on Feb 15, 2026

    @luckylinux

    @leonidas-o: probably because just like I had to deal with podman-compose, in some Situations and Configurations it turned out to be completely unreliable 😕. I was getting random Containers Crashes at Boot Time for one ...

    Anyways, after around 2 Days of messing around with Podman, I had to give up on using a Pod (which is my preferred Solution) because some People decided to use 2 different UID/GID within the multi Node Stack of Containers 👎:

    • UID 999 / GID 999:
      • wazuh-master: USER wazuh
      • wazuh-worker-01: USER wazuh
    • UID 1000 / GID 1000:
      • wazuh-indexer-01: USER wazuh-indexer
      • wazuh-indexer-02: USER wazuh-indexer
      • wazuh-indexer-03: USER wazuh-indexer
      • wazuh-dashboard: USER wazuh-dashboard

    As such, in a Pod Configuration, it's NOT possible to remap different UID in the Container to the same UID on the HOST (using either UIDMap or UserNS=keep-id).

    Now I "took" each Container out of the Pod and switched to Bridge Networking instead, which has the additional Issue to require Systemd Socket Activation in Case you want to see the correct Remote (Source) IP Address and not just some random 10.x.y.z Bridge Gateway Address.

    You can see a bit more about Systemd Socket Activation in Podman here:

    Anyway, my Problem right now is that, even though the Permissions seem "kind of" OK, I have Containers trying to reach to [::1]:9200 for whatever weird Reason 😕.

    Furthermore on the Master for Instance there is in /etc/filebeat/filebeat.yml:

    # Wazuh - Filebeat configuration file
    filebeat.modules:
      - module: wazuh
        alerts:
          enabled: true
        archives:
          enabled: false
    
    setup.template.json.enabled: true
    setup.template.overwrite: true
    setup.template.json.path: '/etc/filebeat/wazuh-template.json'
    setup.template.json.name: 'wazuh'
    setup.ilm.enabled: false
    output.elasticsearch:
      hosts: ['https://wazuh.indexer:9200']
      username: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
      password: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
      #ssl.verification_mode:
      #ssl.certificate_authorities:
      #ssl.certificate:
      #ssl.key:
    
    logging.metrics.enabled: false
    
    seccomp:
      default_action: allow
      syscalls:
      - action: allow
        names:
        - rseq
    

    While my wazuh-master.container Configuration contains this:

    Environment=WAZUH_INDEXER_HOSTS=wazuh-indexer-01:9200,wazuh-indexer-02:9200,wazuh-indexer-03:9200
    

    On the other Hand wazuh-dashboard shows this (repeated several Times):

    "type":"log","@timestamp":"2026-02-15T15:52:31Z","tags":["error","opensearch","data"],"pid":50,"message":"[ConnectionError]: connect ECONNREFUSED ::1:9200"}
    

    I cannot exclude that I initialized the entire Container Stack once with the wrong Settings for some Environment Variables. But given that '/etc/filebeat is not bind-mounted from something on the Host, that also makes little sense 😕.

    For some Reason, the Dashboard is trying to reach Localhost.

    Did I miss some Part of the Docker Multi Node Setup 😕 ?

    EDIT 1: some Files have been deleted from the main Branch and the only way to find them is to manually select the 4.14.3 Tag:

    https://github.com/wazuh/wazuh-docker/blob/40c88305bc2454109ac1a4a6f89ddbda64bdad54/docs/ref/configuration/configuration-files.md

    https://github.com/wazuh/wazuh-docker/blob/v4.14.3/multi-node/docker-compose.yml

    EDIT 2: seems like all Environment Variables are ignored for the Indexer, I needed to mount a /usr/share/wazuh-indexer/config/opensearch.yml into the Container to set the correct Node's Name

  7. added a commit that references this issue on Jun 26, 2026
    16691bd
  8. added 4 commits that reference this issue on Aug 6, 2026
    37d9d64
    73ff4be
    cc264a2
    241b724
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions