====== Docker (DKA-Develop) ====== [[https://www.youtube.com/playlist?list=PLD5U-C5KK50XMCBkY0U-NLzglcRHzOwAg|DKA-Develop]] Образ (image) - основа для запуска контейнеров. Образы хранятся в реестрах (registries) - публичных (типа [[https://hub.docker.com/|hub.docker.com]]) или приватных, т. е., можно развернуть свой. # Добавить пользователя в группу Docker, чтобы он имел там все права: sudo usermod -aG docker username newgrp docker # чтобы не перезапускать машину # Запустить контейнер с Ubuntu и зайти в его терминал docker run -it ubuntu bash # Список всех контейнеров, в т. ч. остановленных docker ps -a # Запустить остановленный контейнер docker start containername # Остановить docker stop containername # Запуск с указанием имени хоста docker run -h hostname -it ubuntu bash # Список запущенных контейнеров docker ps # Информация о контейнере docker inspect containername # Задать имя контейнера docker run --name containername -it ubuntu bash # Посмотреть изменения в контейнере docker diff containername # Журнал контейнера docker logs containername # Удалить контейнер docker rm containername # Удалить все остановленные контейнеры docker rm -v $(docker ps -aq -f status=exited) # Удалить все образы docker rmi $(docker images -q) --force # Запустить в фоне с пробросом порта хост:контейнер docker run -d -p 8000:8080 apache # Список образов docker images # Создать образ из контейнера docker commit containername username/imagename # Закачать образ в реестр-репозиторий # Тэг можно не указывать, тогда будет присвоено значение latest docker push username/imagename:tag ===== Dockerfile ===== Набор инструкций для сборки образа. # Базовый образ FROM ubuntu # Информация о создателе MAINTAINER Username # Команды для выполнения внутри контейнера RUN apt update && apt install cowsay -y && ln -s /usr/games/cowsay /usr/bin/cowsay # Что выполнять по умолчанию при запуске контейнера ENTRYPOINT ["cowsay"] # Собрать образ docker build -t username/imagename . # Запустить docker run username/imagename "test" # Если бы в Dockerfile не был указан параметр ENTRYPOINT, # то запускать надо было бы так: docker run username/imagename cowsay "test" ===== Связь между контейнерами ===== Например, запускается MariaDB: docker run --name mysqlserver -e MYSQL_ROOT_PASSWORD=123456 -d mariadb Затем нужно запустить adminer и связать его с MariaDB. Параметр %%--link%% делает в /etc/hosts контейнера запись db, указывающую на контейнер mysqlserver. docker run --link mysqlserver:db -p 8080:8080 adminer Более современный вариант связи - через publish services. ===== docker-compose ===== docker-compose**.yaml** = docker-compose**.yml** # Use root/example as user/password credentials version: '3.1' services: db: image: mariadb # build: ./db Можно указать вместо image сборку из Dockerfile в каталоге db restart: always environment: MYSQL_ROOT_PASSWORD: example adminer: image: adminer restart: always ports: - 8080:8080 # Запустить docker-compose up # Запуск в фоне docker-compose up -d # Список контейнеров, запущенных через compose docker-compose ps # Собрать, пересобрать проект docker-compose build # Удалить контейнер с БД (по имени сервиса) docker-compose rm db Если в Windows установлен Docker toolbox, то вход в браузере через localhost не сработает, нужно заходить на IP виртуальной машины, который можно узнать, выполнив команду docker-machine ip default ===== Volumes ===== Назначение путям внутри контейнера локальных каталогов на хосте. Удобно тем, что при остановке или уничтожении контейнера данные остаются в целости. Есть 3 способа указания томов: - Объявить том при запуске контейнера.docker run -v /data ubuntuЗдесь каталог /data внутри контейнера станет томом, и всё, что пишется в /data, сохраняется на этот том. Местоположение тома можно посмотреть командойdocker inspect -f {{.Mounts}} containernameОбратное тоже верно - если что-то положить в каталог монтирования тома, это сразу будет видно в контейнере. Можно явно указать соответствие каталогов на хосте и внутри контейнера:docker run -v /localdir:/data ubuntu - Объявить том с помощью инструкции VOLUME в Dockerfile, например,VOLUME /data\\ Сопоставление явно указанного локального каталога и каталога внутри контейнера не работает.VOLUME ./localdir:/data - Объявить том в docker-compose.yml. volumes: - ./localdir:/data - ./database:/var/lib/mysql ===== Пример (Laravel) ===== Необходимо 3 сервиса: PHP, MySQL и Composer. Файл .env с переменными: # Paths DB_PATH_HOST=./databases APP_PATH_HOST=./blog APP_PATH_CONTAINER=/var/www/html/ В данном случае в папку ./blog кладётся проект, например, клон репозитория Github. docker-compose.yml: version: '3' services: web: build: ./web environment: - APACHE_RUN_USER=www-data volumes: - ${APP_PATH_HOST}:${APP_PATH_CONTAINER} ports: - 8080:80 working_dir: ${APP_PATH_CONTAINER} db: image: mariadb restart: always environment: MYSQL_ROOT_PASSWORD: example volumes: - ${DB_PATH_HOST}:/var/lib/mysql adminer: image: adminer restart: always ports: # должны быть разными для хоста у разных сервисов - 6080:8080 composer: image: composer:1.6 volumes: - ${APP_PATH_HOST}:${APP_PATH_CONTAINER} working_dir: ${APP_PATH_CONTAINER} command: composer install ./web/Dockerfile FROM php:7.2-apache RUN docker-php-ext-install pdo_mysql \ && a2enmod rewrite # Сборка и запуск docker-compose up --build # Запустить bash в сервисе-контейнере web # Благодаря указанию working_dir, оболочка сразу попадёт в /var/www/html/ docker-compose exec web bash ===== Решение проблем ===== В Windows при запуске контейнера с Volume ругается на то, что нет прав. Для этого нужно зайти в настройки Docker Desktop и на вкладке Shared Drives поставить галки на нужных дисках.\\ Ситуация может осложняться тем, что у пользователя нет пароля, и в таком случае расшарить диски не получится, так как Докер не даёт нажать ОК, если нет пароля пользователя. Решение - завести нового локального пользователя с бессрочным паролем, дать ему права локального админа, расшарить диски под его именем, а затем дать ему полные права на локальный каталог с проектами. ====== Get started ====== [[https://docs.docker.com/get-started/|Get started]]\\ [[https://docs.docker.com/engine/reference/commandline/docker/|Docker CLI]]\\ [[https://medium.com/the-code-review/top-10-docker-commands-you-cant-live-without-54fb6377f481|Top 10 Docker CLI commands you can’t live without]] ## List Docker CLI commands docker docker container --help ## Display Docker version and info docker --version docker version docker info ## Execute Docker image docker run hello-world ## List Docker images docker image ls ## List Docker containers (running, all, all in quiet mode) docker container ls docker container ls --all docker container ls -aq # Containers docker build -t friendlyhello . # Create image using this directory's Dockerfile docker run -p 4000:80 friendlyhello # Run "friendlyname" mapping port 4000 to 80 docker run -d -p 4000:80 friendlyhello # Same thing, but in detached mode docker container ls # List all running containers docker container ls -a # List all containers, even those not running docker container stop # Gracefully stop the specified container docker container kill # Force shutdown of the specified container docker container rm # Remove specified container from this machine docker container rm $(docker container ls -a -q) # Remove all containers docker image ls -a # List all images on this machine docker image rm # Remove specified image from this machine docker image rm $(docker image ls -a -q) # Remove all images from this machine docker login # Log in this CLI session using your Docker credentials docker tag username/repository:tag # Tag for upload to registry docker push username/repository:tag # Upload tagged image to registry docker run username/repository:tag # Run image from a registry # Services docker stack ls # List stacks or apps docker stack deploy -c # Run the specified Compose file docker service ls # List running services associated with an app docker service ps # List tasks associated with an app docker inspect # Inspect task or container docker container ls -q # List container IDs docker stack rm # Tear down an application docker swarm leave --force # Take down a single node swarm from the manager # Swarms docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux) docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10 docker-machine env myvm1 # View basic information about your node docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm docker-machine ssh myvm1 "docker node inspect " # Inspect a node docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end docker node ls # View nodes in swarm (while logged on to manager) docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm docker-machine ls # list VMs, asterisk shows which VM this shell is talking to docker-machine start myvm1 # Start a VM that is currently not running docker-machine env myvm1 # show environment variables and command for myvm1 eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1 & "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression # Windows command to connect shell to myvm1 docker stack deploy -c # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app) docker-machine ssh myvm1 "docker stack deploy -c " # Deploy an app using ssh (you must have first copied the Compose file to myvm1) eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker docker-machine stop $(docker-machine ls -q) # Stop all running VMs docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images ====== Docker (udemy.com) ====== Докер упрощает установку и запуск программ без возни с зависимостями. Это платформа, создающая и запускающая т. н. контейнеры. Контейнер - это запущенный изолированный экземпляр приложения из заранее заготовленного образа (image). При установке Докера ставится 2 компонента - клиент (CLI, управление) и сервер (функциональная часть). При установке Докера рекомендуется регистрация на docker.com. После установки Докера и перезагрузки нужно вбить рег. данные в запущенный Докер для доступа к Docker Cloud. ===== 01 Dive Into Docker ===== # Версия: docker version # Запустить тестовый контейнер: docker run hello-world Если образа нет в локальном кэше (image cache), он скачивается автоматически из репозитория (Docker Hub). Программы взаимодействуют с железом через ядро (kernel) путём системных запросов (system call). Контейнер - это изолированная область для запуска процессов, использует namespacing для изолирования системных ресурсов для запуска процесса, и control groups (cgroups) для лимита выделяемой мощности. В частности, это позволяет, например, запускать программы с разными зависимостями, в то время как эти зависимости не могли бы работать одновременно в одной и той же ОС. Т. е., виртуализация приложения или сервиса внутри одной ОС. Так как namespacing и cgroups - сугубо линуксовое явление, то, когда Докер ставится на Windows или Mac, в реальности туда ставится виртуальная машина с линуксом, через которую и работает контейнеризация. ===== 02 Manipulating Containers with the Docker Client ===== # Аргументы контейнеру docker run busybox echo hi there docker run busybox ls # Список запущенных контейнеров docker ps # Список когда-либо запущенных контейнеров docker ps --all docker run = docker create + docker start docker create hello-world # создаёт контейнер и показывает его ID docker start -a ID # запускает конкретный контейнер с выводом результатов docker start ID # просто запускает контейнер без вывода результатов на экран. Выводится только ID запущенного контейнера. docker logs ID # посмотреть вывод уже отработавшего контейнера по команде docker start ID # (не в реальном времени, а то, что успело выполниться на момент запроса). Запущенный однажды контейнер лежит на диске и его всегда можно вызвать по ID, но команду заменить не получится. Например, запускали docker run busybox echo hi there # Если запустить docker start -a ID echo bye there # то ничего не получится. # Удаление остановленных контейнеров, используемых ими сетей, кэша # (образы нужно будет скачивать заново из Docker Hub) docker system prune docker stop ID docker kill ID # Если stop не отрабатывает за 10 сек, автоматически посылается kill. # Запуск команд внутри контейнера docker exec -it ID # Например, docker exec -it ID redis-cli # Без ключа -it (-i -t) процесс запустится, но без возможности ввода. # -i - STDIN, -t - вывод текста. Можно запустить и docker exec -i ID redis-cli # но в этом случае не будет выводиться консоль и будет показываться # только ввод пользователя, выглядит непривычно и неудобно, хотя всё работает. # Запустить "удалённую" консоль в контейнере docker exec -it ID sh # потом можно запускать redis-cli и всё остальное за один сеанс, чтобы не вводить каждый раз docker exec -i ID redis-cli # Создать контейнер и запустить там консоль docker run -it busybox sh Файловые системы разных контейнеров изолированы, и созданное что-либо в одном контейнере не видится из других. ===== 03 Building Custom Images Through Docker Server ===== Для создания собственного образа нужно написать Dockerfile, который через клиент передаётся серверу, который на основе докерфайла создаёт образ. Докерфайл пишется так: - Указывается базовый образ (типа выбора ОС) - Запускаются команды для установки доп. программ (типа скачивания инсталлятора и установки, команды относятся к базовому образу, не к Докеру) - Указываются команды, выполняемые в момент запуска контейнера (типа запуска .exe) Создаётся каталог, где размещается файл Dockerfile (с большой буквы, без расширения). Например: FROM alpine RUN apk add --update redis CMD ["redis-server"] Затем запускается docker build . На каждом этапе сборки создаётся промежуточный контейнер, где выполняется команда, затем контейнер стирается и выполняется следующая.\\ Если добавить команду на 2 этап, например, RUN apk add --update gcc то предыдущие команды уже не будут выполняться, потому что результат уже есть в кэше. Но если поменять порядок команд, то всё будет выполняться заново, т. к. в кэше нет таких контейнеров. # Чтобы задать человеческое имя образу при сборке: docker build -t YourDockerID/RepoOrProjectName:Version . # Например, docker build -t username/redis:latest . # Версия latest грузится и запускается Докером по умолчанию. # Если просто написать docker build -t username/redis, # то версия latest добавится автоматически. # Можно сделать образ руками, запустив все нужные команды в консоли, а затем "закоммитив" образ. docker run -it alpine sh apk add --update redis # Из другой консоли: docker ps # (узнать ID изменяемого контейнера) docker commit -c 'CMD ["redis-server"]' ID # Создастся образ с новым ID. Чтобы запустить образ, можно не копировать весь ID, достаточно части, которая будет уникальной. ===== 04 Making Real Projects with Docker ===== Задача: сделать образ, который при обращении браузером на порт 8080 показывает страничку. Функционал состоит из package.json { "dependencies": { "express": "*" }, "scripts": { "start": "node index.js" } } и index.js const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('How are you doing'); }); app.listen(8080, () => { console.log('Listening on port 8080'); }); Dockerfile: # FROM alpine - не сработает, т. к. там нет npm, и на втором шаге будет ошибка FROM node:alpine # это вариант (тэг) alpine установки образа node, где npm есть # RUN npm install - не сработает, т. к. внутри контейнера нет файла package.json, # поэтому сначала надо их скопировать туда COPY ./ ./ # копирование в контексте сборочного каталога. В данном случае - скопировать все файлы RUN npm install # теперь можно запускать установку CMD ["npm", "start"] # Собрать: docker build -t username/simpleweb . # Запуск: docker run username/simpleweb Но localhost:8080 работать не будет, т. к. нужно настроить проброс порта в контейнер.\\ Для этого нужно запустить контейнер так: docker run -p 8080:8080 username/simpleweb # запросы на localhost:порт в контейнере COPY ./ ./ бросает файлы в корень файловой системы контейнера, что нехорошо. Для решения проблемы добавляется параметр WORKDIR /usr/app и все дальнейшие команды будут выполняться в контексте указанного каталога. Есть нюанс: если изменить что-либо в коде index.js и запустить пересборку образа, то это потянет за собой npm install заново. Решение - так как для запуска npm install нужен только package.json, то Dockerfile нужно писать так: COPY ./package.json ./ RUN npm install COPY ./ ./ Это предоствратит постоянную инсталляцию с нуля зависимостей npm при пересборке. ===== 05 Docker Compose with Multiple Local Containers ===== Задача: сделать веб-страницу со счётчиком посещений. Node отвечает за веб-часть, а Redis считает посещения. Можно засунуть Node и Redis в один контейнер, но при масштабировании (увеличении кол-ва контейнеров) получится так, что посещения будут записываться в разные экземпляры Redis. Поэтому нужно сделать два контейнера - один с Node, другой с Redis, и масштабировать только контейнеры с Node. Так как два контейнера должны взаимодействовать между собой, нужно их соответственно настроить, т. к. изначально всё, что внутри контейнера, изолировано от внешней среды. Есть два способа - использовать Docker CLI (что очень неудобно) или docker-compose, который позволяет избежать постоянного ввода одних и тех же команд через Docker CLI и позволяет автоматизировать работу. Docker-compose использует конфиг docker-compose.yml, куда добавляются команды Docker CLI. version: '3' services: redis-server: image: 'redis' node-app: build . ports: - "4001:8081" docker-compose автоматически открывает сетевой доступ между контейнерами, созданными в docker-compose.yml. В качестве хоста в index.js нужно указывать название сервиса, указанного в docker-compose.yml, например, host: 'redis-server', port: 6379 # Запуск: docker-compose up # = docker run myimage docker-compose up -d # (запуск в фоне) # Сборка и запуск docker-compose up --build # = docker build . + docker run myimage # Остановка (всех контейнеров) docker-compose down Автоперезапуск контейнера при падении\\ Варианты перезапуска: * no - не перезапускать (по умолчанию) * always - всегда перезапускать * on-failure - перезапускать в случае ошибки (код возврата не 0) * unless-stopped - всегда перезапускать, если только это не было остановлено специально Добавляется в docker-compose.yml в параметры конкретного сервиса. restart: always # Статус контейнеров docker-compose docker-compose ps показывает статус контейнеров, перечисленных в docker-compose.yml. Если этого файла в каталоге нет, то будет ошибка, т. к. docker-compose не знает, о чём идёт речь. ===== 06 Creating a Production-Grade Workflow ===== Этапы развития ПО - разработка, тестирование, продуктивное использование.\\ Создаётся 2 файла: Dockerfile.dev (для разработки) и просто Dockerfile (продакшн).\\ Вызвать Dockerfile.dev можно так: docker build -f Dockerfile.dev . Volume - ссылки изнутри контейнера на ресурсы вовне, например, на файлы и каталоги, чтобы не пересобирать и не перезапускать контейнер каждый раз, как требуются изменения. Например, docker run -p 3000:3000 -v /app/node_modules -v $(pwd):/app **$(pwd):/app -** $(pwd) - present working directory в хостовой системе, привязать её к каталогу /app внутри контейнера.\\ **-v /app/node_modules** указывает, что нужно использовать этот каталог внутри контейнера и не использовать ссылку вовне (синтаксис без :). Это используется, если ресурсы имеются в контейнере и их нет на хосте, вроде исключения. Чтобы избежать излишней сложности запуска контейнеров через docker run, можно использовать docker-compose. В docker-compose.yml нужно указать: version: '3' services: web: build: context: . dockerfile: Dockerfile.dev ports: - "3000:3000" volumes: - /app/node_modules - .:/app и запустить docker-compose up. Секция build содержит опции context: и dockerfile:, т. к. используется нестандартное имя докерфайла Dockerfile-dev. После того, как создан Volume, который смотрит из контейнера на хост, возникает вопрос, нужна ли операция копирования (COPY . .) в докерфайле? Лучше её оставить, т. к. в будущем может быть принято решение не использовать docker-compose, или этот докерфайл будет использован впоследствии для создания докерфайла для продуктивного использования. Тестирование: после docker build -f Dockerfile.dev . # запустить docker run -it ID npm run test Тесты в данном случае находятся в src/app.test.js, и чтобы менять их на ходу, можно создать для них Volume в docker-compose.yml, но есть другой способ - выполнить команду для запущенного контейнера: docker exec -it ID npm run test Можно применять это как временное решение, т. к. надо выяснять ID запущенного контейнера. Есть ещё один способ - добавить сервис для тестирования в docker-compose.yml services: tests: build: context: . dockerfile: Dockerfile.dev volumes: - /app/node_modules - .:/app command: ["npm", "run", "test"] # Далее нужно пересобрать и запустить контейнеры docker-compose up --build После этого можно менять файл тестов и видеть, как они сразу выполняются в терминале. Недостаток в том, что нельзя управлять выполнением тестов, т. к. нет меню управления.\\ # Вывод процесса в контейнере на стандартный вывод, т. е. экран, не помогает. docker attach -it ID Это связано с тем, что процесс запускается отдельным потоком, а подключение через attach происходит в первичную консоль. Можно посмотреть процессы, запустив docker exec -it ID sh # и там выполнив команду ps ==== Продакшен ==== Замена движка на nginx. Вопрос в том, что в данном случае нужно использовать ДВА исходных образа - node и nginx, который нужно добавить после сборки npm. Dockerfile: #Build phase FROM node:alpine as builder WORKDIR '/app' COPY package.json . RUN npm install COPY . . RUN npm run build # всё барахло (результат сборки) теперь находится в /app/build # Run phase FROM nginx # копировать из builder (пред. стадии), # из каталога с барахлом в стандартный каталог nginx COPY --from=builder /app/build /usr/share/nginx/html # Собрать образ для продакшена: docker build . # Запустить: docker run -p 8080:80 ID ===== 07 Continuous Integration and Deployment with AWS ===== Теперь с задействованием Гитхаба - двух веток feature и master. Master делает pull request из feature. Код проверяется Travis CI, который при новых коммитах также делает pull request кода и запускает проверки. Сначала нужно создать открытый пустой репозиторий на Гитхабе. Затем git init git add . git commit -m "initial commit" Содержимое каталога скопируется. git remote add origin travis-ci.org, авторизовать Тревис на Гитхабе, указать нужный репозиторий. Настройки тревиса буду указаны в файле .travis.yml, его нужно положить в корень каталога проекта. Схема файла - копировать запущенный докер, построить образ из Dockerfile.dev, настройки тестов, настройки выкладки кода на AWS (Amazon Web Services) и т. п. sudo: required services: - docker before_install: - docker build -t username/docker-react (or testme or smth else) -f Dockerfile.dev . script: - docker run username/docker-react npm run test -- --coverage -- --coverage нужен, чтобы после прохождения тестов скрипт выходил, а не ждал выбора пользователя. Затем выполняем git add . git commit -m "added travis file" git push origin master После этого коммит проваливается в Гит и забирается Тревисом, который действует сообразно .travis.yml и выдаёт результат теста. Далее идём в AWS и ищем там Elastic beanstalk, создать приложение, например, docker-react, создать там окружение - web server environment, платформа - docker. Затем нужно добавить в .travis.yml deploy: provider: elasticbeanstalk region: "us-west-2" (берётся из URL приложения на Амазоне) app: "docker-react" env: "Docker-env" bucket_name: "elasticbeanstalk-us-west-2-28354762534" bucket_path: "docker" (= app:) on: branch: master Затем на Амазоне надо добавить IAM - ключи для доступа и права для пользователя, например, docker-react-travis-ci. Ключи надо добавить в Environment variables Тревиса, но не в .travis.yml! В .travis.yml в раздел deploy затем нужно добавить access_key_id: $AWS_ACCESS_KEY secret_access_key: secure: "$AWS_SECRET_KEY" git add . git commit -m "added travis deploy config" git push origin master Чтобы пробросить порт из контейнера в случае использования elastic-beanstalk, нужно прописать в Dockerfile после FROM nginx: EXPOSE 80 Потом опять та же мантра: git add . git commit -m "added expose 80" git push origin master ===== 09 Dockerizing Multiple Services ===== Сервис состоит из нескольких компонентов - самого приложения (подпапки client, api и worker), Postgres и Redis. Dockerfile подпапок client, api и worker: FROM node:alpine WORKDIR '/app' COPY ./package.json ./ RUN npm install COPY . . CMD ["npm", "run", "start"] Docker-compose.yml в корне: version: '3' services: # просто добавляем соответствующие контейнеры последних версий postgres: image: 'postgres:latest' redis: image: 'redis:latest' api: build: dockerfile: Dockerfile # без путей context: ./api # путь здесь volumes: - /app/node_modules # не трогать эту папку внутри контейнера - ./api:/app # скопировать в /app контейнера содержимое ./server # Следующая секция - переменные (configuration variables). # Переменные задают параметры контейнера при его запуске (не при создании образа). # В примере параметры хранятся в файле ./server/keys.js, # там всякие настройки базы, адреса хостов и порты, пароли и т. п. # Есть два способа задать значение переменной: # variableName=value - жёстко задать значение # variableName - взять значение с хоста environment: - REDIS_HOST=redis # в данном случае, имя сервиса - REDIS_PORT=6379 # и т. д. client: build: dockerfile: Dockerfile context: ./client volumes: - /app/node_modules - ./client:/app worker: build: dockerfile: Dockerfile context: ./worker volumes: - /app/node_modules - ./worker:/app Далее нужно добавить nginx как редиректор разных URL в разные сервисы. Это нужно, чтобы не плодить порты доступа в контейнеры этих сервисов. На примере nginx - настройки в файле default.conf: * Указать upstream-сервер client:3000 * Указать upstream-сервер server:5000 * Слушать порт 80 * Если кто приходит на /, слать на client upstream * Если кто приходит на /api, слать на server upstream Нужно создать в проекте папку nginx, а в ней - default.conf: upstream client { server client:3000; } upstream api { server api:5000; } server { listen 80; location / { proxy_pass http://client; } # Починить ругань на websockets, # которые обращаются на /sockjs-node location /sockjs-node { proxy_pass http://client; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } location /api { rewrite /api/(.*) /$1 break; proxy_pass http://api; } } Дальше нужно создать Dockerfile в папке ./nginx: FROM nginx WORKDIR '/app' COPY ./default.conf /etc/nginx/conf.d/default.conf Добавить в Docker-compose.yml сервис nginx: nginx: restart: always build: dockerfile: Dockerfile context: ./nginx ports: - 3050:80