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.
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.
/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:latestThe
: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.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.targetThen
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.-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.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.
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.
podman ps (add -a for stopped containers too)podman run -d --name web -p 8080:80 -v /srv/web-data:/var/www/html:Z nginx:1.26.container file, then systemctl enable --now webjournalctl -u web -p err --since todaypodman network create app-net — no published port for the databasepodman pull, recreate the container, keep the volume, keep the old image as rollbackCommands 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.
