Приветствую, дорогие читатели! Некоторое время назад у меня в голове засела мысль о том, что неплохо было бы посидеть немного с Docker, запустить локально всякие сервисы, имеющие отношение к фронтенду, с которыми довелось работать, и рассмотреть их более детально. Причем обладая правами администратора! И хорошо бы еще, чтобы была какая‑то практическая польза. В общем, чтобы можно было все потрогать и ничего за это не было. Примерно как в контактном зоопарке!
Заселением такого зоопарка мы и займемся. А в следующий раз будем играть с питомцами.
Кому это может пригодиться
Должен предупредить. Эту статью я задумывал в первую очередь для фронтендеров, но в этот раз будет довольно мало непосредственно фронтенда. Больше про инфраструктуру, которая нас окружает. Об использовании на практике инструментов, о которых пойдет речь, расскажу в следующий раз.
Материал, изложенный ниже, может оказаться полезным тем фронтенд‑разработчикам, кто хочет:
узнать, как быстро развернуть и настроить полезные инструменты для разработки;
познакомиться с возможностями некоторых сервисов;
взглянуть на то, как устроен файл compose.yaml и как с ним работать;
просто ненадолго вылезти из своих формочек на React и расширить кругозор;
понажимать кнопку
? Admin=)
Вообще, весь эксперимент показался мне весьма интересным, а некоторые сервисы я по итогу встроил в процессы работы над своими домашними проектами. Но об этом позже.
Общий план прост. Для начала обозначим несколько общих идей. Потом пройдемся по всем текущим обитателям моего зоопарка. По пути буду рассказывать о проблемах, возникших в процессе. А под конец немного порассуждаем обо всем увиденном.
С чего начнем
Необходимые знания и ПО для старта
Строить контактный зоопарк в Docker, не имея никакого представления о контейнеризации, — затея не самая перспективная. Поэтому рекомендую прочесть немного базы: о контейнерах, об образах и о Docker Compose. Это поможет немного освоиться в терминологии Docker.
При этом данная статья не претендует на статус подробного руководства по развертыванию и настройке контейнеров. В интернетах вообще, и на Хабре в частности, есть огромное количество материалов, рассчитанных на любой уровень, и рассматривающих самые разнообразные кейсы. Мы же тут ради легкой прогулки=)
На самом деле, все, о чем я тут расскажу, является результатом длительного (иногда) копания в документации (не только Docker, но вообще всего, о чем пойдет речь) и поиска решений возникающих проблем. И при изучении очередного раздела я фиксировал для себя полезные моменты: «Ага, эта штука еще и так умеет работать, позже попробую. Когда‑нибудь». А на текущий момент получилась такая вот компиляция. Всем рекомендую читать документацию!
Для управления контейнерами я использую Docker Desktop для Windows. Минимум возни с командной строкой, все для ленивых! На сегодняшний день успешная установка Docker Desktop говорит о том, что все необходимое для продолжения работы есть в системе: собственно Docker Engine, Docker Compose и Docker CLI, WSL, а также дополнительный функционал Docker Desktop, в котором можно покопаться в поисках че��о‑нибудь интересного.
Подготовка compose.yaml
Название файла compose.yaml говорит само за себя. По сути, это описание композиции: общего окружения и набора контейнеров.
В нашем файле будет всего два раздела: services, в котором содержатся определения сервисов, и networks, в котором указана одна сеть zoo без дополнительных настроек.
Изначально у меня было три раздела. Я начал разбираться с использованием именованных томов, которые описываются в разделе
volumes. Но в процессе отказался в пользу указания прямого пути.
Итак, структура файла:
services:
# сюда будут добавляться определения сервисов
networks:
zoo:
По мере добавления сервисов можно запускать все командой:
docker compose -p zoo up -d
-p zoo тут просто название проекта, а -d — флаг, указывающий, что нужно запускать контейнеры в фоновом режиме. Имя compose.yaml входит в число имен, которые автоматически цепляются при вызове docker compose, так что указывать его нигде не нужно.
Verdaccio
Знакомьтесь, первый питомец моего контактного зоопарка.
Verdaccio — это реестр npm‑пакетов. Фронтендеру такой реестр однозначно может пригодиться для публикации и тестирования собственных пакетов. Verdaccio довольно простенький: из основного необходимого функционала — умение хранить уже установленные пакеты и ползать в npmjs.org за отсутствующими пакетами.
Определение сервиса (к закомментированным строкам вернемся в следующем разделе):
verdaccio:
image: verdaccio/verdaccio:6.2
container_name: verdaccio
# labels:
# traefik.http.routers.verdaccio.rule: 'Host(`registry.localhost`)
environment:
- VERDACCIO_PORT=4873
ports:
- '4873:4873'
volumes:
- /c/storage/npm_proxy_registry:/verdaccio/storage
networks:
- zoo
Немного расскажу о том, что тут происходит.
image — название образа сервиса с указанием версии. Все образы дергаются из Docker Hub. Приведенные номера версий я зафиксировал в compose.yaml для большинства сервисов после проверки, что все корректно работает (авторы ПО по каким‑то причинам считают, что нам жизненно необходима именно версия latest, а нас больше интересуют стабильность и повторяемость).
container_name — имя контейнера. Внезапно.
environment — в этом разделе приведены переменные окружения, которые будут использоваться внутри контейнера.
ports — определяет, какой порт хоста будет виден, и какой порт внутри контейнера будет ему соответствовать.
volumes — в данном случае указывает в каких папках на хосте будут храниться данные из соответствующих папок контейнера.
network — название сети, которое мы указали ранее.
Возвращаться к этим параметрам уже не буду, а о других скажу пару слов по мере их появления в определениях.
Вот тут сделаю небольшую ремарку. Как я уже говорил ранее, это не руководство по контейнеризации. Тома, сети, порты, окружение, еще некоторые параметры, о которых я еще скажу позднее, — это не простые переменные, которым можно назначить одно из, условно, 2–3 возможных значений. Каждый параметр вносит определенный вклад в общую инфраструктуру. На примере моего определения сервиса: я оставил значение 4873 для порта на хосте по умолчанию (как было в документации). Но кому‑то может не нравиться такой подход к маппингу портов. Кто‑то является адептом исключительно именованных томов. Кто‑то скажет, что вообще так все делать — небезопасно...
Если совсем немного освоиться с матчастью, все можно сделать красиво, безопасно, по даосским практикам, в соответствии со своей натальной картой... Да как угодно.
А сейчас я решил, что моя задача решена, и приведенная конфигурация меня устраивает в таком виде. В конце концов, не на прод несу=)
Если теперь выпо��нить команду docker compose -p zoo up -d, запустится первый контейнер, и Verdaccio будет доступен по адресу http://localhost:4873.
Вот на этом моменте я понял, что рано или поздно могу запутаться в портах. Захотелось сделать так, чтобы у сервисов были удобные адреса. Поэтому следующий житель, который появился в моем зоопарке — Traefik. Это, я бы сказал, смотритель, который будет в дальнейшем подсказывать мне, как проще пройти к тому или иному животному.
Traefik
Traefik — это обратный прокси‑сервер, балансировщик нагрузки и маршрутизатор HTTP. Нам сейчас нужно из всего этого минимум функционала: только чуть‑чуть маршрутизации.
traefik:
image: traefik:v3.5.4
container_name: traefik
command:
- '--api.insecure=true'
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
ports:
- '80:80'
- '8080:8080'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- zoo
command в данном случае добавляет флаги к основной команде, которая прописана в образе.
Теперь можно убрать комментарий из определения Verdaccio, и доступ к сервису появится также по адресу http://registry.localhost.
По адресу http://localhost:8080/dashboard/ доступен UI Traefik. По меткам (labels) Traefik собирает правила для маршрутизации. Ссылку на подробную документацию по этой теме я оставлю в конце статьи.
PostgreSQL
Нам придется отойти от сервисов для фронтендеров еще на 500 м. Далее нам понадобится контейнер с БД. Следующие два питомца используют PostgreSQL для собственных нужд.
postgres:
image: postgres:17.6
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: zoomaster
POSTGRES_PASSWORD: zoomaster0
POSTGRES_DB: zoo
volumes:
- /c/storage/postgres:/var/lib/postgresql
- /c/storage/postgres/data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -h postgres
retries: 5
start_period: 10s
interval: 5s
timeout: 5s
networks:
- zoo
restart определяет политику перезапуска контейнера, а healthcheck отвечает за команду, которая подтвердит, что контейнер работает в штатном режиме.
Разбираясь, как правильно написать определения для Bugsink и SonarQube, я видел, что обоим требуется дополнительный контейнер с PostgreSQL, но в документации предлагаются разные определения для этих контейнеров. Изначально у меня так и было: два контейнера из разных образов, каждый со своим определением. Учитывая, что к текущему моменту у меня было уже достаточно понимания, за что отвечает каждый из параметров, я заменил два определения на одно и подправил определения зависимых сервисов.
Все заработало, вот только проворачивать подобное нужно очень аккуратно. У меня теперь два сервиса пользуются одной БД. Конфликтов пока не замечено, но риски остаются. Кажется, тут более правильным решением было бы добавить скрипт, который будет создавать нужные БД в контейнере с PostgreSQL, а уже их названия дальше указывать для сервисов.
Bugsink
Следующий зверек в нашем контактном зоопарке — трекер ошибок Bugsink. Это аналог более известного Sentry, с совместимым API, но не такой громоздкий. С помощью этого сервиса можно научиться отслеживать ошибки и понаблюдать за трассировкой ошибок.
bugsink:
image: bugsink/bugsink:2.0.4
container_name: bugsink
labels:
traefik.http.routers.bugsink.rule: 'Host(`bugsink.localhost`)'
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
ports:
- '8000:8000'
environment:
SECRET_KEY: RMLYThim9NybWgXiUGat32Aa0Qbgqscf4NPDQuZO2glcZPOiXn
CREATE_SUPERUSER: admin:admin
PORT: 8000
DATABASE_URL: postgresql://zoomaster:zoomaster0@postgres:5432/zoo
BEHIND_HTTPS_PROXY: 'false'
BASE_URL: 'http://bugsink.localhost'
networks:
- zoo
depends_on указывает на сервис, который должен исправно работать для корректной работы текущего сервиса.
Тут я впервые за время эксперимента увидел передачу секрета в окружение контейнера (переменная
SECRET_KEYвenvironment). Вообще, не рекомендую делать так, как я сделал выше. Стоит использовать, например, хранилище секретов. Для себя я отметил, что обязательно надо будет поселить какой‑нибудь Vault в мой зоопарк.Оставляю данный ключ в статье, так как это тот самый ключ из документации. Можно сгенерировать любой другой, но стоит ознакомиться с требованиями к ключу.
Почему я обращаю на это внимание сейчас? Для себя отношу это к разряду таких вещей, о которых лучше не забывать, на них не забивать и следить за порядком. Да и умение работать с секретами — навык весьма полезный, для фронтендера в том числе=)
SonarQube
SonarQube — инструмент для статического анализа и проверки кода на качество и безопасность. Такое животное мы точно хотим поселить в контактном зоопарке!
sonarqube:
image: sonarqube:community
container_name: sonarqube
labels:
traefik.http.routers.sonarqube.rule: 'Host(`sonarqube.localhost`)'
read_only: true
depends_on:
postgres:
condition: service_healthy
environment:
SONAR_JDBC_URL: jdbc:postgresql://postgres:5432/zoo
SONAR_JDBC_USERNAME: zoomaster
SONAR_JDBC_PASSWORD: zoomaster0
volumes:
- /c/storage/sonarqube/data:/opt/sonarqube/data
- /c/storage/sonarqube/extensions:/opt/sonarqube/extensions
- /c/storage/sonarqube/logs:/opt/sonarqube/logs
- /c/storage/sonarqube/temp:/opt/sonarqube/temp
ports:
- '9000:9000'
networks:
- zoo
read_only указывает, что файловая система контейнера работает только для чтения.
Где‑то тут я столкнулся с некоторыми проблемами с производительностью, о которых расскажу чуть позже.
Шалость № 1. Самый большой зверь
У нас получилась инфраструктура, включающая реестр пакетов, трекер ошибок и статический анализатор. Можно было бы остановиться и приступить к изучению этого инструментария, но давайте немного пошалим. Что, если...
gitlab:
image: gitlab/gitlab-ce:18.5.1-ce.0
container_name: gitlab
labels:
traefik.http.routers.gitlab.rule: 'Host(`gitlab.localhost`)'
restart: always
environment:
GITLAB_OMNIBUS_CONFIG: |
# Add any other gitlab.rb configuration here, each on its own line
external_url 'http://localhost:8929'
ports:
- '8929:8929'
volumes:
- /c/storage/gitlab/config:/etc/gitlab'
- /c/storage/gitlab/logs:/var/log/gitlab'
- /c/storage/gitlab/data:/var/opt/gitlab'
shm_size: '256m'
networks:
- zoo
Да, вам не показалось. Мы поселили в контактном зоопарке целый GitLab!
Да, это уже откровенное баловство. Но всегда было интересно, что есть в админке. Ранее приходилось сталкиваться с контейнеризацией фронтенд‑приложений в GitLab CI/CD, с возникновением каких‑то ошибок на различных этапах пайплайна, с настройками репозиториев и MR, и хотелось самому глянуть, что там вообще можно покрутить=)
Это я к тому, что у себя локально можно развернуть что угодно. Хватило бы ресурсов...

