Linux Containers for the Sysadmin Who Missed the Boat: LXC, Podman, and Docker in 2026

root@rhel:~# podman ps
root@rhel:~# podman ps # what is running, right now, on this host

Linux Containers for the Sysadmin Who Missed the Boat
LXC, Podman, and Docker in 2026

If you ran Solaris zones, you already understand containers. A zone was an isolated environment sharing one kernel, with its own filesystem, network, and resource limits. A Linux container is the same idea, at a different scale, with a different packaging story. This guide is for the sysadmin, not the developer: running services in containers, keeping them up, keeping their data safe, and knowing when not to containerize at all. No Kubernetes. No CI pipeline. One host, real services, and the three tools that matter in 2026.

# the lineage, in one paragraph — zones to containers
Solaris zones (2005) proved the model: lightweight isolation on a shared kernel, with resource controls and a network stack per zone. FreeBSD jails did the same on BSD. Linux took longer to get the kernel pieces — namespaces (each container sees its own processes, network, mounts, users) and cgroups (each container gets a bounded share of CPU and memory) — and then LXC wrapped them into something usable in 2008. Docker (2013) made containers portable by adding the image: a layered filesystem you could build once and run anywhere. Podman (2018, Red Hat) kept the image format and dropped the daemon. That is the whole family tree, and if you ran zones, you have already done this job — the vocabulary changed, the work did not.
# the three tools, honestly compared — pick one per job, not one for everything
LXC / LXD / Incus — the full-system container. Closest to a zone: a container that behaves like a whole machine, with its own init, its own services, and a lifecycle you manage like a host. Use it when you want a throwaway test system, a legacy service that expects to be a machine, or a clean-room environment that is not an application in a box. LXD is Canonical’s version; Incus is the community fork that followed the LXD licence change — same idea, different steward. On RHEL you will reach for this least, but it is the right tool when the job is “a whole system”, not “one service”.
Podman — the RHEL-native choice, and the default answer on this site’s stack. Daemon-free, rootless by design, and speaking the same image format as Docker, so every Docker image runs under Podman and every Docker command has a Podman equivalent. On RHEL 8, 9, and 10 it is the container tool in the base repositories; Docker is not — Docker needs its own repository on RHEL, which is itself a statement about where Red Hat thinks you should be. Rootless matters: a container that runs as your user, not as root, so a breakout does not hand over the host. And systemd integration is native, not bolted on — see the quadlets section below.
Docker — the one everyone else already uses. The image format, the Hub, and the ecosystem are Docker’s, and that is a real advantage: if a project ships a container, it ships a Docker image, and it will run under Podman anyway. The cost is the daemon: a root-running service that every container depends on, and a socket that is effectively root access to the host for anyone who can reach it. On a single host you administer, that is a risk you can manage; on a shared host, it is a risk you should not take. Docker is not wrong. It is the tool with the most history and the weakest privilege story, and you should choose it knowing that.
Want a whole system?
LXC / Incus — closest to a zone
Want to run services on RHEL?
Podman — rootless, daemon-free, systemd-native
Want the biggest ecosystem?
Docker — but mind the daemon and the socket

# podman, the working pattern — one service, one container, data outside it
Run it rootless, as a service user. Create a dedicated user for the service, give it subuid/subgid ranges (/etc/subuid, /etc/subgid — Podman sets these up when you install it), and run the container as that user. The container’s root is your user on the host, not host root. A breakout from a rootless container lands in an unprivileged account, which is the whole point.
Keep the data outside the container. A container’s filesystem is disposable by design — update the image, the old filesystem goes away. Anything you care about lives in a volume or a bind mount on the host, backed up like any other data:
podman run -d --name web -p 8080:80 -v /srv/web-data:/var/www/html:Z nginx:latest
The :Z matters on RHEL: it relabels the volume for SELinux so the container can actually read it, and forgetting it is the single most common “Podman cannot see my files” bug on a RHEL host. The data is in /srv/web-data, on the host, in your backup. The container is replaceable. That separation is the discipline; everything else is syntax.
Pin the image, or it will move under you. nginx:latest is a different image next month than it is today. For a service you run, pin a version (nginx:1.26) or a digest, and update deliberately — the same way you would patch a package, not by accident on a restart. An unpinned image is an unplanned upgrade waiting for a reboot.
# quadlets: a container that behaves like a service — the systemd part, and why it matters
The problem quadlets solve: a container started by hand does not start on boot, does not restart on failure, and does not show up in systemctl status. You can write a systemd unit that runs podman run by hand, and many of us did — but Podman now generates the unit for you from a short declarative file, and that is the right way to do it on RHEL 9 and 10.
A quadlet is a .container file in /etc/containers/systemd/ (system-wide) or ~/.config/containers/systemd/ (per-user, rootless):
[Container]
Image=docker.io/library/nginx:1.26
PublishPort=8080:80
Volume=/srv/web-data:/var/www/html:Z
[Service]
Restart=always
[Install]
WantedBy=default.target

