Гид по технологиям

Контейнеризация Node.js и PostgreSQL с Docker

7 min read DevOps Обновлено 24 Dec 2025
Контейнеризация Node.js и PostgreSQL с Docker
Контейнеризация Node.js и PostgreSQL с Docker

Логотип Docker и название на тёмном фоне.

Процесс развёртывания и запуска приложений в разных средах может быть сложным: нужно учесть переменные окружения, зависимости, версии пакетов и параметры базы данных. Docker решает большинство этих проблем — контейнер содержит код и все его зависимости, поэтому приложение запускается одинаково в локальной, тестовой и продуктивной средах.

Что такое Docker?

Docker — платформа для разработки и доставки приложений, которая упаковывает приложение и все его зависимости в переносимый образ (image). Образ запускается как контейнер — изолированный процесс с собственным файловым пространством и сетью.

Кратко: образ — неизменяемый артефакт, контейнер — запущенный экземпляр образа.

Грузовые корабли в порту

Прежде чем начать, установите Docker Desktop или Docker Engine на вашу машину. Проверьте системные требования и инструкции по установке в официальной документации Docker для вашей ОС.

Пример: создаём Node.js REST API

В этом руководстве мы создаём простой Node.js сервер с тремя маршрутами и подключением к PostgreSQL через knex и pg. Исходный код можно хранить в репозитории GitHub.

Установите зависимости проекта:

npm install morgan pg knex

Пояснения по пакетам:

  • pg — драйвер для подключения к PostgreSQL.
  • knex — query builder для SQL (удобная обёртка для написания запросов).
  • morgan — middleware для логирования HTTP-запросов в консоли.

Создайте файл index.js и добавьте следующий код сервера:

const express = require("express")
const morgan = require("morgan")
const app = express()
const db = require('./db')
const PORT = process.env.PORT || 5000

