Docker Compose: build чи image на VPS
Пояснення для VPS: image завантажує опублікований тег, build збирає Dockerfile локально, а docker compose up може не побачити його зміни без повторної збірки.
Docker Compose: build та image — коротка відповідь
У файлі Docker Compose параметр image: задає ім’я образу, який потрібно завантажити з registry, а build: вказує Compose зібрати образ на цьому комп’ютері з Dockerfile. Якщо задано лише image:, Compose завантажує цей тег і запускає його. Якщо задано лише build:, Compose збирає образ локально та надає йому ім’я, сформоване з назви проєкту й сервісу. Якщо задано обидва параметри, Compose збирає образ локально, а потім присвоює результату ім’я з image:. Так можна зібрати образ і передати його в registry під вибраним вами іменем.
Це вся відмінність. Нижче описано, як вона проявляється під час роботи на сервері. Передбачається, що Docker Engine і плагін Compose вже встановлено; у матеріалі про запуск Docker на VPS описано цю частину.
Три форми повністю
Отримайте опублікований тег і запустіть його. Dockerfile на жодному етапі не використовується.
services:
web:
image: nginx:1.27
restart: unless-stopped
ports:
- "80:80"Зберіть образ із Dockerfile у поточному каталозі. Окрім базового образу, зазначеного в FROM, нічого не отримується.
services:
web:
build: .
restart: unless-stopped
ports:
- "80:80"Зберіть образ локально та призначте результату тег. Після цього docker compose push може передати цей точний тег до registry.
services:
web:
build:
context: .
dockerfile: Dockerfile
image: registry.example.com/acme/web:1.4.2
restart: unless-stopped
ports:
- "80:80"context — це каталог, який передається builder. dockerfile визначається відносно цього контексту, тому context: . разом із dockerfile: docker/prod.Dockerfile є звичайним і правильним варіантом. Виконайте docker compose images, щоб побачити назву образу та ID образу для кожного контейнера сервісу. Це найшвидший спосіб перевірити, яку саме з цих трьох форм ви фактично записали.
Чому docker compose up не перебудовує образ після зміни Dockerfile?
Тому що up перевіряє, чи існує образ, а не те, чи він актуальний.
Коли Compose запускає сервіс, у якому є секція build:, він шукає образ у локальному сховищі образів. Якщо образ із таким іменем уже існує, Compose використовує його. Він не читає Dockerfile, не порівнює файли вихідного коду й не перевіряє часові мітки. Специфікація Compose визначає це правило атрибутом pull_policy, а стандартна поведінка полягає в тому, що образ створюється лише за його відсутності. Наявний образ вважається достатнім.
Тому ви редагуєте app.py, запускаєте docker compose up -d, бачите в повідомленні Compose, що контейнер працює, але сервіс використовує старий код. Помилки не сталося, тому жодного попередження не було. Це найпоширеніша причина повідомлень «моя зміна не застосувалася» під час роботи з Compose. Підказкою є слово стану, яке Compose виводить поруч з іменем контейнера: для контейнера, який Compose замінив, буде вказано recreated або started, а для контейнера, який Compose вирішив не змінювати, — running.
Достатньо виконати дві перевірки. docker compose images виводить ID образу, який використовує кожен контейнер, тому зафіксуйте його перед розгортанням і порівняйте після нього. docker image ls містить стовпець CREATED; образ, створений до останнього коміту, є застарілим незалежно від того, що вивів скрипт розгортання.
Які прапорці примусово перебудовують образ
docker compose up -d --buildспочатку виконує збірку, а потім повторно створює кожен контейнер, образ якого змінився. Саме цей прапорець найчастіше потрібен користувачам.docker compose build webзбирає один сервіс і нічого не запускає. Додайте після ньогоdocker compose up --no-deps -d web, щоб замінити лише цей контейнер і залишити решту стека запущеною.docker compose build --no-cache webвідкидає всі кешовані шари та виконує збірку з першої інструкції.docker compose build --pullнамагається завантажити новішу версію базового образу вFROM, тому змінний тег, наприкладnode:22, отримує поточний вміст замість копії, завантаженої в березні.docker compose up -d --force-recreateповторно створює контейнери з образу, який вони вже використовують. Цей прапорець ніколи не виконує збірку. Частою помилкою є використати його замість--build.
Рішення також можна перенести у файл. Відповідно до специфікації Compose, pull_policy: build означає, що Compose збирає образ і перебудовує його, якщо образ уже існує. Кожен запуск up після цього виконує збірку, що доречно на ноутбуці, але рідко потрібно на сервері.
services:
web:
build: .
image: registry.example.com/acme/web:dev
pull_policy: buildВарто знати ще про одну взаємодію прапорців. docker compose pull також намагається завантажити образи для сервісів, які мають секцію build. Якщо завантаження не вдається, команда повідомляє, що образ потрібно зібрати. Передайте --ignore-buildable, щоб мовчки пропустити такі сервіси.
Як кеш складання визначає тривалість розгортання
Кожна інструкція в Dockerfile створює шар, а builder повторно використовує кешований шар, якщо ця інструкція та її вхідні дані не змінилися. Для COPY вхідними даними є вміст файлів, які копіюються. Якщо один шар не знайдено в кеші, усі наступні шари буде перебудовано, оскільки кожен шар створюється на основі файлової системи, яку сформував попередній шар.
Це правило визначає, чи триватиме розгортання секунди або хвилини. Розташовуйте інструкції Dockerfile від тих, що рідко змінюються, до тих, що змінюються під час кожного commit.
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]npm ci розташовано перед COPY . ., тому редагування файлу з вихідним кодом залишає шар інсталяції в кеші, а складання продовжується з кроку копіювання. Якщо поміняти ці два рядки місцями, зміна одного символу повторно інсталює всі залежності, оскільки COPY . . робить недійсним шар, на основі якого створюється npm ci. Така сама схема застосовується до pip install -r requirements.txt і go mod download.
--no-cache — правильний інструмент, якщо ви підозрюєте, що застарілий шар приховує ваше виправлення. Не використовуйте його за замовчуванням, оскільки він знищує повторне використання кешу, заради якого впорядковують Dockerfile.
Одне значення задає image, а Compose може його перевизначити: CMD у Dockerfile визначає, що image запускає за замовчуванням, а ключ command: у service замінює це значення. Тут важливо розуміти як взаємодіють command і entrypoint, оскільки перевизначення в Compose може змусити щойно створений image поводитися точно так само, як старий.
Контекст складання та .dockerignore
context: . означає, що Compose пакує цей каталог і передає його засобу складання до виконання першої інструкції. Передається все його вміст, зокрема .git і будь-який каталог даних, який ви зберігаєте поруч із вихідним кодом. Якщо складання незміненого проєкту затримується на етапі передавання контексту, це означає, що контекст надто великий.
Файл .dockerignore у корені контексту виключає шляхи з цього передавання. Синтаксис подібний до .gitignore.
.git
node_modules
*.log
data/
.envЦе дає дві переваги. Передається менше даних, тому кожне складання починається швидше. Крім того, COPY . . більше не зможе скопіювати .env до образу, звідки його зможе прочитати будь-хто, хто отримає цей образ.
Причиною поступового уповільнення складання часто є bind mount. Іменований том зберігається за межами каталогу проєкту, а bind mount, наприклад ./data:/var/lib/postgresql/data, розташований усередині контексту складання. Тому складання щотижня стає повільнішим у міру зростання бази даних. Один рядок у .dockerignore усуває проблему. У розділі Bind mount та іменовані томи описано ширший компроміс між цими варіантами.
Аргументи складання створюють менший за масштабом, але такий самий ризик. Значення, передані через args:, відображаються в історії образу та доступні кожному, хто має цей образ. Тому передавайте там номер версії, а не токен. У розділі Файли env і секрети в Compose описано, де натомість зберігати облікові дані.
Збирати на VPS чи збирати в іншому місці й завантажувати?
Збірка на сервері, який обслуговує ваш мережевий трафік, є стандартним варіантом, оскільки це найкоротший шлях: git pull, потім docker compose up -d --build. Це прийнятно для невеликого сервера, від якого поки ніхто не залежить. Такий підхід стає неприйнятним із двох причин, які можна виміряти, і ще однієї, що проявляється лише в невдалий момент.
Пам’ять. Під час збірки компілятори та бандлери працюють поруч із вашим активним застосунком, а саме вони споживають найбільше пам’яті в більшості стеків. На VPS із 1 GB JavaScript-бандлер або компіляція Rust зазвичай є найбільшим процесом на сервері. Коли ядру не вистачає пам’яті, воно завершує найбільший процес: або збірка зупиняється з Killed і кодом завершення 137, або замість неї завершується база даних, і сайт стає недоступним посеред розгортання. dmesg -T | grep -i oom виводить рядок про завершення процесу з його назвою, тому можна визначити, який саме з двох процесів було завершено, а не здогадуватися.
Диск. Кожна збірка залишає після себе шари, а builder зберігає власний кеш окремо від ваших образів. docker system df показує обидва значення, а рядок кешу збірки лише збільшується. Звільняйте місце за допомогою docker image prune для невикористовуваних образів і docker builder prune для кешованих шарів. Заповнений диск зупиняє не лише збірку. База даних також перестає записувати дані, а така несправність обходиться значно дорожче, ніж повільне розгортання.
Відтворюваність. Образ, зібраний на сервері, існує лише на цьому сервері. Щоб виконати відкат, потрібно переключитися на старий коміт і зібрати образ знову. Немає гарантії, що результат буде таким самим, оскільки базовий тег і дзеркала пакетів могли змінитися. Якщо збирати в іншому місці та надсилати тег, відкат перетворюється на редагування: вкажіть у image: попередній тег і виконайте docker compose up -d.
Надійна схема проста. Система безперервної інтеграції виконує збірку та надсилає registry.example.com/acme/web:<git-sha>, а файл Compose на VPS містить image: без ключа build:. Тоді розгортання складається з двох команд і майже не потребує пам’яті.
docker compose pull
docker compose up -dВиконайте docker login registry.example.com один раз на сервері, після чого Compose зможе завантажувати приватні теги.
Залиште секцію збірки для розроблення, а не видаляйте її, у файлі з назвою на ваш вибір.
# compose.dev.yaml
services:
web:
build:
context: .
pull_policy: builddocker compose -f compose.yaml -f compose.dev.yaml up -d --buildНазвіть цей файл compose.dev.yaml, а не compose.override.yaml. Compose автоматично завантажує файл перевизначень, якщо він присутній, тому випадково скопійований на сервер файл перевизначень непомітно знову запустить збірку там. У розділі Накладання кількох файлів Compose пояснено, як під час об’єднання визначається значення кожного ключа.
Архітектурна пастка під час складання в іншому середовищі
Образ містить архітектуру CPU, для якої його було зібрано. Якщо зібрати образ на ноутбуці з Apple Silicon, передати його до реєстру, а потім отримати цей тег на VPS з архітектурою x86_64, Docker попередить, що платформа запитаного образу не відповідає визначеній платформі хоста. Після цього процес завершується з exec format error. Це схоже на пошкоджений бінарний файл, але ним не є. Явно вкажіть цільову платформу під час складання:
docker buildx build --platform linux/amd64 \
-t registry.example.com/acme/web:1.4.2 --push .Така сама невідповідність виникає у зворотному напрямку, якщо ваш ноутбук використовує x86, а ви запускаєте ARM VPS замість VPS з архітектурою x86. Якщо доручити CI збирати образи для архітектури, на якій ви їх розгортаєте, це питання зникає.
Що перевірити після розгортання
docker compose imagesвиводить образ і тег, які використовує кожен запущений контейнер. Змінений ID образу підтверджує, що нова збірка працює.docker compose configвиводить об’єднаний файл після підстановки змінних. Так можна перевірити остаточне ім’я образу, яке використає Compose, ще до запуску.docker compose logs -f webпротягом перших 30 секунд після заміни. Контейнер, який запускається й завершує роботу, зациклює перезапуски замість стабільної роботи. Цикл непомітний, якщо його не перевірити.docker image lsпоказує стовпець CREATED. Образ, створений до останнього коміту, не було перебудовано.
Якщо ви ще формуєте файл, проти якого виконуються ці перевірки, у матеріалі основи Compose-файлу на VPS описано пов’язані ключі, а в матеріалі шпаргалка команд Compose наведено решту підкоманд.
FAQ
Чи можна використовувати build і image в одному сервісі?
Так, це стандартна конфігурація для проєкту, який ви збираєте самостійно. Compose збирає образ із секції build: і додає йому тег зі значенням image:. Саме цей тег docker compose push надсилає до registry, а інша машина використовує для завантаження образу. Без ключа image: Compose все одно виконує збірку, але називає образ за іменами проєкту та сервісу й попереджає, що відсутній атрибут не дає надсилати образ до registry.
Чому docker compose up не враховує зміни в моєму Dockerfile?
Тому що up лише перевіряє, чи існує образ із таким іменем. Якщо образ існує, Compose запускає його й не порівнює з Dockerfile або файлами вихідного коду. Виконайте docker compose up -d --build або виконайте docker compose build web, а потім docker compose up --no-deps -d web, щоб замінити один сервіс. Якщо встановити pull_policy: build для сервісу, кожен up виконуватиме повторну збірку. Це зручно на машині для розробки.
У чому різниця між --build і --force-recreate?
--build повторно збирає образ, а потім створює заново контейнери, образи яких змінилися. --force-recreate повторно створює контейнери з наявного в них образу, тому ця команда не може врахувати зміни коду. Якщо зміни внесено у вихідний код або Dockerfile, потрібен прапорець --build. --force-recreate призначений для скидання самого контейнера, наприклад для очищення його доступного для запису шару зі збереженням того самого образу.
Чи слід збирати Docker-образи на VPS або в іншому місці?
Виконуйте збірку в іншому місці й завантажуйте тег після того, як сервер почне обслуговувати трафік. Збірка конкурує із застосунком за пам’ять, а невеликий VPS вирішує цю проблему, коли ядро завершує найбільший процес. Це може бути процес збірки або база даних. Збірки також залишають на диску кеш, який ніхто автоматично не очищає. Збірка на сервері підходить для невеликого проєкту без користувачів. Подальша міграція не потребуватиме значних зусиль, якщо залишити секцію build: у Compose-файлі лише для розробки.
Як не допустити переповнення диска кешем збірки Docker?
Виконайте docker system df, щоб переглянути, скільки місця займають образи та кеш збірки. docker builder prune видаляє кешовані шари, а docker image prune видаляє невикористовувані образи, які залишилися після попередніх збірок. Додавання -a до будь-якої з цих команд виконує агресивніше очищення й змушує наступну збірку почати без кешу. Не плануйте виконання docker system prune -af --volumes на сервері, оскільки --volumes видаляє будь-який том, який наразі не використовується жодним контейнером. Стек, зупинений для технічного обслуговування, зберігає базу даних саме в такому томі.