Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке Docker daemon і Docker client? Як вони взаємодіють?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Docker daemon (`dockerd`)** це фоновий сервіс, що володіє всім Docker-станом: image, container, мережі, volume. **Docker client (`docker` CLI)** це тонкий фронтенд, що конвертує твої команди у REST-виклики і шле на daemon через Unix socket або TCP. ```bash # CLI -> daemon через /var/run/docker.sock $ docker ps # Транслюється у: # GET http://docker/v1.46/containers/json ``` **Головне:** клієнт без стану; daemon тримає все. Можна спрямувати клієнт на віддалений daemon через `DOCKER_HOST=tcp://...` для remote-керування, але daemon і те, чим він керує, завжди разом.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Docker daemon і Docker client** утворюють класичну client-server пару. Daemon робить реальну роботу; client це фронтенд, що говорить REST. Розуміння цього розпилу дозволяє дебажити «чому CLI висить» або «чому мій Mac спілкується з dockerd всередині VM». ## Теорія ### TL;DR - **Daemon (`dockerd`)** = довгоживучий root-процес. Управляє image, container, мережами, volume. Володіє ВСІМ станом. - **Client (`docker` CLI)** = stateless фронтенд. Переводить твої команди у REST API виклики. - **Транспорт** = Unix socket (`/var/run/docker.sock`) за замовчуванням; TCP socket, якщо ставиш `DOCKER_HOST=tcp://...`. - **Формат на проводі** = HTTP + JSON. Engine API версіонований (`/v1.46/...`); daemon домовляється про версію з клієнтом. - **Один до багатьох** = можна спрямувати один CLI на кілька daemon через Docker-контексти (`docker context use ...`). ### Швидкий приклад ```bash # Що `docker ps` насправді робить: $ curl --unix-socket /var/run/docker.sock \ http://docker/v1.46/containers/json | jq '.[0]' { "Id": "a3f9d2b8c1e4...", "Names": ["/web"], "Image": "nginx:1.27-alpine", "Status": "Up 5 minutes", "Ports": [{ "PrivatePort": 80, "PublicPort": 8080, "Type": "tcp" }] } ``` Це рівно те, що CLI зробив під капотом, коли ти набрав `docker ps`. GET у socket; daemon повертає JSON; CLI форматує його у таблицю. ### Daemon (`dockerd`) Linux-сервіс, зазвичай стартує systemd як root. Відповідальності: - **Управління image:** тягне з registry, білдить через BuildKit, зберігає у `/var/lib/docker/`. - **Container lifecycle:** делегує на `containerd` і `runc`, але володіє user-facing container API. - **Мережа:** створює віртуальні bridge, керує iptables-правилами, виділяє IP-адреси container. - **Volume:** named volume, bind mount, plugin-based storage driver. - **API endpoint:** слухає налаштовані socket'и. Конфігурація живе у `/etc/docker/daemon.json` і command-line флагах. Типові налаштування: `data-root` (де зберігати image), `storage-driver` (overlay2 за замовчуванням), `log-driver`, `dns`, `registry-mirrors`. ### Client (`docker` CLI) Один бінар, без daemon, без власного стану. Коли ти набираєш команду: 1. Парсить аргументи (`docker run -p 8080:80 nginx`). 2. Дивиться на активний контекст (з яким daemon спілкуватися). 3. Будує HTTP-запит з відповідними заголовками (`Content-Type: application/json`, API-версія). 4. Шле через socket; читає відповідь. 5. Форматує відповідь (таблиця, JSON, кастомний шаблон через `--format`). CLI настільки тонкий, що його можна замінити на `curl` для будь-якої операції, і багато docs роблять рівно це. ### Транспорт: Unix socket vs TCP За замовчуванням `dockerd` слухає Unix socket: `/var/run/docker.sock`. Local-only, швидко, захищено permissions файлу. ```bash $ ls -l /var/run/docker.sock srw-rw---- 1 root docker 0 Apr 30 10:00 /var/run/docker.sock # Mode 660: owner=root, group=docker. Щоб юзати docker без sudo, # додай себе у групу `docker`. ``` Для remote-керування daemon може слухати TCP. Налаштовуєш у `daemon.json`: ```json { "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"], "tls": true, "tlsverify": true, "tlscacert": "/etc/docker/ca.pem", "tlscert": "/etc/docker/server-cert.pem", "tlskey": "/etc/docker/server-key.pem" } ``` Тоді спрямуй remote-client на нього: ```bash $ DOCKER_HOST=tcp://docker.example.com:2376 docker ps ``` **Завжди вмикай TLS для TCP-експозиції.** Незахищений `tcp://0.0.0.0:2376` це remote root shell, що чекає, коли йому скористаються, Docker API може запустити будь-який container з `--privileged`, монтувати шляхи з хоста і фактично отримати машину. ### Docker-контексти Один CLI, кілька daemon. Корисно, коли ти керуєш локальним dev плюс віддаленим prod або staging. ```bash $ docker context create staging \ --docker "host=ssh://user@staging.example.com" $ docker context ls NAME DESCRIPTION DOCKER ENDPOINT default Local Docker unix:///var/run/docker.sock staging ssh://user@staging.example.com $ docker context use staging $ docker ps # тепер показує container на staging, через SSH ``` Жодного більше жонглювання `DOCKER_HOST=...` env-змінними. Перемикання однією командою. ### API-версіонування Engine API версіонований. CLI шле версію типу `/v1.46/...`; daemon або приймає, або, якщо застара, повертає downgrade hint. Forward-сумісність означає, що клієнт v25 зазвичай говорить з daemon v23 (і навпаки в розумних межах). Mismatch: ```bash $ docker ps Error response from daemon: client version 1.47 is too new. Maximum supported API version is 1.45. ``` Фікс: апгрейдь daemon або пінься на клієнт через `DOCKER_API_VERSION=1.45`. ### Типові помилки **Додавати себе у групу `docker` як security-«зручність»** ```bash $ sudo usermod -aG docker $USER # Тепер можна юзати docker без sudo. Зручно. Також небезпечно. ``` Бути у групі `docker` **еквівалентно root**. Daemon API може монтувати шляхи з хоста у container і chmod'ити файли, будь-який unprivileged-юзер з доступом до socket має шлях до root. Для дев-машини нормально. Для multi-user сервера ні. **Експозиція TCP без TLS** Знайдено на безлічі інтернет-доступних серверів. `dockerd -H tcp://0.0.0.0:2375` (зверни увагу на нешифрований порт) дає будь-якому інтернет-юзеру можливість запустити privileged container і отримати root хоста. Завжди TLS, завжди client cert auth. **Плутати, що CLI зламано і daemon зламано** ```bash $ docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ``` Помилка чесна: це може бути зупинений daemon (`systemctl status docker`), відсутній socket-файл або проблема з permissions (ти не у групі `docker`). Сам CLI рідко зламано; повідомлення показує на daemon. **Рестартити `dockerd`, щоб щось пофіксити, і випадково повбивати всі container** З дефолтними налаштуваннями на старіших Docker `systemctl restart docker` вбивав container. Сучасний Docker має `live-restore`: ```json // /etc/docker/daemon.json { "live-restore": true } ``` З цим увімкненим `systemctl restart docker` тримає container живими крізь рестарт daemon. Containerd-shim на кожен container це те, що робить це можливим. ### Реальне застосування - **Docker Desktop на Mac/Windows:** daemon живе всередині маленької Linux VM. Твій `docker` CLI на хості спілкується з ним через forwarded socket. Це бачиш у Docker Desktop > Settings > Resources, де виділяєш RAM і CPU для VM. - **CI/CD runners:** у кожного runner свій dockerd. Джоби використовують локальний daemon для білду і push image. «Docker-in-Docker» це патерн, де джоба сама крутить `dockerd` всередині privileged-container, але має security-tradeoff. - **Remote-host керування:** ops-команди використовують `docker context` через SSH, щоб адмінити віддалений staging чи prod, не даючи кожному адміну TCP-експозований daemon. - **Rootless Docker:** `dockerd` крутиться під non-root юзером. Кожен юзер отримує свій daemon і socket. Використовується у HPC-кластерах і безпеково чутливих середовищах. ### Питання для поглиблення **Q:** Що відбувається, якщо daemon помирає, а container запущені? **A:** З увімкненим `live-restore` (дефолт у сучасному Docker) container продовжують крутитися. Containerd-shim на кожен container їх тримає. CLI не може взаємодіяти з daemon у це вікно, але самі container і їхня мережа продовжують працювати. Коли `dockerd` рестартує, він re-attach'ується. **Q:** Чи можуть два клієнти спілкуватися з тим самим daemon одночасно? **A:** Так. Daemon серіалізує операції над shared state, але охоче обробляє багато конкурентних клієнтів. CI-ран і твій локальний `docker ps` проти того самого daemon працюють паралельно нормально. **Q:** Чому моя команда висить на `docker ps`? **A:** Майже завжди проблема з daemon. Або він перевантажений (багато конкурентних операцій), або dead-lock'нувся на storage driver, або застряг на завислому containerd. `journalctl -u docker` і `journalctl -u containerd` твої перші зупинки. **Q:** Чи можна написати свій Docker-клієнт? **A:** Так, Engine API задокументований і стабільний. Офіційний Go SDK (`github.com/docker/docker/client`) це те, що використовує сам `docker` CLI. Купа сторонніх інструментів (Lazydocker, Portainer, Dockge) це по суті кастомні клієнти поверх того самого API. **Q:** (Senior) Який security-ризик монтування `/var/run/docker.sock` у container? **A:** Монтування socket дає тому container повний контроль над Docker daemon хоста. Зсередини він може запустити будь-який container з `--privileged`, bind-mount'нути `/` і фактично стати root на хості. Поширене у CI-інструментах, що мають піднімати білд-container (альтернатива DinD), але відомий supply-chain ризик: зловмисний image з доступом до socket ескалює до root хоста. Використовуй rootless Docker, або `sysbox`, або per-runner dockerds, щоб обмежити радіус ураження. ## Приклади ### Виклик Engine API напряму ```bash # Список усіх image $ curl --unix-socket /var/run/docker.sock \ http://docker/v1.46/images/json | jq '.[].RepoTags' ["nginx:1.27-alpine"] ["postgres:16", "postgres:latest"] # Створення і старт container у двох API-викликах $ CID=$(curl --unix-socket /var/run/docker.sock \ -H 'Content-Type: application/json' \ -X POST "http://docker/v1.46/containers/create?name=test" \ -d '{"Image":"alpine","Cmd":["echo","hi"]}' | jq -r .Id) $ curl --unix-socket /var/run/docker.sock \ -X POST http://docker/v1.46/containers/$CID/start ``` Жодного `docker` CLI. CLI це зручність; API авторитетний. ### Перемикання між локальним і віддаленим ```bash $ docker context create prod-eu --docker "host=ssh://ops@prod-eu.example.com" $ docker context create prod-us --docker "host=ssh://ops@prod-us.example.com" $ docker context use prod-eu $ docker ps # container в EU $ docker --context prod-us ps # одноразова команда на US ``` Один CLI, три daemon (local, EU, US). Файл контекстів у `~/.docker/contexts/` тримає роутинг. ### Трасування CLI-виклику для підтвердження протоколу ```bash $ DOCKER_DEBUG=true docker ps 2>&1 | head -10 GET /v1.46/containers/json HTTP/1.1 Host: docker User-Agent: Docker-Client/26.1.0 (linux) Content-Type: application/json HTTP/1.1 200 OK Api-Version: 1.46 Content-Type: application/json Docker-Experimental: false Server: Docker/26.1.0 (linux) ``` GET-запит, JSON-відповідь, узгодження версії у заголовках. Це все, що є у протоколі.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.