Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як поділитись даними між контейнерами?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Змонтуй той самий named volume у кілька container.** Обидва бачать ті самі файли. Мережа кращий варіант для service-to-service спілкування. ```bash docker volume create shared docker run -d --name producer -v shared:/data myproducer docker run -d --name consumer -v shared:/data myconsumer # Обидва читають/пишуть ті самі /data файли. ``` ```yaml # Compose services: producer: { volumes: [shared:/data] } consumer: { volumes: [shared:/data] } volumes: shared: ``` **Головне:** для файлів = shared volume. Для повідомлень/RPC = мережа (один container expose сервіс, інші викликають). Для broadcast = черга (Redis, Kafka). Обирай за патерном доступу, не за топологією container.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Поділ даних між Docker container** має три хороші відповіді залежно від того, що ти маєш на увазі під «дані». Файли? Shared volume. Повідомлення? Network-виклики. Broadcast? Черга. Правильний вибір важить, бо неправильний дає concurrency-баги на масштабі. ## Теорія ### TL;DR - **Файли (read-mostly або single-writer):** named volume, змонтований у кілька container. - **Service-to-service (RPC, HTTP):** Docker-мережа. Container викликають одне одного по імені. - **Багато writer, queue-семантика:** message-queue (Redis, RabbitMQ, Kafka). - **Read-only config:** bind-mount того самого файлу у кожен container, або Docker configs / Swarm secrets. - **Уникай `--volumes-from`** для нового коду, працює, але це legacy-форма. Бери named volume. ### Патерн 1: shared named volume ```bash docker volume create shared docker run -d --name writer \ -v shared:/data \ alpine sh -c 'while true; do date >> /data/log.txt; sleep 5; done' docker run --rm -v shared:/data alpine cat /data/log.txt # Wed Apr 30 12:00:00 UTC 2026 # Wed Apr 30 12:00:05 UTC 2026 # ... ``` Обидва container бачать той самий `/data/log.txt`. Конкурентні записи потребують app-level координації (сам volume не lock'ить). ### Патерн 2: network-комунікація Часто правильна відповідь, коли «дані» це насправді сервіс або RPC, не файли. ```yaml services: api: image: myapp environment: WORKER_URL: http://worker:5000 worker: image: myworker expose: ["5000"] ``` `api` викликає `http://worker:5000/...`. Розшарені дані це API-контракт, не файли. **Коли краще, ніж shared volume:** - Кілька writer з різних процесів (race condition у shared-файлах vs послідовні API-виклики). - Cross-host операції (volume локальні; мережі span'ять host через overlay). - Версіонування контракту (HTTP/gRPC мають явні schema; сирі файли ні). ### Патерн 3: message-queue Для broadcast, fan-out, async work-distribution: ```yaml services: redis: image: redis:7 producer: image: myproducer environment: { REDIS_URL: redis://redis:6379 } depends_on: [redis] worker: image: myworker deploy: { replicas: 5 } environment: { REDIS_URL: redis://redis:6379 } depends_on: [redis] ``` Producer push'ить повідомлення у Redis. П'ять workers pull'ять і обробляють. Дані течуть через чергу, не через shared filesystem. Той самий патерн з RabbitMQ, Kafka, NATS. Обирай за гарантіями (at-least-once, at-most-once, ordering, persistence). ### Патерн 4: read-only config-sharing Кілька container потребують той самий config-файл: ```yaml services: web: image: nginx volumes: - ./shared.conf:/etc/myapp/config.conf:ro api: image: myapp volumes: - ./shared.conf:/etc/myapp/config.conf:ro ``` Bind-mount той самий host-файл в обидва. `:ro` робить read-only. Edit на host, restart container, обидва підхоплюють новий config. Для Swarm або K8s бери **configs** (`docker config create`) і **secrets** (`docker secret create`), вони розподіляють файли через кластер. ### `--volumes-from` (legacy) ```bash docker run -d --name data -v /shared alpine docker run --rm --volumes-from data alpine ls /shared ``` Другий container успадковує volume-mount першого. Спочатку використовувалося для «data-container», патерн з часів до named volume. Сьогодні **named volume чистіше**. Уникай `--volumes-from` у новому коді. ### Підступи конкурентного доступу Shared volume це просто директорія. OS магічно не серіалізує записи між container. Поширені gotcha: ```bash # Container A пише file.json # Container B читає file.json У ТОЙ ЖЕ ЧАС # B може прочитати partial-write → JSON-parse error ``` Mitigation: - **Атомарні записи:** пиши у `tmp.json`, потім `mv tmp.json file.json` (атомарно на тій самій filesystem). - **File-locks:** `flock`, але координація між container потребує shared-lock-файл. - **Single-writer-конвенція:** лише один container коли-небудь пише; інші читають. - **Бери чергу або DB натомість**, коли конкурентний доступ норма. ### Типові помилки **Розшарювати volume для того, що має бути RPC** ```bash # НЕПРАВИЛЬНО: api пише request-файл, worker стежить за директорією docker run -d --name api -v shared:/queue myapp docker run -d --name worker -v shared:/queue myworker # Race condition, file-locking жах. # ПРАВИЛЬНО: api викликає worker через HTTP # АБО producer push'ить у Redis, workers споживають ``` File-as-message це патерн, що змусив людей винайти message queue. **Bind-mount host-шляхів і припущення портативності** ```yaml volumes: - /home/user/myapp/data:/data # Працює на host A. Падає на host B, якщо /home/user/myapp/data не існує. ``` Named volume портативні; bind mount прив'язує до конкретної host-filesystem. Для shared-даних на multi-host бери network volume-driver або network-сервіс. **Забути permission через container** Container A крутиться як UID 1000, пише файли. Container B як root, без проблем. Container C як UID 1001, не може прочитати, що A написав (perm 700, owner 1000). Обери UID-стратегію: той самий UID між container, або файли пишуться з permissive-mode (`umask 002`). **Монтувати той самий volume read-write всюди** Корисно, коли багато reader не потребують писати. Mount writer RW, reader RO: ```yaml writer: volumes: [shared:/data] reader-1: volumes: [shared:/data:ro] # ← read-only reader-2: volumes: [shared:/data:ro] ``` Ловить випадкові записи від reader. ### Реальне застосування - **Web + reverse-proxy ділять серти:** і nginx, і certbot монтують `/etc/letsencrypt`, тож certbot пише, nginx читає. - **App + log-shipper sidecar:** обидва монтують `/var/log/app` volume; застосунок пише, fluentd читає і шиппить. - **Backup-container:** read-only mount прод-volume, крутить `tar` у backup-destination. - **Build-артефакти:** CI білдить у container A, container B (publish) читає результат через shared volume, змонтований в обидва. - **Init-container патерн (K8s, також можна у Compose з `service_completed_successfully`):** init пише config, app його читає. ### Питання для поглиблення **Q:** Чи можуть два container писати у той самий файл одночасно? **A:** Так, але OS не координує. Last-write-wins або partial-write ймовірні. App-level координація (lock, атомарні записи, append-only логи) це твоя відповідальність. **Q:** Чи безпечні shared volume через host-reboot? **A:** Так, volume персистентні. Reboot host, restart container з тими ж volume-mount, дані цілі. **Q:** Яка різниця між shared volume і bind mount того самого host-шляху? **A:** Функціонально схоже, обидва дають кільком container бачити ті самі файли. Volume Docker-managed (портативні, life-cycle контрольовано), bind mount пінується до host-шляху (не портативно). **Q:** Як ділити дані між container на різних host? **A:** Кілька опцій. (1) Network-сервіс (DB, Redis), доступний з обох host. (2) Network-filesystem (NFS, GlusterFS, EFS), змонтована на обох host і bind-mounted у container. (3) Object-storage (S3, Minio) з обома container як client. **Q:** (Senior) Коли sharing volume стає архітектурним smell? **A:** Коли два сервіси ділять стан без чіткого owner, обидва пишуть у ті самі файли. Цей coupling робить scaling їх незалежно неможливим: баг одного стає проблемою іншого. Smell супроводжується фіксом: introduce owning-сервіс (DB, queue), що стає єдиним source of truth, з обома «sharers» як клієнтами. Filesystem-as-IPC нормально для cache і immutable-даних; для живого mutable-стану бери сервіс. ## Приклади ### Sidecar log-shipper патерн ```yaml services: app: image: myapp volumes: - applogs:/var/log/app fluentd: image: fluent/fluentd volumes: - applogs:/fluentd/log:ro # read-only, fluentd не пише app-логи - ./fluent.conf:/fluentd/etc/fluent.conf:ro depends_on: [app] volumes: applogs: ``` App пише логи у volume, fluentd їх читає і шиппить у aggregator. Loose coupling, єдиний owner записів. ### Build-and-publish pipeline ```yaml services: build: image: builder volumes: - artifact:/out command: build-script.sh restart: "no" publish: image: publisher volumes: - artifact:/in:ro # publisher не модифікує depends_on: build: condition: service_completed_successfully restart: "no" volumes: artifact: ``` Build-стейдж дає файли, publish-стейдж їх споживає. Volume це передача. `service_completed_successfully` робить порядок явним. ### Cert-sharing між web і certbot ```yaml services: web: image: nginx:1.27-alpine volumes: - certs:/etc/letsencrypt:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro ports: ["80:80", "443:443"] certbot: image: certbot/certbot volumes: - certs:/etc/letsencrypt # certbot пише - ./acme-challenge:/var/www/acme-challenge entrypoint: | sh -c "trap exit TERM; while :; do certbot renew --webroot -w /var/www/acme-challenge --quiet nginx -s reload sleep 12h & wait $${!}; done" ``` Certbot пише серти у volume; nginx їх читає. Renewal трапляється періодично; nginx reload підхоплює нові серти без рестарту container.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.