Я написал приложение node, используя синтаксис ES6, то есть import express from 'express';. После этого, чтобы упростить жизнь, я хотел использовать Docker для размещения приложения. Поэтому я погуглил свой запрос, как и вы, и нашел кучу руководств о том, как создать Dockerfile и docker-compose.yml.

Мила, давайте!

TL;DR

Вы можете найти весь код на GitHub @ https://github.com/Tabofa/demo-app

После того, как я написал все инструкции, я запустил его, создал образ, и docker-compose запустил контейнер. К моему большому разочарованию, я получил ошибки. Он не признавал импорт и громко жаловался на это.

После того, что казалось вечностью гугления, я наконец обнаружил, что есть две вещи, о которых я не знал.

  1. Мне пришлось добавить { "type": "module" } как поле верхнего уровня в package.json.
  2. И мне нужен был Node 13 или новее.

Итак, имея эту информацию под рукой, я наконец смог создать свой новый образ.

Если вы хотите продолжить, я создал очень простое приложение узла для тестирования, если нет, прокрутите вниз до The Dockerfile.

mkdir demo-app
cd demo-app
npm init
npm install express
npm install pm2 # I want pm2 to host my app in the container later
touch Dockerfile
touch docker-compose.yml
mkdir src
cd src
touch index.js

Если вы будете следовать, у вас должна быть файловая структура, подобная следующей

demo-app
| src
  | index.js
| Dockerfile
| docker-compose.yml
| package.json
| package-lock.json

Первый трюк, чтобы заставить node читать мое приложение как es6, а не CommonJs, заключался в том, что мне пришлось немного изменить файл package.json. Добавление поля type

{
    "name": "demo-app",
    "version": "1.0.0",
    "description": "",
    "type": "module",
    "main": "src/index.js",
    "script": {
        ...
    }
}

В ./src/index.js я написал простое приложение, которое будет предоставлять порт и конечную точку, которая при вызове будет отвечать «hello world». Используя экспресс, который мы установили в начале здесь.

# src/index.js
import express from 'express'
const app = express()
const PORT = 3000
app.get('/', (req, res) => {
    res.send('hello world')
})
app.listen(PORT, () => {
    console.log(`listening on port ${PORT}`)
})

Как только я попробовал это с node ./src/index.js, я мог приступить к созданию Dockerfile.

Докерфайл

# Dockerfile
FROM node:14-alpine

Это был еще один трюк: используя базовый образ с узлом 13 или более поздней версии, я решил использовать 14, так как это была последняя основная версия на момент написания.

Я продолжил добавлять обычные команды в Dockerfile

# Dockerfile
# Base image to use
FROM node:14-alpine 
#Create the directory we're going to use
RUN mkdir -p /usr/src/app
# Set it as work directory
WORKDIR /usr/src/app
# Copy all the code to our work directory
COPY . .
# Install all our dependencies
sRUN npm install
# Expose the port we want to communicate on
EXPOSE 3000
# Start the container
CMD [ "npx", "pm2-runtime", "start", "src/index.js" ]

В последней строке Dockerfile я указываю команду для выполнения после создания образа. в этом случае мы используем pm2-runtime согласно документации для pm2, это способ сделать это в контейнере докера, иначе докер может подумать, что процесс завершен, и закрыть его, мы этого не хотим.

Я также использую npx здесь, поэтому мы используем pm2 из наших node_modules, что дает нам роскошь не указывать это как глобальную установку в Dockerfile, мы могли бы, если бы мы хотели запустить RUN npm install -g pm2 после нашего оператора RUN npm install, установив его глобально . Но зачем заморачиваться.

Файл docker-compose.yml

Перейдем к следующей части, нашему docker-compose.yml, он не нужен, но, на мой взгляд, упрощает жизнь.

# docker-compose.yml
version: '3'
services:
  demo-app:
    build:
      context: .
    ports:
    - "3000:3000"

Теперь вы сможете запустить docker-compose up в окне терминала, и если вы откроете браузер и перейдете к http://localhost:3000, вы увидите на экране слова «hello world».

Удачного кодирования!