Представь типичную картину. Ты годами набивал руку на Docker, у тебя в голове намертво прошиты docker run, docker build, docker compose. И тут приходишь в enterprise на RHEL, а там никакого Docker-демона нет вообще, есть podman. Или поднимаешь кластер Kubernetes - и узнаешь, что Docker оттуда выпилили еще в 1.24, а под капотом крутится containerd, к которому ты лезешь через nerdctl. Или security-отдел запрещает запускать что-либо от root, и привычный демон, висящий root-процессом на сокете, просто не проходит аудит.
Хорошая новость: паниковать не надо. Docker - это давно не единственный способ работать с контейнерами, а лишь один из игроков в большой OCI-экосистеме. И главное, что нужно усвоить с самого начала: образ, который ты собрал Docker-ом, без проблем запустится в Podman, в containerd, в Kubernetes и наоборот. Потому что все они говорят на одном стандартизированном языке - OCI. Запрос "docker альтернатива" в Яндексе стабильно растет не от хайпа, а потому что в реальном проде эти инструменты уже стоят. Этот урок - про то, как устроена экосистема вокруг Docker и почему "Docker без Docker" в 2026 году звучит абсолютно нормально.

OCI: почему образы взаимозаменяемы
Чтобы понять, как одна и та же картинка работает в пяти разных движках, надо разобрать слово, которое ты будешь слышать постоянно - OCI (Open Container Initiative). Это не программа, это набор спецификаций. Их три, и их полезно держать в голове:
- image-spec - как устроен образ: манифест, конфиг, набор слоев (tar-архивы с файлами), хэши-дайджесты. Любой образ, который ты тянешь с Docker Hub, - это просто набор слоев плюс JSON-конфиг по этой спеке.
- runtime-spec - как из распакованного образа сделать запущенный контейнер: какой rootfs, какие namespaces, cgroups, capabilities, точки монтирования. Эту спеку реализует runc - низкоуровневый рантайм, который непосредственно дергает системные вызовы Linux и создает процесс.
- distribution-spec - как образы лежат в реестре и как их по HTTP качать и пушить. Поэтому Docker Hub, Yandex Container Registry, VK Cloud Container Registry, Harbor, GHCR работают по одному протоколу.
Podman: daemonless и rootless по умолчанию
Podman - самая известная альтернатива, и тема "podman vs docker" в обсуждениях всплывает чаще всего. Главное архитектурное отличие звучит так: Podman daemonless, то есть без постоянного демона. В Docker есть процесс dockerd, который висит всегда, владеет всеми контейнерами и слушает сокет. Падает демон - падает (в худшем случае) управление всем зоопарком. В Podman демона нет вообще. Команда podman run - это обычный процесс: он напрямую готовит namespaces, запускает через conmon (монитор контейнера) нужный рантайм (по умолчанию crun или runc) и завершается. Контейнер при этом живет дальше как потомок conmon, а не висит на демоне.
Что это дает на практике:
- Rootless по умолчанию. Ты запускаешь контейнеры от своего обычного юзера, без root. Под капотом - user namespaces: твой UID 1000 внутри контейнера маппится в root (UID 0), а наружу остается непривилегированным. Диапазон субординатных UID/GID берется из /etc/subuid и /etc/subgid. Это огромный плюс для безопасности: даже если из контейнера сбежит злоумышленник, снаружи он останется обычным юзером без прав.
- Нет единой точки отказа. Обновил Podman - бегущие контейнеры не падают, потому что их держит conmon, а не общий демон.
- Прозрачность для systemd. Раз нет своего демона, контейнеры можно отдать под управление systemd как нормальные сервисы (об этом ниже).
Код: Выделить всё
# Алиас, который делает большинство привычных команд рабочими
alias docker=podman
# Запуск - флаги те же
podman run -d --name web -p 8080:80 docker.io/library/nginx:alpine
# Проверяем, что rootless: смотрим маппинг UID
podman unshare cat /proc/self/uid_map
# 0 1000 1
# 1 100000 65536
Важный нюанс портов: rootless-процесс не может слушать привилегированные порты ниже 1024 без донастройки sysctl net.ipv4.ip_unprivileged_port_start. Поэтому в примерах обычно видишь 8080, а не 80 наружу. Это типичная грабля при переезде с Docker, где демон-то был под root и порт 80 биндился спокойно.
Pods и Quadlet: чего нет в чистом Docker
Podman принес две фишки, ради которых его многие и берут.
Первая - pods (отсюда и имя, Podman = Pod Manager). Pod - это группа контейнеров, делящих общий network namespace: они видят друг друга по localhost, как поды в Kubernetes. Это не случайно - Podman умеет генерировать Kubernetes YAML из запущенных подов командой podman kube generate и наоборот поднимать поды из YAML через podman kube play. Локально потестил - и почти готовый манифест для кластера.
Код: Выделить всё
podman pod create --name app -p 8080:80
podman run -d --pod app --name api myapi:latest
podman run -d --pod app --name cache docker.io/library/redis:7
# api и cache теперь общаются по localhost внутри пода
Код: Выделить всё
# /etc/containers/systemd/web.container
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
[Install]
WantedBy=multi-user.target default.target
Код: Выделить всё
systemctl daemon-reload
systemctl start web.service # имя сервиса = имя файла без расширения
containerd и nerdctl: то, на чем стоит Kubernetes
Теперь самый важный для DevOss кусок. containerd - это тот самый низкоуровневый менеджер контейнеров, который Docker использует у себя внутри. Это демон, но не такой увесистый, как dockerd: он отвечает за жизненный цикл контейнеров, образы, снапшоты, передачу образов из реестра. Высокоуровневых удобств (build из Dockerfile, compose, удобный CLI) у него штатно нет - это намеренно, containerd создан как кирпич для платформ, а не как инструмент для человека за терминалом.
Именно поэтому Kubernetes в свое время ушел от dockershim. dockershim - это была прослойка-переходник, через которую kubelet общался с Docker. Но Docker под капотом все равно вызывал containerd - получалась лишняя ступенька (kubelet -> dockershim -> dockerd -> containerd). Когда у k8s появился стандартный интерфейс CRI (Container Runtime Interface), оказалось, что containerd реализует его напрямую, а Docker - нет, для него и держали shim. Поддерживать костыль ради совместимости стало невыгодно, и в Kubernetes 1.24 dockershim удалили. Никакой драмы для образов это не вызвало: твои Docker-образы как были OCI, так и остались, кластер их прекрасно тянет через containerd. Поменялся рантайм узлов, а не формат.
Вот тут и появляется nerdctl - contaiNERD CTL, Docker-совместимый CLI для containerd. Он закрывает ровно ту дыру, про которую я сказал: дает человеку привычный docker-like интерфейс поверх голого containerd.
Код: Выделить всё
# Те же команды, что в Docker, только бинарь другой
nerdctl run -d --name web -p 8080:80 nginx:alpine
nerdctl ps
nerdctl build -t myapp:1.0 .
nerdctl compose up -d # да, compose тоже поддерживается
Код: Выделить всё
nerdctl --namespace k8s.io ps
Последний кит экосистемы Red Hat - Buildah, инструмент именно для сборки OCI-образов. Podman внутри для build вызывает как раз Buildah. Зачем отдельный инструмент, если есть docker build? Две причины.
Первая - сборка без привилегированного демона. Классический docker build идет через root-демон, что в CI и в кластере - дыра в безопасности. Buildah собирает образы в rootless-режиме, не требуя демона вообще. Это давняя боль "как собрать образ внутри Kubernetes-пода, не дав ему root и доступ к docker.sock" - Buildah (и BuildKit) ее закрывают.
Вторая, более интересная, - Buildah умеет собирать образы скриптом, без Dockerfile. Ты получаешь полный контроль: монтируешь рабочий контейнер как обычную файловую систему и правишь его шелл-командами. Это спасает, когда логика сборки сложнее, чем линейный список RUN, и ее удобнее выразить циклом на bash.
Код: Выделить всё
#!/bin/bash
ctr=$(buildah from alpine:3.20) # создаем рабочий контейнер из базового образа
buildah run $ctr apk add --no-cache curl # выполняем команды как RUN
buildah copy $ctr ./app /app # копируем файлы как COPY
buildah config --entrypoint '["/app"]' $ctr
buildah commit $ctr myapp:1.0 # фиксируем результат как образ
Когда мигрировать, а когда не трогать
Без фанатизма. Docker - отличный инструмент, и для локальной разработки на ноуте он часто остается самым удобным, особенно на Mac/Windows через Docker Desktop с WSL2. Менять шило на мыло без причины не надо. Реальные триггеры миграции:
- Требования безопасности. Нужен rootless, запрет демона под root, прохождение аудита - смотри в сторону Podman. Это самый частый мотив в enterprise.
- RHEL и systemd-инфраструктура. Если все на Red Hat и systemd - Podman с Quadlet родной, Docker там лишний.
- Kubernetes-узлы. Там Docker уже не нужен в принципе: рантайм - containerd, диагностика - nerdctl/crictl. Образы продолжаешь собирать чем угодно.
- CI-сборка без docker.sock. Buildah или BuildKit/buildx в rootless-режиме безопаснее, чем монтировать сокет демона в раннер.
- Слепо ставить alias docker=podman и ждать 100% совместимости. 90% команд совпадают, но края разные: сеть (Podman использует Netavark и Aardvark-DNS вместо bridge-демона Docker), порты ниже 1024 в rootless, отсутствие живого демона для интеграций, которые лезут в docker.sock.
- Забыть про namespace k8s.io в nerdctl и решить, что контейнеров на узле нет.
- Думать, что "альтернатива Docker" означает несовместимые образы. Нет. OCI-образ один на всех - это и есть главный вывод урока.
Если есть Linux (или WSL2), повтори руками:
- Поставь podman и проверь rootless: podman run --rm docker.io/library/alpine id, затем podman unshare cat /proc/self/uid_map - убедись, что внутренний root маппится на твой обычный UID.
- Подними nginx через Podman на порту 8080 и проверь curl localhost:8080. Обрати внимание, что ты не использовал root.
- Опиши этот же nginx через Quadlet-файл в ~/.config/containers/systemd/, выполни systemctl --user daemon-reload и подними как сервис. Посмотри логи через journalctl --user.
- Собери простой образ через Buildah-скрипт (from/run/copy/commit) без всякого Dockerfile и запусти его подом.
- Если есть containerd - поставь nerdctl и собери тот же образ через nerdctl build, сравни вывод nerdctl images и podman images: дайджесты слоев совпадут, потому что формат один.
- Почему Docker-образ без переделки запускается в Podman, containerd и Kubernetes? Какие три спецификации OCI это обеспечивают?
- Что значит "Podman daemonless и rootless по умолчанию" и какой механизм Linux дает rootless-изоляцию?
- Почему Kubernetes отказался от dockershim в 1.24 и при чем тут CRI и containerd?
- В каких двух случаях Buildah выигрывает у docker build и зачем рядом нужен Skopeo?
"Docker без Docker" - это не экзотика, а нормальная реальность 2026 года. Под капотом у всех один фундамент: OCI-образы, runc/crun, namespaces, cgroups. Podman дает daemonless и rootless с pods и Quadlet и царит в RHEL/enterprise. containerd с nerdctl - то, на чем стоит Kubernetes после ухода от dockershim. Buildah собирает образы без демона и даже без Dockerfile. Главное помни: образ один на всех, а инструмент выбирай под задачу и требования безопасности, а не по привычке.