Skip to content

[Feature]: add native Podman deployment support #1104

Description

@excla1mmm

Problem or Motivation

MikoPBX currently does not detect Podman as a container runtime.

Docker is detected by checking /.dockerenv, while Podman provides the standard /run/.containerenv marker. As a result, an unmodified MikoPBX image running in Podman is not recognized as a container and may attempt to manage networking, firewall, storage, time, and other host-controlled resources.

A real deployment was tested with:

  • MikoPBX 2026.3.40
  • Ubuntu 26.04 LTS
  • Podman 5.7.0
  • ARM64/aarch64
  • Rootful Podman
  • --network=host
  • Persistent /cf and /storage bind mounts

The installation works when an artificial Docker marker is mounted:

/var/spool/mikopbx/podman.dockerenv -> /.dockerenv:ro

With this workaround, the following were successfully verified:

  • Web interface over HTTPS
  • REST API and JWT authentication
  • Asterisk 22.8.2
  • SIP, SIP TLS, AMI, and Asterisk HTTP interfaces
  • Persistent configuration and storage
  • Container restart
  • ARM64 image compatibility

However, mounting /.dockerenv makes MikoPBX report Podman as Docker and couples Podman deployment to Docker-specific behavior.

Native Podman support is needed without changing the existing Docker detection, Docker provisioning, Docker entrypoint, or Docker deployment behavior.

Proposed Solution

Add Podman as a separate, explicitly detected runtime. Existing Docker behavior should remain unchanged.

1. Add separate Podman detection

Keep the existing method unchanged:

public static function isDocker(): bool
{
    return file_exists('/.dockerenv');
}

Add a new method:

public static function isPodman(): bool
{
    return file_exists('/run/.containerenv')
        && !file_exists('/.dockerenv');
}

The additional /.dockerenv check provides a safe migration path: existing Podman installations using the compatibility mount remain in Docker mode until that mount is removed.

Update only the generic container checks:

public static function isContainer(): bool
{
    return self::isDocker()
        || self::isPodman()
        || self::isLxc();
}

Add explicit Podman handling to capability methods such as:

  • canManageNetwork()
  • canManageFirewall()
  • reboot and shutdown handling
  • storage handling
  • DNS and NTP configuration
  • network configuration
  • container-specific UI restrictions

Existing Docker branches should remain unchanged.

2. Add an independent Podman provisioning provider

Create:

src/Core/System/CloudProvisioning/PodmanCloud.php

PodmanCloud should:

  • extend CloudProvider directly;
  • use System::isPodman();
  • have CloudID = 'PodmanCloud';
  • apply environment overrides on every container start;
  • apply early Redis, Beanstalk, and NATS port overrides;
  • use ProvisioningConfig::fromEnvironment();
  • report the runtime as Podman.

DockerCloud.php should not be changed and PodmanCloud should not inherit from it.

Add Podman as a separate provider in CloudProvisioning:

if (System::isPodman()) {
    PodmanCloud::applyPortOverrides();
}

if (System::isDocker()) {
    DockerCloud::applyPortOverrides();
}

And:

if (System::isPodman()) {
    PodmanCloud::applyEnvironmentOverrides();
} elseif (System::isDocker()) {
    DockerCloud::applyEnvironmentOverrides();
} elseif (System::isLxc()) {
    LxcCloud::applyProxmoxOverrides();
}

3. Add separate shell detection

Keep is_docker() unchanged and add:

is_podman()
{
    [ -f "/run/.containerenv" ] && [ ! -f "/.dockerenv" ]
}

Update the generic detector:

is_container()
{
    is_docker || is_podman || is_lxc
}

Update pbx-env-detect to return the actual runtime:

if [ -f "/.dockerenv" ] || [ -n "${DOCKER_CONTAINER:-}" ]; then
    echo "docker"
    return
fi

if [ -f "/run/.containerenv" ]; then
    echo "podman"
    return
fi

The same separate Podman detection should be added to pbx-message.

4. Add a dedicated Podman entrypoint

Create:

src/Core/System/RootFS/sbin/podman-entrypoint

The Podman entrypoint should start ContainerEntrypoint.php, monitor the existing reboot/shutdown flags, and provide guarded signal cleanup.

It should prevent recursive cleanup through the EXIT trap:

cleanup_started=0

cleanup()
{
    if [ "$cleanup_started" -eq 1 ]; then
        return 0
    fi

    cleanup_started=1
    trap - 2 15 1 3 EXIT

    /sbin/freestorage 'doNotUnMount'
    /sbin/freeupoffload

    sleep 4
    exit 0
}

The existing docker-entrypoint should remain unchanged.

5. Handle Podman storage separately

In Podman, disk usage should be calculated directly from the /storage bind mount:

