はじめに
1年前に フロントエンド開発のためのセキュリティ入門 知らなかったでは済まされない脆弱性対策の必須知識 を買ったのだが、それを読んでおらず、最近になって読み始めた。
この中で、Node.js
+ Express
でハンズオンの環境を構築しているので、書籍の通り構築してみる。
※ どこでも環境を作れるようにDocker
で構築を試してみる。
環境
MacOS sonoma 14.2.1
Docker Desktop 4.28.0 (139021)
Server: Docker Desktop 4.28.0 (139021)
Engine:
Version: 25.0.3
API version: 1.44 (minimum version 1.24)
Go version: go1.21.6
Git commit: f417435
Built: Tue Feb 6 21:14:25 2024
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.28
GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
runc:
Version: 1.1.12
GitCommit: v1.1.12-0-g51d5e94
docker-init:
Version: 0.19.0
GitCommit: de40ad0
docker initを使って構築する
実は docker initを試してみるでも、docker init
を使って、Node.js
+Express
の環境を構築していた。
https://www.publickey1.jp/blog/24/dockerdocker_initdockerfilecompose.html によると、docker init
は2024/01/25に正式リリースされたので、以前の記事の時点では実験的リリースだったようだ。
成果物に変わりがありそうなので、再度docker init
で構築を試してみる。
ワークディレクトリの作成
今回は、~/workspace/nodejs-express-handson
というパスでディレクトリを作成し、そこで作業をする。
mkdir -p ~/workspace/nodejs-express-handson && cd $_
docker init の実行
docker init
での構築を実施してみる。
docker init
Welcome to the Docker Init CLI!
This utility will walk you through creating the following files with sensible defaults for your project:
- .dockerignore
- Dockerfile
- compose.yaml
- README.Docker.md
Let's get started!
プロジェクトにどのアプリケーションプラットフォームを使うのかを聞かれるので、Node
を選択する。
? What application platform does your project use? [Use arrows to move, type to filter]
Go - suitable for a Go server application
Python - suitable for a Python server application
> Node - suitable for a Node server application
Rust - suitable for a Rust server application
ASP.NET Core - suitable for an ASP.NET Core application
PHP with Apache - suitable for a PHP web application
Java - suitable for a Java application that uses Maven and packages as an uber jar
Other - general purpose starting point for containerizing your application
Don't see something you need? Let us know!
Quit
Node
のバージョンを聞かれる。
https://nodejs.org/en/about/previous-releasesを見ると、LTSかつ最新なものは20.11.1
となるので、これにする。
? What version of Node do you want to use? (8.9.4) 20.11.1
パッケージマネージャは何を使うと聞かれる。
pnpm
使ってみたいしこれにしよう。
? Which package manager do you want to use? [Use arrows to move, type to filter]
npm - (detected)
yarn
> pnpm
pnpm
のバージョン何にすると聞かれる。
https://github.com/pnpm/pnpm/releasesのGitHubのリポジトリのReleases
のページを見てみた。v8.15.4
が最新らしい。(v9.0.0もあるがアルファ版)
? What version of pnpm do you want to use? 8.15.4
どういうコマンドでアプリケーションが起動するか教えてと聞かれる。
前回は、node index.js
みたいなコマンドだったので、それにしておこう。
? What command do you want to use to start the app? [tab for suggestions] node index.js
ポートは何番を使うかを聞かれる。3000
番としておく。
? What port does your server listen on? 3000
できた
✔ Your Docker files are ready!
Take a moment to review them and tailor them to your application.
WARNING: The following files required to run your application were not found. Be sure to create them before running your application:
- package.json
- pnpm-lock.yaml
When you're ready, start your application by running: docker compose up --build
Your application will be available at http://localhost:3000
Consult README.Docker.md for more information about using the generated files.
成果物を確認していこう、このあたりができている。
.dockerignore Dockerfile README.Docker.md compose.yaml
環境の起動
docker compose up -d --build
failed to solve: failed to compute cache key: failed to calculate checksum of ref 100ebf9f-d869-4ab8-a483-2915b8123053::ejlz08rw16fqm6bbzjdwc1yl2: "/package.json": not found
package.json
がなくて怒られる。
package.json
やその他ファイルがある前提でDockerfile
が作成されるので初期構築が少し難しい。
そのため、修正を加えることにする。
docker init 成果物の修正とindex.jsの追加
Dockerfile
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
ARG NODE_VERSION=20.11.1
ARG PNPM_VERSION=8.15.4
FROM node:${NODE_VERSION}-alpine
# Use production node environment by default.
ENV NODE_ENV production
# Install pnpm.
RUN --mount=type=cache,target=/root/.npm \
npm install -g pnpm@${PNPM_VERSION}
WORKDIR /usr/src/app
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.local/share/pnpm/store to speed up subsequent builds.
# Leverage a bind mounts to package.json and pnpm-lock.yaml to avoid having to copy them into
# into this layer.
#RUN --mount=type=bind,source=package.json,target=package.json \
# --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
# --mount=type=cache,target=/root/.local/share/pnpm/store \
# pnpm install --prod --frozen-lockfile
# Run the application as a non-root user.
USER node
# Copy the rest of the source files into the image.
COPY . .
# Expose the port that the application listens on.
EXPOSE 3000
# Run the application.
CMD ["tail", "-f", "/dev/null"]
#CMD node index.js
としておく。
compose.yml
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker Compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/
# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
server:
build:
context: .
environment:
NODE_ENV: production
ports:
- 3000:3000
# The commented out section below is an example of how to define a PostgreSQL
# database that your application can use. `depends_on` tells Docker Compose to
# start the database before your application. The `db-data` volume persists the
# database data between container restarts. The `db-password` secret is used
# to set the database password. You must create `db/password.txt` and add
# a password of your choosing to it before running `docker-compose up`.
# depends_on:
# db:
# condition: service_healthy
# db:
# image: postgres
# restart: always
# user: postgres
# secrets:
# - db-password
# volumes:
# - db-data:/var/lib/postgresql/data
# environment:
# - POSTGRES_DB=example
# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
# expose:
# - 5432
# healthcheck:
# test: [ "CMD", "pg_isready" ]
# interval: 10s
# timeout: 5s
# retries: 5
# volumes:
# db-data:
# secrets:
# db-password:
# file: db/password.txt
こっちは特に変更を入れていない。
index.jsの作成
const http = require('http');
const express = require('express');
const app = express();
app.get("/", function(req, res){
return res.send("Hello World");
});
const server = http.createServer(app);
server.listen(3000);
修正後の作業
この状態で、docker compose up -d --build
とし、コンテナを起動。
エントリーポイントが CMD ["tail", "-f", "/dev/null"]
の部分となっているので停止せずに動いている状態となる。
起動したら、下記でコンテナ内に入る。
docker compose exec --user=root server sh
その後、pnpm init
を実行する。
pnpm init
/usr/src/app # pnpm init
Wrote to /usr/src/app/package.json
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
/usr/src/app # ls
README.Docker.md index.js package.json
その後、pnpm add express
でexpress
も入れておこう。
pnpm add express
/usr/src/app # pnpm install express
Packages: +64
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 64, reused 0, downloaded 64, added 64, done
dependencies:
+ express 4.18.3
devDependencies: skipped because NODE_ENV is set to production
Done in 2.6s
できたので、これをホスト側のPCに持ってくる。docker cp
を使う。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
986d5bc0a0f9 nodejs-express-handson-server "docker-entrypoint.s…" 9 minutes ago Up 9 minutes 0.0.0.0:3000->3000/tcp nodejs-express-handson-server-1
CONTAINER ID
が986d5bc0a0f9
なので、
docker cp 986d5bc0a0f9:/usr/src/app/package.json .
docker cp 986d5bc0a0f9:/usr/src/app/pnpm-lock.yaml .
で持ってこられる。
Dockerfileを元に戻す
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
ARG NODE_VERSION=20.11.1
ARG PNPM_VERSION=8.15.4
FROM node:${NODE_VERSION}-alpine
# Use production node environment by default.
ENV NODE_ENV production
# Install pnpm.
RUN --mount=type=cache,target=/root/.npm \
npm install -g pnpm@${PNPM_VERSION}
WORKDIR /usr/src/app
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.local/share/pnpm/store to speed up subsequent builds.
# Leverage a bind mounts to package.json and pnpm-lock.yaml to avoid having to copy them into
# into this layer.
-#RUN --mount=type=bind,source=package.json,target=package.json \
-# --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
-# --mount=type=cache,target=/root/.local/share/pnpm/store \
-# pnpm install --prod --frozen-lockfile
+RUN --mount=type=bind,source=package.json,target=package.json \
+ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
+ --mount=type=cache,target=/root/.local/share/pnpm/store \
+ pnpm install --prod --frozen-lockfile
# Run the application as a non-root user.
USER node
# Copy the rest of the source files into the image.
COPY . .
# Expose the port that the application listens on.
EXPOSE 3000
# Run the application.
-CMD ["tail", "-f", "/dev/null"]
+CMD node index.js
起動
docker compose up -d --build
ちゃんと起動した〜
今回作成したもの
下記に成果物を入れておいた。
https://github.com/katsuobushiFPGA/nodejs-express-with-docker-handson
おわりに
書籍のハンズオンをこれからやっていきたい。
そして、フロントエンド技術を深く理解していきたい。pnpm
とか使ったことなかったので良い機会になるかも。
あと、docker init
で生成した成果物は全部production
環境で動作するようにできているみたいだ。
開発環境であれば、ソースコードをマウントしたりした方が開発効率は良いので、そうできるように環境ごとのyml
を作ったり、うまいこと切り替える機構を使ってみるかな。
ということで、まだ改善の余地があり。
※現状だと、ソースコード直すたびにコンテナビルドが必要になる。