Итоги
compose.yaml со всеми сервисами
Если собрать все вместе, получится файл compose.yaml со следующим содержанием:
compose.yaml
services:
traefik:
image: traefik:v3.5.4
container_name: traefik
command:
- '--api.insecure=true'
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
ports:
- '80:80'
- '8080:8080'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- zoo
postgres:
image: postgres:17.6
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: zoomaster
POSTGRES_PASSWORD: zoomaster0
POSTGRES_DB: zoo
volumes:
- /c/storage/postgres:/var/lib/postgresql
- /c/storage/postgres/data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -h postgres
retries: 5
start_period: 10s
interval: 5s
timeout: 5s
networks:
- zoo
verdaccio:
image: verdaccio/verdaccio:6.2
container_name: verdaccio
labels:
traefik.http.routers.verdaccio.rule: 'Host(`registry.localhost`)'
environment:
- VERDACCIO_PORT=4873
ports:
- '4873:4873'
volumes:
- /c/storage/npm_proxy_registry:/verdaccio/storage
networks:
- zoo
bugsink:
image: bugsink/bugsink:2.0.4
container_name: bugsink
labels:
traefik.http.routers.bugsink.rule: 'Host(`bugsink.localhost`)'
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
ports:
- '8000:8000'
environment:
SECRET_KEY: RMLYThim9NybWgXiUGat32Aa0Qbgqscf4NPDQuZO2glcZPOiXn
CREATE_SUPERUSER: admin:admin
PORT: 8000
DATABASE_URL: postgresql://zoomaster:zoomaster0@postgres:5432/zoo
BEHIND_HTTPS_PROXY: 'false'
BASE_URL: 'http://bugsink.localhost'
networks:
- zoo
sonarqube:
image: sonarqube:community
container_name: sonarqube
labels:
traefik.http.routers.sonarqube.rule: 'Host(`sonarqube.localhost`)'
read_only: true
depends_on:
postgres:
condition: service_healthy
environment:
SONAR_JDBC_URL: jdbc:postgresql://postgres:5432/zoo
SONAR_JDBC_USERNAME: zoomaster
SONAR_JDBC_PASSWORD: zoomaster0
volumes:
- /c/storage/sonarqube/data:/opt/sonarqube/data
- /c/storage/sonarqube/extensions:/opt/sonarqube/extensions
- /c/storage/sonarqube/logs:/opt/sonarqube/logs
- /c/storage/sonarqube/temp:/opt/sonarqube/temp
ports:
- '9000:9000'
networks:
- zoo
gitlab:
image: gitlab/gitlab-ce:18.5.1-ce.0
container_name: gitlab
labels:
traefik.http.routers.gitlab.rule: 'Host(`gitlab.localhost`)'
restart: always
environment:
GITLAB_OMNIBUS_CONFIG: |
# Add any other gitlab.rb configuration here, each on its own line
external_url 'http://localhost:8929'
ports:
- '8929:8929'
volumes:
- /c/storage/gitlab/config:/etc/gitlab'
- /c/storage/gitlab/logs:/var/log/gitlab'
- /c/storage/gitlab/data:/var/opt/gitlab'
shm_size: '256m'
networks:
- zoo
networks:
zoo:Что теперь с этим делать
Каждый из установленных сервисов уже готов к работе, доступен на хосте, указанном в labels, но требует выполнения определенных действий. Для использования Verdaccio, например, нужно подправить конфигурацию npm (глобально или на уровне проекта). А SonarQube сразу попросит изменить пароль администратора. А дальше все ограничено только вашей фантазией!
О настройке всего этого добра и работе с сервисами я расскажу в следующей статье.
Примечательно еще то, что большинство сервисов работают и с фронтендом, и с бэкендом.
Немного о производительности
Эксперимент проводился на довольно слабеньком ноутбуке (i3-1025G1, 16 ГБ ОЗУ). Некоторые подтормаживания были замечены после успешного запуска SonarQube, но не особо мешали. А вот когда в Docker был запущен GitLab, работать стало откровенно некомфортно. Запускать локально подобных монстров — сомнительная идея. Память сжиралась довольно стабильно. Нагрузка на ЦП резко подскакивала на этапе запуска контейнеров, а потом стабилизировалась примерно на одно уровне, зависящем от количества активных контейнеров.
Traefik, Verdaccio и Bugsink вместе отъедают не очень много памяти, с ними экспериментов будет больше. SonarQube я точно оставлю, но буду запускать по мере необходимости. А GitLab я удалю, как только он мне надоест, на него ресурсов не напасешься...
Тут все зависит от конфигурации вашего ПК. Меньше ресурсов потреблялось бы, если развернуть сервисы прямо в системе (хотя для Windows — это не точно). Но вся прелесть использования Docker и заключается в том, что мы почти не зависим от системы, наличия билдов нужных сервисов под конкретную ОС, наличия установленного ПО, необходимого для работы сервиса. Все, что требуется, уже упаковано в контейнер и ждет нас.
Под спойлером ниже оставлю несколько скучных скриншотов с графиками потребления памяти, а также пару скриншотов Docker Desktop, по которым можно оценить нагрузку в моем случае.
Скучные скриншоты





Вместо заключения
Какого‑то однозначного вывода я сделать не могу. Было очень интересно, хоть сначала не все получалось (за кадром остались и ошибки внутри контейнеров из‑за неправильных значений в определениях сервисов, и вся сопутствующая ненормативная лексика). Где‑то после пятого определения ориентироваться в параметрах становится проще, но прекрасно понимаю, что это довольно далеко от реального применения всего инструментария контейнеризации.
И вообще... Не бойтесь экспериментировать и старайтесь смотреть на вещи под разными углами!