Then systemctl daemon-reload and systemctl enable --now web, and the container is a service: it starts on boot, restarts on failure, logs to the journal where journalctl can find it, and answers to the same systemctl status web as everything else on the box. That is the point — a container that is administered like a service, not a process you started once and hope is still running.
The journal connection is not incidental. A quadlet-managed container’s logs go to the journal, so the journalctl guide’s filtering — journalctl -u web -p err --since today — works on containers exactly as it works on native services. One log story for the whole host, containers included.
If a container is not in systemd, it is not a service. It is a process you are babysitting. Quadlets are the difference, and they take five lines.
# networking: when to bridge, when to leave it alone — the three sane choices
Published ports (the default, and usually right). -p 8080:80 maps a host port to a container port, and the container lives on its own private network. The host’s firewall — firewalld on RHEL — sees the published port, and the firewalld guide’s rules apply to it exactly as they apply to a native service: open 8080 to the networks that should reach it, nobody else. This is the sane default for a web service behind a reverse proxy.
Host networking (rare, and honest about why). --network=host gives the container the host’s network stack directly — no port mapping, no isolation. Use it only when the service genuinely needs it (some network tools, some discovery protocols), and understand what you gave up: the container can now see and bind the host’s ports, and the firewall rules apply to the host, not to a container boundary.
A dedicated network for containers that talk to each other. podman network create app-net, then run the web container and the database container on it, and they reach each other by name. The database listens only on that network — no published port, no exposure to the host at all — which is the container version of the firewalld guide’s rule for port 3306: internal ports stay internal, and the exception list is a list of containers, not a list of ports.
# updates and hygiene — images rot, just like packages
Update deliberately. podman pull the new image, stop the old container, start a new one from the new image with the same volume — the data never moves, the code does. Or let podman auto-update do it for quadlet-managed containers, with a label on the container marking it eligible. Either way, the update is a deliberate act with a rollback — the old image is still there until you prune it — not a surprise.
Prune, but not blindly. podman image prune and podman system prune reclaim the disk that old images and stopped containers eat. Do it on a schedule, and never with -a on a host whose rollback image you still need. A pruned rollback is a rollback you do not have.
Trust the image, or do not run it. A container image is someone else’s filesystem, running on your host with your kernel. Prefer official images, pin versions, and check what an image actually contains before you run it with a published port. The Cursor backdoor post on this site is the cautionary tale in the other direction — a trusted distribution channel carrying something malicious — and it applies to container images exactly as it applies to extensions.
# when not to containerize — the honest limits
A database with real performance needs. You can run a database in a container, and many people do — but a database that needs direct I/O performance, huge memory, and careful tuning is often better on the host or on a dedicated VM, where the storage path is short and the tuning is yours. If you do containerize it, the data volume is on fast local storage, not an overlay filesystem, and the memory limit is set deliberately, not left to default.
A service that needs the host. Backup agents, monitoring agents that read the host, and anything that manages the host itself generally belong on the host. Containerizing the tool that manages the host is a layer of indirection that buys you nothing.
A single static service on a quiet host. If the box runs one web server and nothing else, a container adds a moving part for no benefit. Containers pay for themselves when there are several services, when they need isolation from each other, or when they need to move between hosts. One service, one host, no movement — just install it.
# the cheat sheet — keep this, not the man page
what is running
podman ps (add -a for stopped containers too)
run a service, data outside
podman run -d --name web -p 8080:80 -v /srv/web-data:/var/www/html:Z nginx:1.26
make it a real service
quadlet .container file, then systemctl enable --now web
logs, the same as everything else
journalctl -u web -p err --since today
containers talking to each other
podman network create app-net — no published port for the database
update deliberately
podman pull, recreate the container, keep the volume, keep the old image as rollback
rootless is the default posture
run as a service user; the daemon-running-as-root model is Docker’s, not Podman’s

Commands verified against Podman, LXC/Incus, and Docker documentation and RHEL 9/10 behaviour, November 7, 2026. Podman is the container tool in RHEL base repositories; Docker requires its own repository on RHEL. Quadlet files live in /etc/containers/systemd/ (system) or ~/.config/containers/systemd/ (per-user). Image names and versions in examples are illustrative — pin the version your service actually needs.

Scroll to Top