Docker Compose
SFTP с sudo без пароля
Чтобы можно было использовать WinSCP для подключения и редактирования конфигов, требующих права root, разрешим повышать привилегии без ввода пароля.
echo "your_username ALL=(ALL:ALL) NOPASSWD: ALL" | tee /etc/sudoers.d/dont-prompt
sudo sed -i 's/^Subsystem.*sftp.*/Subsystem sftp sudo -n true \&\& sudo -n \/usr\/lib\/openssh\/sftp-server || \/usr\/lib\/openssh\/sftp-server/' /etc/ssh/sshd_config
Установка Docker Compose
Использую официальную документацию, установим Docker и Docker Compose из официального репозитория.
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker --version
docker compose version
Безопасность Docker
В среде безопасности есть два варианта: user namespace и rootless mode. Рассмотрим их подробнее.
https://rezbez.ru/article/bezopasnost-kontejnerov-na-primere-platformy-docker https://selectel.ru/blog/courses/docker-security/ https://medium.com/@SecurityArchitect/docker-security-settings-for-running-untrusted-trusted-containers-at-the-same-time-88c4ca012726
User namespace
Пространство имен пользователей (user namespace) - процесс, запущенный от имени root в контейнере, может работать от имени другого (не root) пользователя на хосте; другими словами, процесс имеет полные привилегии для операций внутри пространства имен пользователей, но непривилегирован для операций вне этого пространства имен.
Запуск приложения в контейнере происходит обычно под непривилегированным пользователем. Однако если процесс в контейнере нужно запускать под пользователем root, то используя user namespace мы переназначим пользователя на непривилигированного на Docker хосте (re-map). Демон Docker продолжает запускаться под пользователем root. Для этого используется подчиненный идентификатор пользователя из файла /etc/subuid:
testuser:231072:65536
Файл конфигруации Docker daemon.json для пользователя dockremap, которого создает Docker автоматически если указать настройку:
{ "userns-remap": "default" }
id dockremap
grep dockremap /etc/subuid
docker run hello-world
ls -l /var/lib/docker/231072.231072/
Rootless mode
Демон Docker и контейнеры запускаются под непривилегированным пользователем. Для этого используется пользовательский неймспейс. Публикация портов <1024 требует специальной настройки. Использует скрипт по установке dockerd-rootless-setuptool.sh из пакета docker-ce-rootless-extras. Устанавливается под непривилегированным пользователем. Сервис Docker запускается из пути пользователя ~/.config/systemd/user/docker.service . Папка с данными в пути пользователя ~/.local/share/docker . Конфигурация демона в пути пользователя ~/.config/docker . Сокет демона хранится в пути пользователя unix:///run/user/$UIDdocker.sock .
Docker Compose Rootless User Namespace
https://docs.docker.com/engine/security/rootless/ https://docs.docker.com/engine/security/userns-remap/ https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2314 https://docs.dev.br/docs/docker/doc/manuals/engine/security/userns-remap https://wiki.archlinux.org/title/Docker#User_namespace_isolation https://blog.it-playground.eu/accessing-host-socket-when-using-namespaces-for-docker-isolation/ https://stackoverflow.com/questions/76307484/docker-rootless-mode-without-userns-remap/78419102#78419102 https://www.redhat.com/sysadmin/rootless-podman-user-namespace-modes https://blog.devops.dev/docker-isolation-with-namespace-6a5e6e49b570 Rootless volumes https://github.com/moby/moby/issues/45919 mount volume as user other than root https://github.com/moby/moby/issues/2259 https://github.com/mamba-org/micromamba-docker/issues/407 https://stackoverflow.com/questions/72157304/restart-docker-daemon-in-rootless-mode-on-linux https://security.stackexchange.com/questions/270278/does-it-matter-if-my-docker-container-image-is-rootless-if-docker-daemon-is-roo https://collabnix.com/how-to-run-docker-in-a-rootless-mode/ https://www.redhat.com/sysadmin/user-flag-rootless-containers https://github.com/nestybox/sysbox/tree/master
Mount volume in rootless
mkdir -p tmp && ls -ld tmp && docker run --user $(id -u):$(id -g) -it --rm -v ./tmp:/tmp/test alpine:latest stat -c "%u" /tmp/test
Узнать ID пользователя:
docker compose exec web sh -c 'id -u'
При монтировании каталога с хоста в контейнер не происходит сопоставления разрешений (поэтому в случае -v
Give the container read/write access to a mapped directory on the host.
https://stackoverflow.com/questions/77640424/how-can-i-map-user-ids-from-host-to-container-using-docker-desktop
ID-Mapped mounts в SysBox
Сопоставление идентификаторов пользователей и групп файловой системы между пространством имен пользователей Linux контейнера и начальным пространством имен пользователей хоста.
Например, внутри контейнера Sysbox диапазон идентификаторов пользователей 0->65535 всегда отображается на непривилегированный диапазон идентификаторов пользователей на уровне хоста, выбранный Sysbox (например, 100000->165535) через пространство имен пользователей Linux. Таким образом, контейнерные процессы полностью непривилегированы на уровне хоста.
Однако такое сопоставление подразумевает, что если файл хоста с идентификатором пользователя 1000 будет смонтирован в контейнер, то внутри контейнера он будет отображаться как nobody:nogroup, поскольку идентификатор пользователя 1000 находится вне диапазона 100000->165536.
Функция ядра «ID-mapped mounts» решает эту проблему. Она позволяет Sysbox попросить ядро изменить идентификаторы пользователей (и идентификаторы групп) для хост-файлов, монтируемых в контейнер. Следуя приведенному выше примеру, хост-файл с user-ID 1000 теперь будет отображаться в контейнере с user-ID 1000, так как ядро отобразит user-ID 1000->101000 (и наоборот).
Это выгодно, поскольку с точки зрения пользователя вам не нужно беспокоиться о том, какие сопоставления пользовательского пространства имен были назначены Sysbox для контейнера. Это также означает, что теперь вы можете без проблем обмениваться файлами между хостом и контейнером или между контейнерами, наслаждаясь при этом дополнительной изоляцией и безопасностью, обеспечиваемыми пространством имен пользователей Linux.
Например, если у нас есть каталог хоста под названием my-host-dir, где файлы принадлежат пользователям в диапазоне [0:65536], и этот каталог привязано монтируется в системный контейнер следующим образом:
$ docker run --runtime=sysbox-runc -it --mount type=bind,source=my-host-dir,target=/mnt/my-host-dir alpine
то Sysbox настроит монтирование с привязкой к ID на my-host-dir, в результате чего файлы будут отображаться с тем же правом собственности ([0:65536]) внутри контейнера, даже если идентификаторы пользователя и группы контейнера привязаны к совершенно другому набору ID на хосте (например, 100000->165536).
Таким образом, пользователям не нужно беспокоиться о том, какие идентификаторы хостов отображаются на контейнер через пространство имен пользователей Linux. Sysbox позаботится о том, чтобы смонтированные файлы отображались в контейнере с правильными разрешениями.
Это позволяет контейнерам Sysbox обмениваться файлами с хостом или другими контейнерами.
Обратите внимание, что если на вашем хосте нет ни одного из ID-сопоставлений, то файлы хоста, смонтированные в контейнер Sysbox, будут отображаться как принадлежащие nobody:nogroup внутри контейнера.
I believe that you should be running the containers that require to talk to the docker socket in the host user namespace. userns_mode: 'host'
Запуск контейнера под определенным пользователм
Основная проблема с привязкой монтирования в Docker заключается в том, что хост и контейнер могут иметь разные сопоставления идентификаторов пользователя (UID) и группы (GID).
Использование подхода с общим UID/GID для решения проблемы несоответствия прав в Docker подразумевает обеспечение запуска контейнера с теми же идентификаторами пользователей и групп, что и у владельцев файлов на хост-системе.
При использовании связывающего монтирования доступ к файлам на хосте получает контейнер. Если пользователь в контейнере не имеет совпадающих UID/GID с файловой системой хоста, это может привести к проблемам с правами доступа. Если настроить параметры контейнера так, чтобы запускать процессы с UID/GID, совпадающими с UID/ID хоста, эти проблемы можно устранить.
Во-первых, определите UID и GID пользователя хоста, которому принадлежат файлы, к которым вы планируете предоставить общий доступ:
id -u <username>
id -g <username>
Запуск контейнера с определенным UID/GID: - При создании контейнера Docker вы можете указать UID и GID с помощью флага --user:
docker run --user <uid>:<gid> -v /host/path:/container/path <image_name>
Замените
Дайте команду Docker запускаться с этими идентификаторами. Пользовательский сценарий входа может способствовать динамическому назначению UID/GID на основе переменных окружения. Вам нужен Dockerfile, который устанавливает пользователя среды, и скрипт точки входа, который настраивает UID и GID при запуске.
Dockerfile: Пользователь с UID/GID по умолчанию создается для того, чтобы Docker был доволен, пока вы не измените его в соответствии с потребностями во время выполнения.
FROM ubuntu:20.04
# Add a user and group with specific IDs, we will change these later
RUN groupadd -g 1000 appgroup && \
useradd -m -u 1000 -g appgroup appuser
# Copy the entrypoint script
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Set the user and entrypoint
USER appuser
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
entrypoint.sh: - Проверяет, установлены ли значения HOST_UID и HOST_GID. - Использует usermod и groupmod для изменения идентификатора пользователя на указанный в переменных окружения. - Корректирует права собственности на домашний каталог для обеспечения согласованности. Строка exec «$@» выполняет команду, переданную из CMD или аргументы, предоставленные через docker run.
#!/bin/bash
# Update UID and GID of 'appuser'
if [ ! -z "$HOST_UID" ] && [ ! -z "$HOST_GID" ]; then
echo "Changing appuser UID to $HOST_UID and GID to $HOST_GID"
usermod -u $HOST_UID -o appuser
groupmod -g $HOST_GID -o appgroup
chown -R appuser:appgroup /home/appuser
fi
# Execute the main process
exec "$@"
При запуске контейнера передайте UID/GID текущего пользователя хоста с помощью переменных окружения:
- Теперь ваш основной процесс контейнера (some_command_here
) должен запускаться с нужными правами.
docker run \
-e HOST_UID=$(id -u <host_user>) \
-e HOST_GID=$(id -g <host_user>) \
-v /host/path:/container/path \
<image_name> \
some_command_here
Согласование UID/GID между хостом и контейнерами Docker позволяет поддерживать согласованность и избегать проблем с разрешениями, упрощая доступ к смонтированным с привязкой томам.
Volumes
Named Volumes: Docker volumes managed by Docker, which are not bound to a specific location on the host file system. These volumes abstract away the underlying location, helping mitigate some of the permission issues, as they are fully managed by Docker and can be used with the docker run -v volume_name:/container/path command.
Сеть Docker Compose
По умолчанию Docker запускается через несетевой сокет Unix.
Типы сетей:
- bridge - трансляция сетевых адресов (NAT), «userland-proxy».
- host - контейнеру не выделяется собственный IP-адрес. Например, если вы запускаете контейнер, который привязывается к порту 80, и используете сеть host, приложение контейнера будет доступно на порту 80 на IP-адресе хоста. Никаких «userland-proxy».
Опубликованные порты контейнеров сопоставляются с IP-адресами хостов. По умолчанию всем внешним IP-адресам разрешено подключаться к портам, которые были опубликованы на адресах хоста Docker.
По умолчанию контейнеры используют те же DNS-серверы, что и хост. Встроенный DNS-сервер перенаправляет внешние DNS-поиски на DNS-серверы, настроенные на хосте.
Пользовательские хосты, заданные в файле /etc/hosts на хост-машине, не наследуются контейнерами.
Контейнеры могут разрешать друг друга по имени или псевдониму.
networks:
frontend:
# Specify driver options
driver: bridge
driver_opts:
com.docker.network.bridge.host_binding_ipv4: "127.0.0.1"
services:
frontend:
image: example/webapp
networks:
front-tier:
ipv4_address: 172.16.238.10
networks:
front-tier:
ipam:
driver: default
config:
- subnet: "172.16.238.0/24"
Docker Storage
Данные не сохраняются, когда контейнер перестает существовать.
В Docker есть две возможности для контейнеров хранить файлы на хост-машине, чтобы они сохранялись даже после остановки контейнера: тома и связывающие монтирования.
Docker Compose environments
https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/ https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/
Don't use environment variables to pass sensitive information, such as passwords, in to your containers. Use secrets instead.
Docker Compose use secrets instead of env variables
https://docs.docker.com/compose/how-tos/use-secrets/
Docker Compose Service depends on
https://docs.docker.com/compose/how-tos/startup-order/
phpmyadmin:
image: phpmyadmin
depends_on: [db]
ENtrypoint and CMD
Эффективная комбинация - использование ENTRYPOINT и CMD для обеспечения гибкого, но контролируемого процесса запуска: Dockerfile: В этом примере: - ENTRYPOINT указывает на сценарий точки входа. - CMD предоставляет аргумент по умолчанию, my_app, который может быть командой, выполняемой сценарием точки входа. - Вы можете переопределить аргументы CMD во время выполнения, указав дополнительные аргументы в docker run.
FROM ubuntu:20.04
# Install application dependencies
RUN apt-get update && apt-get install -y curl
# Environment Variables
ENV APP_ENV=production
# Copy entrypoint script
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Set entrypoint
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# Set default command arguments
CMD ["my_app"]
Docker Project name
https://docs.docker.com/compose/how-tos/project-name/
Configuration project
mkdir my_docker_project
cd my_docker_project
nano docker-compose.yml
Subnet
docker network create --subnet=172.18.0.0/16 mynet
networks:
mynet:
external:
name: mynet
networks:
mynet:
driver: bridge
ipam:
config:
- subnet: 192.168.20.0/24
gateway: 192.168.20.1
Traefik + WHOAMI
# docker network create indocker-app-network
version: '3.8'
services:
my-nginx:
image: nginx:latest
labels:
- traefik.enable=true
# Router docs: https://doc.traefik.io/traefik/routing/providers/docker/#routers
- traefik.http.routers.my-nginx-router.rule=Host(`my-nginx.indocker.app`)
- traefik.http.routers.my-nginx-router.service=my-nginx-service
# Service docs: https://doc.traefik.io/traefik/routing/providers/docker/#services
- traefik.http.services.my-nginx-service.loadbalancer.server.port=80
- traefik.http.services.my-nginx-service.loadbalancer.healthcheck.path=/
- traefik.http.services.my-nginx-service.loadbalancer.healthcheck.interval=5s
networks: [indocker-app-network]
security_opt: [no-new-privileges:true]
whoami:
image: containous/whoami:latest
labels:
- traefik.enable=true
- traefik.http.routers.whoami-router.rule=Host(`whoami.indocker.app`)
- traefik.http.routers.whoami-router.entrypoints=http # force HTTP instead of HTTPS
- traefik.http.routers.whoami-router.service=whoami-service
- traefik.http.services.whoami-service.loadbalancer.server.port=8080
command: --port 8080
networks: [indocker-app-network]
security_opt: [no-new-privileges:true]
networks:
indocker-app-network:
external: true
Docker compose YML
version: '3'
services:
web:
image: nginx:alpine
ports:
- "80:80"
database:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: examplepassword
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
app:
image: your_custom_app_image:latest
depends_on:
- database
environment:
DB_HOST: database
DB_USER: exampleuser
DB_PASS: examplepass
DB_NAME: exampledb
version: '3.7'
services:
app1:
image: your_app1_image:latest
container_name: app1
networks:
mynet:
ipv4_address: 172.18.0.2
volumes:
- /var/docker/app1/data:/app/data
- /var/docker/app1/config:/app/config
environment:
- VIRTUAL_HOST=app1.in.devexperts.com
restart: always
app2:
image: your_app2_image:latest
container_name: app2
networks:
mynet:
ipv4_address: 172.18.0.3
volumes:
- /var/docker/app2/data:/app/data
- /var/docker/app2/config:/app/config
environment:
- VIRTUAL_HOST=app2.in.devexperts.com
restart: always
postgres:
image: postgres:latest
container_name: postgres
networks:
mynet:
ipv4_address: 172.18.0.4
volumes:
- /var/docker/postgres/data:/var/lib/postgresql/data
environment:
POSTGRES_USER: exampleuser
POSTGRES_PASSWORD: examplepassword
restart: always
nginx-proxy:
image: nginx
container_name: nginx-proxy
networks:
mynet:
ipv4_address: 172.18.0.5
volumes:
- /var/run/docker.sock:/tmp/docker.sock
- /var/docker/nginx-proxy/conf:/etc/nginx/conf.d
- /var/docker/nginx-proxy/vhost.d:/etc/nginx/vhost.d:ro
- /var/docker/nginx-proxy/html:/usr/share/nginx/html:ro
- /var/docker/certs:/etc/nginx/certs:ro
ports:
- "80:80"
- "443:443"
restart: always
networks:
mynet:
external:
name: mynet
Run Docker Compose
docker-compose up -d
docker-compose ps
docker-compose logs
docker-compose down
Traefik + Portainer + Keycloak https://www.zencod.ru/articles/traefik-portainer-keycloak/
Portainer behind Traefik Proxy https://docs.portainer.io/advanced/reverse-proxy/traefik
Traefikация сервера https://habr.com/ru/articles/757820/
Docker Compose Traefik v2 https://rafrasenberg.com/docker-compose-traefik-v2/
Portainer with Traefik labels https://www-ayedo-dev.translate.goog/posts/portainer-mit-traefik-labels-bereitstellen-und-unter-dns-eintrag-nutzen/?_x_tr_sl=de&_x_tr_tl=ru&_x_tr_hl=ru&_x_tr_pto=sc
Traefik wildcard certificate https://blog.sebastian-daschner.com/entries/rolling-updates-production-traefik
Traefik 3 and FREE Wildcard Certificates https://technotim.live/posts/traefik-3-docker-certificates/
gitlab-traefik-letsencrypt https://github.com/heyvaldemar/gitlab-traefik-letsencrypt-docker-compose/blob/main/gitlab-traefik-letsencrypt-docker-compose.yml
Docker, GitLab, Traefik https://github.com/Akkarine/demo_cicd/tree/master
GitLab https://sysadmintalks.ru/docker-gitlab-install/
Upgrade Gitlab and Postgres https://codereviewvideos.com/how-to-upgrade-gitlab-and-postgres-with-docker-compose/
GitLab + nginx + PostgreSQL https://rascal.su/blog/2016/09/18/%D1%80%D0%B0%D0%B7%D0%B2%D0%BE%D1%80%D0%B0%D1%87%D0%B8%D0%B2%D0%B0%D0%B5%D0%BC-gitlab-%D0%BD%D0%B0-%D0%B1%D0%B0%D0%B7%D0%B5-docker/
Gitlab + nginx https://ealebed.github.io/posts/2017/%D0%BF%D0%BE%D0%B4%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D0%BC-gitlab-%D0%B2-docker-%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D0%B0%D1%85-%D0%B7%D0%B0-nginx/
GitLab + Ansible-lint https://habr.com/ru/companies/slurm/articles/310278/
GitLab + Traefik https://www.heyvaldemar.net/ustanovka-gitlab-s-ispolzovaniem-docker-compose/
GitLab + nginx + PostgreSQL https://github.com/SedovSG/docker-gitlab