if (System::isPodman()) {
    $disk['free_space'] = Storage::getFreeSpace('/storage');
} elseif ($disk['sys_disk'] === true) {
    // Existing Docker/LXC/VM logic remains unchanged.
}

This prevents container block devices such as /dev/vda2 from being incorrectly transformed into partition names such as /dev/vda24.

6. Add an official Podman Quadlet

Add:

deploy/podman/mikopbx.container

The supported configuration should use:

  • Rootful Podman
  • systemd Quadlet
  • Network=host
  • Persistent /cf and /storage
  • The dedicated podman-entrypoint
  • Versioned MikoPBX image tags
  • systemd restart policy

Example:

[Unit]
Description=MikoPBX Podman
Wants=network-online.target
After=network-online.target

[Container]
Image=ghcr.io/mikopbx/mikopbx:2026.3.40
ContainerName=mikopbx
HostName=mikopbx
Network=host

Entrypoint=/bin/sh
Exec=/sbin/podman-entrypoint

Volume=/var/spool/mikopbx/cf:/cf
Volume=/var/spool/mikopbx/storage:/storage

Environment=SSH_PORT=23
Environment=ID_WWW_USER=1002
Environment=ID_WWW_GROUP=1002

Pull=missing

[Service]
Restart=always
RestartSec=10
TimeoutStartSec=900
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target

No artificial /.dockerenv mount should be required.

7. Expose the runtime in diagnostics

Keep existing Docker fields unchanged and add:

{
  "isDocker": false,
  "isPodman": true,
  "isContainer": true,
  "containerRuntime": "podman"
}

The system banner and diagnostic information should display Podman rather than Docker.

8. Add integration coverage

Test matrix:

  • Docker amd64
  • Docker arm64
  • Podman amd64
  • Podman arm64
  • LXC
  • VM or bare metal

Podman acceptance checks:

test -f /run/.containerenv
test ! -f /.dockerenv
test "$(/sbin/pbx-env-detect --nocache --type)" = "podman"

Also verify:

  • HTTPS returns HTTP 200
  • REST authentication works
  • Asterisk starts successfully
  • Environment provisioning is applied
  • /cf and /storage survive restart
  • Graceful stop completes without forced SIGKILL
  • No false low-disk-space warning is generated
  • The container starts automatically after a host reboot
  • Existing Docker tests and behavior remain unchanged

Alternatives Considered

Mounting an artificial /.dockerenv

This workaround was tested and works:

/var/spool/mikopbx/podman.dockerenv -> /.dockerenv:ro

However, it makes MikoPBX identify Podman as Docker and depends on implementation details intended for another runtime.

Extending System::isDocker() to include Podman

For example:

return file_exists('/.dockerenv')
    || file_exists('/run/.containerenv');

This would be a small change, but it would mix two different runtimes, make diagnostics inaccurate, and could unintentionally affect existing Docker behavior.

This alternative is intentionally not proposed.

Reusing or inheriting from DockerCloud

Podman and Docker use similar environment provisioning, but inheriting PodmanCloud from DockerCloud would couple the implementations.

A separate PodmanCloud is preferred so existing Docker behavior can remain unchanged.

Rootless Podman

Rootless Podman was not selected as the initial supported configuration because MikoPBX requires low ports, host networking, SIP/RTP, and predictable UID/GID handling.

The initial supported target should be rootful Podman with systemd and cgroup v2.

Area

Docker / Containers

Additional Context

The test environment was a clean ARM64 virtual machine:

  • Ubuntu 26.04 LTS
  • Linux aarch64
  • Podman 5.7.0
  • cgroup v2
  • systemd
  • crun
  • netavark
  • MikoPBX 2026.3.40 arm64

The container was started with:

--network=host
--restart=always
/cf bind mount
/storage bind mount
SSH_PORT=23
ID_WWW_USER and ID_WWW_GROUP

Observed results:

  • Initial startup completed successfully.
  • Restart startup completed in approximately 7.4 seconds.
  • Web interface returned HTTP 200 over HTTPS.
  • HTTP correctly redirected to HTTPS.
  • REST API returned the expected 401 response without authentication.
  • JWT login and an authenticated API request succeeded.
  • Asterisk and all Monit-managed services were operational.
  • Persistent configuration survived container restart.
  • TCP ports 23, 80, 443, 5060, 5061, 8088, and 8089 were reachable.
  • The ARM64 image worked correctly.

Observed Podman-related issues:

  1. Native Podman was not detected without mounting /.dockerenv.
  2. Container shutdown did not complete within 30 seconds and Podman used SIGKILL.
  3. A false low-storage warning was generated for /dev/vda2, although approximately 18 GB was available.
  4. The workaround caused diagnostics to report Docker instead of Podman.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions