Skip to content

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
Пользователю testuser с ID 231072 на Docker хосте будет соответствовать пользователю root (UID 0) в контейнере. Таким образом, даже запустив приложение в контейнере под привилегированным пользователем root, на Docker хосте оно не будет иметь повышенных прав. 231072 - Это число, на которое будет сопоставлен хост uid 1 в контейнере. Это означает, что процессы в пользовательском неймспейсе, запущенные testuser, принадлежат Docker хосту с UID 231072 (который в пространстве имен выглядит как UID 0) по 296607 (231072 + 65536 - 1). Функция user namespace настраивается через опцию userns-remap в Docker. Её значение может быть именем существующего пользователя на Docker хосте. Значение default означает, что будет создан пользователь dockremap. Использование этой функции ограничено. Например, в ситуациях, когда контейнеру необходим доступ к ресурсам на хосте Docker, например, для привязки монтирования к областям файловой системы, пользователь системы не сможет туда писать. Если на хосте Docker есть места, куда непривилегированный пользователь должен писать, настройте разрешения этих мест соответствующим образом. Используемый пользователь будет владеть директорией namespace в пути /var/lib/docker/. Образы и контейнеры сохраняются в поддириктории /var/lib/docker/. Когда user namespace включен, то все контейнеры запускаются под пользователем, который указан в настройках Docker.

Файл конфигруации 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 :) разрешения не сопоставляются. Пользователь в контейнере не может писать в смонтированный каталог в Docker в режиме rootless-mode.

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>

Замените и на идентификаторы пользователя и группы хоста, /host/path - на каталог хоста, /container/path - на каталог внутри контейнера, а - на используемый образ Docker.

Дайте команду 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

Источник 1