app.use(morgan('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => res.send('Hello World!'))

app.get('/users', async (req, res) => {
  const users = await db.select().from('users')
  res.json(users)
})

app.post('/users', async (req, res) => {
  const user = await db('users').insert({ name: req.body.name }).returning('*')
  res.json(user)
})

app.listen(PORT, () => console.log(`Server up at PORT:${PORT}`))

Настройка подключения к базе данных

Создайте файл db.js в корне проекта со следующим содержимым. Он настраивает knex для соединения с PostgreSQL, где хост будет именем сервиса в docker-compose (db).

const knex = require('knex')
module.exports = knex({
  client: 'postgres',
  connection: {
    host: 'db',
    user: 'testUser',
    password: 'mypassword123',
    database: 'testUser'
  }
})

Важно: в контейнеризированной сети Docker Compose сервисы видят друг друга по имени сервиса (здесь — db). В локальной среде вы, возможно, будете использовать localhost или другой хост.

Скрипты миграции и сидирования

Создайте папку scripts и добавьте migrate.js и seed.js, чтобы автоматически создать таблицу users и добавить тестовые записи.

migrate.js:

const db = require('../db');
(async () => {
  try {
    await db.schema.dropTableIfExists('users')
    await db.schema.withSchema('public').createTable('users', (table) => {
      table.increments()
      table.string('name')
    })
    console.log('Created users table!')
    process.exit(0)
  } catch (err) {
    console.log(err)
    process.exit(1)
  }
})()

seed.js:

const db = require('../db');
(async () => {
  try {
    await db('users').insert({ name: 'Test User1' })
    await db('users').insert({ name: 'Test User2' })
    console.log('Added dummy users!')
    process.exit(0)
  } catch (err) {
    console.log(err)
    process.exit(1)
  }
})()

Добавьте команды в package.json, чтобы запускать миграции и сиды через npm run:

  "scripts": {
    "start": "node index.js",
    "migrate": "node scripts/migrate.js",
    "seed": "node scripts/seed.js"
  }

Примечание: если структура папок иная, проверьте относительные пути require в скриптах.

Dockerfile для Node.js приложения

Создайте файл Dockerfile в корне проекта. Он определяет образ для приложения.

FROM node:16.3.0-alpine3.13
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8000
CMD [ "node", "index.js" ]

Разбор инструкций:

  • FROM — базовый образ Node.js (alpine — компактная версия).
  • WORKDIR — рабочая директория внутри контейнера.
  • COPY package*.json ./ — копируем package.json и package-lock.json (если есть) для установки зависимостей до копирования всего кода (лучше для кэширования слоёв).
  • RUN npm install — устанавливаем зависимости в образе.
  • COPY . . — копируем весь код в контейнер.
  • EXPOSE 8000 — декларативно указываем порт приложения внутри контейнера (важно: порт внутри контейнера должен соответствовать коду; в index.js мы используем порт 5000 по умолчанию, синхронизируйте значение).
  • CMD — команда при запуске контейнера.

Важно: в примере EXPOSE указан 8000, а в приложении порт — 5000. Проверьте соответствие. В production лучше задать PORT через переменные окружения.

Docker Compose: связываем приложение и базу данных

Создайте docker-compose.yml в корне проекта. Он описывает сервисы: server и db.

version: '3.9'

services:
  server:
    build: .
    ports:
      - '5000:5000'
    depends_on:
      - db
    environment:
      - PORT=5000
      - NODE_ENV=development
  db:
    image: 'postgres'
    ports:
      - '4321:5432'
    environment:
      POSTGRES_PASSWORD: 'mypassword123'
      POSTGRES_USER: 'testUser'
      POSTGRES_DB: 'testUser'
    volumes:
      - data:/var/lib/postgresql/data

volumes:
  data:

Ключевые моменты:

  • ports: ‘5000:5000’ — прокидывает порт приложения наружу (хост:контейнер).
  • depends_on: db — гарантирует порядок запуска контейнеров, но не гарантирует готовность сервиса. Следует ожидать готовности БД (см. раздел Troubleshooting).
  • volumes: data — сохраняет данные PostgreSQL между перезапусками контейнеров.

Пример для разработки: bind-mount и файлы с окружением

Для локальной разработки удобно использовать bind-mount, чтобы код на хосте отражался в контейнере без пересборки образа:

  server:
    build: .
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - '5000:5000'
    environment:
      - NODE_ENV=development

Добавьте .env файл и подключите его через env_file: .env, если хотите хранить секреты вне repo (но не коммитьте .env в публичные репозитории).

Сборка и запуск контейнеров

Соберите и запустите сервисы в фоне:

docker-compose up -d --build

Проверьте логи сервиса:

docker-compose logs -f server

Два контейнера Docker, созданные и запущенные

Тестирование REST API и выполнение миграций

Выполните миграцию внутри контейнера сервера:

docker exec -it  npm run migrate

В оригинальном примере команда выглядела так:

docker exec docker_node-server-1 npm run migrate

Где docker_node-server-1 — автоматически сгенерированное имя контейнера; используйте docker ps чтобы узнать имя или ID.

Ожидаемый результат — в логах появится сообщение о создании таблицы и в консоли вы увидите вывод скрипта.

Вывод сервера в консоли после выполнения скрипта migrate и создания таблицы

Публикация образа на Docker Hub

  1. Зарегистрируйтесь в Docker Hub и создайте репозиторий (настройте Public или Private).
  2. Войдите в Docker через терминал:
docker login
  1. Пометьте (tag) образ именем вашего репозитория:
docker tag  /:
  1. Отправьте образ в Docker Hub:
docker push /:

После этого образ станет доступен для скачивания и использования на других хостах.

Лучшие практики и рекомендации

  • Секреты и пароли храните в менеджере секретов или в переменных окружения CI — не в Dockerfile и не в репозитории.
  • Используйте multistage сборку, чтобы уменьшить размер образа и убрать dev зависимости.
  • Пиньте версии базовых образов и зависимостей (например, node:16.3.0-alpine3.13), чтобы сборки были воспроизводимы.
  • Обновляйте зависимости и базовые образы по расписанию безопасности.
  • В production используйте не root-пользователя внутри контейнера.
  • Настройте healthcheck в docker-compose для автоматической проверки готовности сервисов.

Пример healthcheck для db:

  db:
    image: postgres
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testUser"]
      interval: 10s
      timeout: 5s
      retries: 5

Безопасность и жёсткая настройка

  • Минимизируйте набор установленных пакетов в образе.
  • Запускайте контейнеры с ограниченными правами (user, capabilities).
  • Ограничивайте ресурсы контейнера (cpus, memory) в docker-compose для предотвращения DoS.
  • Шифруйте трафик между сервисами (TLS) в продуктиве.
  • Регулярно сканируйте образы на уязвимости (clair, trivy).

Отладка и типичные проблемы

  1. Проблема: сервер не может подключиться к БД сразу после запуска — причина: БД ещё не готова. Решение: добавьте healthcheck и механизм повторных попыток подключения в приложении.
  2. Проблема: несоответствие портов (EXPOSE 8000, но контейнер слушает 5000). Решение: синхронизируйте PORT (в коде и в Dockerfile/docker-compose).
  3. Проблема: данные не сохраняются после удаления контейнера. Решение: используйте volumes в docker-compose (named volumes или bind-mount на хост).
  4. Проблема: «permission denied» при записи в volume. Решение: проверьте владельца и права на папку на хосте или используйте user в контейнере.

CI/CD: подсказки для автоматической доставки

  • В CI собирайте образ и пушьте в приватный реестр (Docker Hub, GitHub Container Registry, GitLab Registry).
  • В пайплайне выполняйте тесты, миграции и проверку на уязвимости до деплоя.
  • Для деплоя используйте target-окружения и стратегии (Blue/Green, Rolling) — зависит от платформы (Kubernetes, ECS, Docker Swarm).

Шаблон чеклистов по ролям

Разработчик:

  • Локально протестировал API
  • Обновил package.json и package-lock.json
  • Обновил миграции/сиды

DevOps:

  • Написал Dockerfile и docker-compose.yml
  • Настроил volumes и резервное копирование БД
  • Настроил CI для сборки и пуша образа

QA:

  • Проверил основные API-эндпоинты
  • Проверил поведение при падении БД и восстановлении
  • Проверил миграции на чистой базе

Критерии приёмки

  • Приложение запускается из docker-compose и отвечает на / и /users.
  • Скрипты миграции и сидирования выполняются без ошибок.
  • Данные сохраняются между перезапусками (volume работает).
  • Образ опубликован в реестре и может быть скачан и запущен на другом хосте.

Маленькая шпаргалка команд

  • Сборка и запуск: docker-compose up -d –build
  • Остановка: docker-compose down
  • Просмотр логов: docker-compose logs -f server
  • Список контейнеров: docker ps
  • Войти внутрь контейнера: docker exec -it /bin/sh
  • Тег и пуш образа: docker tag / docker push

Решение: когда подходит, когда нет

Подходит:

  • Быстрая упаковка приложения и его зависимостей.
  • Локальная разработка, тестирование интеграций, развертывание в облаке.

Не подходит или требует осторожности:

  • Задачи, где нужна тонкая виртуализация ядра или специфичные права хоста.
  • Сценарии с высокими требованиями к производительности без оптимизации образов.

Заключение

Docker упрощает развёртывание приложений, сводя к минимум различия между средами. В этом руководстве показано, как контейнеризовать простое Node.js приложение с PostgreSQL, настроить миграции и сиды, протестировать и опубликовать образ. Также приведены рекомендации по безопасности, отладке и CI/CD.

Важное: всегда проверяйте соответствие портов и корректно храните секреты.

Краткое резюме:

  • Упакуйте приложение и зависимости в Dockerfile.
  • Опишите сервисы в docker-compose.yml и используйте volumes для сохранения данных.
  • Добавьте healthchecks и retry-механизмы для надёжности.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Перенаправление вывода в bash: >, >> и tee
Bash

Перенаправление вывода в bash: >, >> и tee

Опросы в WhatsApp: как создать и отслеживать
WhatsApp

Опросы в WhatsApp: как создать и отслеживать

Звуки оповещений в Ubuntu — изменить и добавить
Ubuntu

Звуки оповещений в Ubuntu — изменить и добавить

Купить набор Stand With Ukraine — Humble Bundle
Игры и благотворительность

Купить набор Stand With Ukraine — Humble Bundle

Easy Diffusion: локальная генерация AI-искусства
Генеративное искусство

Easy Diffusion: локальная генерация AI-искусства

Как продать старые гаджеты за деньги
Технологии

Как продать старые гаджеты за деньги