HostiServer
2026-09-16 11:18
Docker на VPS: встановлення і перший контейнер
📚 Серія «Docker на VPS», частина 1 з 5:
- Docker на VPS: встановлення і перший контейнер ← ви тут
- Docker Compose: кілька сервісів разом
- Docker у production
- Docker + CI/CD: автодеплой з GitHub Actions / GitLab CI
- Gitea / Forgejo у Docker: повний self-hosted стек
Docker на VPS: встановлення і перший контейнер
У попередній серії ми автоматизували сам процес доставки коду: push у гілку — і за кілька хвилин зміни вже на сервері без участі людини. Але схема «push → SSH deploy → перезапуск сервісу» лікує лише один симптом. Другий, не менш болючий, лишається на місці: залежності на сервері все одно доводиться встановлювати руками, і вони конфліктують між проєктами, які живуть на одному й тому самому VPS.
Класична ситуація: один проєкт написаний під Node.js 18 і залежить від конкретної мажорної версії пакетів, другий — новіший, вже на Node.js 20. Обидва мають деплоїтись на той самий сервер. Без Docker це або два системних Node.js, які конфліктують через nvm і змінні PATH, або компроміс, коли один із проєктів працює на версії, під яку він не тестувався. Те саме повторюється з PHP, Python, версіями бібліотек, системними пакетами — будь-яка спільна залежність на хості рано чи пізно стає точкою конфлікту.
Docker вирішує це на рівні архітектури, а не домовленості. Кожен застосунок отримує власне ізольоване середовище зі своїми залежностями, і це середовище описане в коді, а не в голові адміністратора. Той самий образ, який ви зібрали і перевірили локально, запускається на сервері бітово ідентичним. А відкат до попередньої версії — це не «згадати, які файли перезаписати», а один рядок: docker run зі старим тегом образу.
ℹ️ Пов'язана стаття: якщо CI/CD-пайплайн для деплою у вас ще не налаштований, варто почати з матеріалу «Що таке CI/CD: від ручного deploy до автоматизованих pipelines». Автоматизація процесу деплою і контейнеризація того, що деплоїться, — це дві частини однієї задачі: зробити інфраструктуру керованою кодом, а не набором ручних кроків.
2. Ключові концепції Docker
Перш ніж встановлювати щось на сервер, варто розібратися у п'яти термінах, без яких жодна команда Docker не матиме сенсу.
2.1 Image — незмінний шаблон
Image (образ) — це read-only шаблон, з якого запускається контейнер: файлова система, встановлені залежності, змінні середовища, команда запуску за замовчуванням. Образ ніколи не змінюється сам по собі — node:20-alpine, nginx:latest або власний my-app:1.0 залишаються тими самими байтами, доки ви не зберете новий образ явно.
2.2 Container — запущений екземпляр
Container — це процес, запущений з image, з власною файловою системою поверх шарів образу, власним мережевим простором і власним PID namespace. З одного image можна запустити скільки завгодно контейнерів одночасно, і кожен буде ізольованим від інших, навіть якщо вони походять з того самого образу.
2.3 Layer-система і кеш збірки
Кожна інструкція в Dockerfile створює окремий шар (layer), і шари кешуються. Якщо package.json не змінився з минулої збірки, шар з npm install береться з кешу без повторного виконання — це саме та властивість, яка робить повторні збірки в CI в рази швидшими за перші.
2.4 Реєстри образів
Docker Hub — публічний реєстр за замовчуванням: docker pull nginx без додаткових налаштувань завантажує офіційний образ звідти. Але це не єдиний варіант.
| Реєстр | Коли підходить | Особливість |
|---|---|---|
| Docker Hub | Публічні офіційні образи, невеликі приватні проєкти | Ліміти на анонімні pull-запити для неавторизованих клієнтів |
| GHCR (GitHub Container Registry) | Проєкти, які вже живуть на GitHub | Права доступу керуються через ту саму організацію GitHub |
| Self-hosted registry | Закритий периметр, вимоги комплаєнсу | Повний контроль, але адміністрування — на вас |
2.5 Docker Engine vs Docker Desktop
Docker Desktop — це графічна обгортка для локальної розробки на Mac і Windows, з власною віртуальною машиною під капотом. На VPS вона не потрібна і навіть не встановлюється: сервер отримує тільки Docker Engine — daemon (dockerd) і CLI, без жодного графічного шару. Саме Engine ми ставимо в наступному розділі.
3. Встановлення Docker на VPS
3.1 Чому не apt install docker.io
Найпростіший шлях виявляється пасткою. Пакет docker.io зі стандартного репозиторію Ubuntu чи Debian — це версія, зафіксована на момент випуску дистрибутива, тобто зазвичай 20.x у той час, коли актуальна лінійка вже 26.x і вище. Різниця — це не просто номер версії: у старих збірках відсутні виправлення безпеки і частина сучасних можливостей BuildKit.
3.2 Офіційний спосіб — репозиторій Docker Inc.
Найшвидший надійний варіант — офіційний інсталяційний скрипт:
curl -fsSL https://get.docker.com | sh
Скрипт сам визначає дистрибутив, додає офіційний репозиторій Docker і встановлює актуальну Community Edition разом з containerd і CLI-плагінами (Compose, Buildx). Для production-серверів, де скрипт з інтернету — не бажаний варіант з міркувань безпеки, той самий репозиторій можна додати вручну за офіційною документацією для конкретного дистрибутива.
3.3 Запуск без sudo
За замовчуванням доступ до Docker daemon має тільки root, і кожна команда потребує sudo. Щоб працювати від звичайного користувача:
sudo usermod -aG docker $USER
newgrp docker
Замість newgrp docker можна просто перелогінитись — зміна групи застосується при новому вході в систему.
⚠️ Про безпеку групи docker: членство в групі docker фактично еквівалентне правам root на хості, оскільки daemon працює з root-привілеями і будь-хто з групи може змонтувати кореневу файлову систему хоста всередину контейнера. Додавайте в цю групу тільки тих, кому дійсно потрібен прямий доступ до Docker на цьому сервері.
3.4 Автозапуск після перезавантаження
Офіційний скрипт встановлення вже вмикає автозапуск, але варто перевірити явно:
sudo systemctl enable docker
3.5 Перевірка встановлення
docker --version
docker info
docker run hello-world
Остання команда — найкращий швидкий тест: якщо Docker здатний завантажити крихітний образ і вивести привітання, значить daemon працює, права налаштовані правильно і мережа для pull-запитів доступна.
4. Перші команди: практика з nginx
Теорія стає зрозумілою тільки після кількох команд у терміналі. Пройдемось по життєвому циклу контейнера на прикладі nginx.
4.1 Завантаження образу
docker pull nginx:alpine
Тег вказано явно, а не latest — це принципово для відтворюваності: latest сьогодні і latest через місяць можуть бути різними образами, і сервер після випадкового docker pull без тега одного дня отримає несподівану мажорну версію.
4.2 Запуск контейнера
docker run -d -p 80:80 --name my-nginx nginx:alpine
| Флаг | Значення |
|---|---|
-d |
Detached — запуск у фоні, термінал одразу повертається |
-p 80:80 |
Порт хоста : порт контейнера |
--name |
Читабельне ім'я замість випадкового хеша |
4.3 Перегляд запущених контейнерів
docker ps
docker ps -a
docker ps показує лише запущені контейнери, -a додає і зупинені — корисно, щоб знайти контейнер, який завершився з помилкою одразу після старту.
4.4 Логи
docker logs my-nginx
docker logs -f my-nginx
-f (follow) працює так само, як tail -f: зручно тримати відкритим під час діагностики проблеми в реальному часі.
4.5 Shell всередині контейнера
docker exec -it my-nginx sh
Відкриває інтерактивну оболонку всередині вже запущеного контейнера — переглянути конфіг, перевірити мережу, подивитись файли, не зупиняючи сервіс.
4.6 Зупинка і видалення
docker stop my-nginx
docker rm my-nginx
stop надсилає сигнал завершення процесу всередині контейнера, rm видаляє сам контейнер разом з його файловою системою. Образ, з якого він був створений, при цьому лишається на диску.
4.7 Керування образами на диску
docker images
docker image prune
docker images показує список завантажених образів з розміром, image prune прибирає dangling-образи — ті, що лишились без тега після пересборки з тим самим ім'ям.
5. Перший Dockerfile: власний образ
5.1 .dockerignore — перший файл, а не Dockerfile
Перед тим як писати Dockerfile, варто створити .dockerignore: node_modules, .env, .git, *.log не повинні потрапляти всередину образу. Без цього файлу контекст збірки (усе, що передається daemon'у) роздувається, а секрети з .env можуть випадково опинитись у шарі образу назавжди.
5.2 Структура Dockerfile
Порядок інструкцій має значення — саме він визначає, що потрапить у кеш, а що буде перезбиратись щоразу:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
5.3 Чому package*.json копіюється окремо
Якщо скопіювати весь код одним COPY . . перед встановленням залежностей, будь-яка зміна в коді інвалідує кеш шару з npm ci, і залежності перевстановлюються заново при кожній збірці. Розділивши це на два кроки, шар npm ci береться з кешу доти, доки не зміняться самі package.json чи package-lock.json.
5.4 Alpine чи повний образ
| Базовий образ | Розмір | Коли обрати |
|---|---|---|
node:20-alpine |
~130 MB | Production за замовчуванням |
node:20 |
~1.1 GB | Потрібні системні бібліотеки, яких немає в Alpine (musl vs glibc) |
5.5 CMD чи ENTRYPOINT
| Інструкція | Поведінка |
|---|---|
CMD |
Аргументи за замовчуванням; повністю перевизначаються при docker run image команда |
ENTRYPOINT |
Фіксований виконуваний файл; аргументи з docker run додаються до нього, а не замінюють його |
5.6 Збірка образу
docker build -t my-app:1.0 .
Той самий образ можна позначити другим тегом без повторної збірки — наприклад, docker build -t my-app:latest . в тому самому контексті створить ще один тег на ті самі шари.
5.7 Запуск власного образу
docker run -d -p 3000:3000 --name my-app my-app:1.0
6. Volumes: дані поза контейнером
6.1 Чому дані зникають
Файлова система контейнера ефемерна за задумом: docker rm видаляє все, що було записано всередину під час роботи. Для stateless-застосунку це не проблема, але для бази даних чи будь-якого сервісу, що зберігає стан, це означає втрату даних при кожному оновленні контейнера.
6.2 Bind mount
docker run -v /var/log/myapp:/app/logs my-app:1.0
Директорія з хоста монтується прямо всередину контейнера, зміни видно з обох боків у реальному часі. Зручно для конфігів, які треба редагувати ззовні, і для логів, які потім читає окрема система моніторингу на хості.
6.3 Named volume
docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgres:16-alpine
Named volume керується самим Docker'ом і фізично живе в /var/lib/docker/volumes/. Для баз даних це зазвичай кращий вибір за bind mount: Docker сам керує шляхами і правами доступу, а бекап можна знімати через допоміжний контейнер з --volumes-from, не турбуючись про точну структуру директорій хоста.
| Параметр | Bind mount | Named volume |
|---|---|---|
| Розташування на хості | Ви вказуєте самі | Керує Docker |
| Типове застосування | Конфіги, логи, код у розробці | Дані баз даних, стан сервісів |
| Портативність | Прив'язана до структури хоста | Однакова команда на будь-якому сервері |
6.4 Керування volumes
docker volume ls
docker volume inspect pgdata
docker volume prune
prune видаляє volumes, які не використовуються жодним контейнером — корисно, але варто перевірити список перед виконанням: даних, видалених таким чином, не повернути.
7. Висновок
За цю статтю ми пройшли шлях від встановлення Docker Engine на чистому VPS до власного зібраного образу з даними, які переживають перезапуск контейнера. Цього вже достатньо, щоб запустити один застосунок ізольовано від решти системи і не боятись конфлікту версій Node.js чи Python з сусіднім проєктом.
Незручність, яка стає помітною одразу після перших кількох контейнерів: кожен сервіс — це окремий довгий рядок docker run з портами, змінними середовища і volumes, який легко переплутати чи забути відтворити після оновлення сервера. Коли застосунок складається не з одного контейнера, а з декількох — сам застосунок, база даних, кеш — керувати цим набором окремими командами стає складно і схильно до помилок.
Саме цю проблему вирішує Docker Compose: весь стек описується один раз у файлі docker-compose.yml і піднімається однією командою.
📚 Навігація серії:
Ви читаєте частину 1 з 5 «Docker на VPS: встановлення і перший контейнер».
Далі: Частина 2. Docker Compose: кілька сервісів разом →
🚀 VPS під Docker-навантаження
Контейнери додають накладні витрати на CPU і диск понад сам застосунок: образи, шари, логи, volumes для баз даних. Hostiserver дає ресурси, розраховані саме під це.
🖥️ Виділені Сервери
- Від $90/міс, повний контроль над залізом для важких контейнерних навантажень
- Без спільних ресурсів: сусідні орендарі не впливають на продуктивність ваших контейнерів
- 24/7 підтримка: допоможемо з налаштуванням Docker і мережі між сервісами
💻 Cloud (VPS) Хостинг
- Від $19.95/міс, KVM-ізоляція, виділені vCPU і RAM
- NVMe-диски: швидкі шари образів і volumes для баз даних
- Ідеально для першого Docker-хоста: встановили, підняли контейнер, перевірили на реальному проєкті
- Легко масштабувати: більше RAM під нові сервіси або окремий VPS під кожен проєкт
💬 Не впевнені, який варіант потрібен?
💬 Напишіть нам — допоможемо з вибором!
Часті запитання
- Скільки RAM потрібно для Docker на VPS?
Сам Docker Engine займає мало — до 100-200 MB на daemon і containerd. Основне споживання йде від самих контейнерів: невеликий Node.js-застосунок вкладається в 256-512 MB, база даних на кшталт PostgreSQL комфортно почувається від 1 GB. Для одного застосунку разом з базою даних розумний старт — VPS з 2 GB RAM, з запасом під пікові навантаження та кеш файлової системи.
- Чи можна на одному VPS тримати кілька проєктів з різними версіями Node.js?
Так, і це один з головних мотивів переходу на Docker. Кожен контейнер має власний ізольований runtime незалежно від того, що встановлено на хості. Один контейнер працює на
node:18-alpine, інший поруч — наnode:20-alpine, і вони не бачать середовища одне одного.
- У чому практична різниця між image і контейнером?
Image — це незмінний шаблон на диску, схожий на клас у програмуванні. Контейнер — запущений екземпляр цього шаблону, зі своїм процесом і власним шаром для запису поверх read-only шарів образу. Видалення контейнера не видаляє образ, з якого його створено.
- Контейнер одразу завершується після запуску — що перевірити?
Спочатку
docker ps -a, щоб побачити код завершення, потімdocker logs назва_контейнера— у переважній більшості випадків причина видна одразу в останніх рядках виводу: помилка застосунку при старті, відсутня змінна середовища, порт вже зайнятий. Контейнер завершується, коли завершується головний процес усередині нього, тому це майже завжди помилка самого застосунку, а не Docker.
- Docker чи звичайний systemd-сервіс для невеликого проєкту?
Якщо на сервері живе один застосунок і його залежності не конфліктують ні з чим, systemd-сервіс простіший і має менше рухомих частин. Docker починає окупати себе, щойно з'являється другий проєкт з іншими вимогами до середовища, потреба у відтворюваних збірках або плани перенести застосунок на інший сервер без ризику «на новому сервері щось інакше».
- Чи безпечно давати користувачу доступ до Docker без sudo?
Тут варто дивитись правді у вічі: членство в групі
dockerрівнозначне root-доступу до хоста, оскільки daemon працює з правами root і дозволяє змонтувати будь-яку директорію хоста всередину контейнера. Додавайте в цю групу лише тих, кому дійсно потрібне повне адміністрування Docker на конкретному сервері, а не всіх, кому просто треба запустити один контейнер.
- Named volume чи bind mount для бази даних?
Named volume — типовий вибір для даних бази: Docker сам керує шляхом зберігання, і команда однакова незалежно від сервера. Bind mount має сенс, коли потрібен прямий і передбачуваний доступ до файлів з хоста — наприклад, щоб окрема система бекапів на хості читала файли бази напряму, без проміжного контейнера.