以下の3コマンド、これだけでプロジェクトに必要な開発環境ができあがる。そんな環境を Docker で作ってみませんか?
$ git clone https://github.com/dataich/sample-docker.git $ cd sample-docker $ docker-compose up
こんにちは、Typetalkチームのエンジニアの吉田です!
新しいエンジニアがチームに入ってきてまずやることは、DBやWebサーバなどの開発環境を構築することでしょう(9月に新しい人がチームに入ります、やったね!)。
Typetalkチームでも以前はREADMEに記述された手順に従って、開発者が個別に構築していました。その際READMEに書かれたミドルウェアのバージョンが古かったり、実は手順そのものが変わっていたりすることもあり、ハマることもありました。また、複数のプロジェクトで違うバージョンのミドルウェアを動かすのが面倒なこともありました。
しかし、現在はDockerを導入し、非常に簡単に開発環境を作れるようになっています。今さらDockerの解説?とはいえDockerを初めて使う場合は、用語やDockerの仕組みなど覚えることが多く大変だった印象があります。 そこで本稿では「プロジェクトの開発環境を1ファイルで定義し、3コマンドで立ち上げる」という1点の目標のみを達成すべく、極力簡単でシンプルな Docker チュートリアル を目指しつつ、その手法を紹介します。
本稿について
対象となる読者
- Dockerをなんとなくしってるけど、そんなに使ってない人
- Dockerで開発環境作りたいけど覚えること多そうと敬遠してる人
目標とすること
- プロジェクトの開発環境をDockerで作成できるようになること
- それを他の人に渡して、コマンド1発で同一の環境が作れるようになること
- Dockerで開発環境を作るための最低限の知識を得る
目標としないこと
- プロダクションなどのサービス運用環境でのDocker
- 本記事に必要のないこと
Docker for Mac/Windowsのインストール
Docker チュートリアル を始める前に、Dockerをインストールしましょう。方法はいくつかありますが、ここでは現時点で一番簡単に使えるDocker for Mac/Windowsを下記ドキュメントに従いインストールしてください。
Docker チュートリアル
それでは Docker チュートリアル を開始していきます。DB(Postgresql)からSQLで現在日時を取得して表示する簡単なNode.jsアプリケーションがあるとします。このプロジェクトに必要な以下の環境をDockerを使用して構築します。なおチュートリアルとしてはNode.jsアプリケーションを採用していますが、本稿を読んで基本を押さえれば他の言語・フレームワークでも応用が可能です。
- Appサーバ Node.js v8.4.0
- DBサーバ Postgresql v9.6.4
Docker for Macを起動する
Docker for Windowsでも動くはずですが、ここからはDocker for Macを前提して紹介していきます。まずは、インストールしたDocker for Macを起動してみましょう。以下の図のようにDockerホストが立ち上がります。このDockerホストの中で後述するDockerコンテナが複数動作します。もちろんまだ何の定義もしていないので何もありません。

サンプルプロジェクトをクローンする
次は以下のコマンドでサンプルプロジェクトをcloneしてください。
$ git clone https://github.com/dataich/sample-docker.git $ cd sample-docker
以下のようなディレクトリ構成となっており、Dockerに関する定義は何も入っていない、単なるNode.jsアプリケーションがあるだけとなっています。
$ tree
.
├── app # Node.jsアプリケーション
├── index.js
└── package.json
app/index.jsはHTTPサーバを起動し、「Hello, World」を返すだけの単純なアプリケーションです。
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
const port = 3000;
server.listen(port, '0.0.0.0', () => {
console.log(`Server running at http://localhost:${port}/`);
});
{
"name": "sample-docker",
"private": true,
"scripts": {
"start": "node-dev index.js"
},
"dependencies": {
"pg": "^7.2.0"
},
"devDependencies": {
"node-dev": "^3.1.3"
}
}
DockerコンテナとDockerイメージ
これからこの小さなアプリケーションを動作させるための、Node.jsサーバをDockerを使って構築していきます。 Dockerコンテナとは前述したDockerホスト内で実行される実際の仮想環境だと思ってください。DockerイメージとはDockerコンテナを実行するためのベースとなるファイルシステムです。必要となるミドルウェアがファイルシステムにインストールされたDockerイメージを指定して、Dockerコンテナを起動することで、その機能を持った仮想環境を実行できます。

App(Node.js)サーバを作る
それでは実際に仮想環境、Dockerコンテナを定義していきます。プロジェクト直下にdocker-compose.ymlという名前でファイルを作成し、以下の内容を記述してください。
version: '3.3' # 1
services: # 2
sample.app: # 3
container_name: sample.app # 4
image: node:8.4.0 # 5
volumes: # 6
- ./app:/root/app
working_dir: /root/app # 7
command: bash -c 'npm install && npm start' # 8
ports: # 9
- "3000:3000"
早速ですが、以下のコマンドを実行してみましょう。
$ docker-compose up sample.app Creating sample.app ... Creating sample.app ... done ... sample.app | Server running at http://localhost:3000/
記述に問題がなければ、最終行に表示されている http://localhost:3000/ にアクセスすると「Hello, World!」と表示されるはずです。この段階では以下の図のように、Dockerホスト内でDockerコンテナが1つ実行されています。

Docker Composeというツールが提供するこのコマンドdocker-compose upはdocker-compose.ymlに書かれた定義を元に、複数のDockerコンテナの作成・実行を試みます。それではdocker-compose.ymlの中身を見ていきましょう。
#1 version
docker-compose.ymlのシンタックスバージョンです。現時点では3.3が最新となります。
#2 services
Docker Composeはこの配下にある設定を1サービス(Dockerコンテナ)として認識します。 この配下にサービス(Node.jsサーバ、Postgresqlサーバなど)を複数記述していきます。
#3 sample.app
ここではDockerコンテナsample.appを作成・実行します。配下にこのDockerコンテナの設定を記述していきます。
#4 container_name
Dockerコンテナの名称を記述します。記述しない場合はサービス名などから自動的に作られるのですが、sampledocker_sample.app_1などのように若干長いものになってしまいます。好みではありますが、ここではサービス名と同じようにsample.appという名前を設定しています。
#5 image
このサービスで使うDockerコンテナを作成する際のベースとなるイメージを[イメージ名]:[タグ名]で記述します。イメージはDocker Hubなどで検索します。著名なプロダクトの場合、そのほとんどが公式なイメージを提供してくれています。例えばnodeで検索すると、nodejs.orgが提供するDockerイメージが見つかりますので、そのイメージ名と利用できるタグ名(ここではバージョンと思ってください)を利用します。ここではイメージ名にnode、タグ名に8.4.0を指定しています。

#6 volumes
[Mac側パス]:[Dockerコンテナ側パス]で、Dockerを実行しているマシン(Mac)のディレクトリをDockerコンテナにボリュームとしてマウントすることができます。ここではMac側の./appディレクトリをDockerコンテナ側の/root/appにマウントしています。こうすることで./appディレクトリが同期され、Dockerコンテナ内からソースコードを参照することができます。
#7 working_dir #8 command
Dockerコンテナが実行される際、commandに記述しているコマンドがDockerコンテナ内で実行されます。実行時のカレントディレクトリとしてはworking_dirが使用されます。ここではworking_dirにvolumesでマウントした/root/appが記述されているので、ここでbash -c 'npm install && npm start'が実行されることになります。これでNode.jsアプリケーションが起動します。
#9 ports
npm startによってDockerコンテナ内のNode.jsアプリケーションが3000番でリッスンするようになりました。ただしこのままではMacからアクセスすることができません。[Mac側のポート]:[Dockerコンテナのポート]とすることで、Dockerコンテナ内のポートをMac側に晒し、localhostでアクセスできるようにします。ここでは3000:3000とし、localhost:3000 でアクセスできるようにしています。
DB(Postgresql)サーバを作る
同じくDBサーバもDockerコンテナを使って起動します。先程とやることは大きく変わりません。以下の内容を記述してください。
version: '3.3'
### ここから
volumes: # 1
sample.db.volume:
### ここまで
services:
sample.app:
...
### ここから
sample.db:
container_name: sample.db
image: postgres:9.6.4
volumes: # 2
- sample.db.volume:/var/lib/postgresql/data
environment: # 3
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=sample
### ここまで
ではこれでDockerコンテナを起動しましょう。先程起動してそのままの場合は、一旦Ctrl+Cでプロセスを終了します。その後docker-compose upを再度実行してください。
$ docker-compose up Creating sample.db ... Starting sample.app ... ... sample.app | Server running at http://localhost:3000/ ... sample.db | LOG: database system is ready to accept connections
問題がなければ、上記のようなログが出ます。Creating sample.dbで新たにDockerコンテナが作成されています。また、sample.appは既に作成済みだったので、Starting sample.app ...のように実行だけが行われています。これで2つめのDockerコンテナと後述するボリュームが起動し、以下の図の状態になります。

それではDockerコンテナsample.appの定義では出てこなかった設定について解説します。
#1 #2 volumes
Dockerコンテナにあるデータは永続化されません。例えばPostgresqlのバージョンを9.6.5に上げようと、imageのタグ指定を変えた場合、Dockerコンテナが再作成されるためDockerコンテナ上にあったデータは失われてしまいます。そのためここではデータ永続化のための設定を行っています。 docker-compose.ymlのトップレベルにあるvolumesは、Dockerホスト上にボリュームを作成します。そしてsample.db配下のvolumesではsample.db.volume:/var/lib/postgresql/dataとすることで、そのボリュームを/var/lib/postgresql/dataへマウントしています。こうすることで、sample.dbDockerコンテナが再作成された場合でも、sample.db.volumeボリュームは削除されず、新しいDockerコンテナにマウントされるため、データを引き続き利用することができます。
#3 environment
Dockerコンテナを起動する際に渡す環境変数を記述します。多くのDockerイメージが環境変数を使って各種設定ができるようになっています。postgresのイメージの場合は、DBの認証情報やDB初期化時に作成するDB名を設定することができます。
AppサーバとDBサーバで通信する
最後は、Node.jsアプリケーションからDBに接続し、現在日時をselectしてみます。Dockerコンテナ間でどう通信するのかを見ていきます。app/index.jsを以下のように書き換えてください。
const http = require('http');
const {Client} = require('pg');
const server = http.createServer(async (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
const client = new Client({
user: 'postgres',
password: 'postgres',
host: 'sample.db', #1
database: 'sample'
});
try {
await client.connect();
const {rows} = await client.query('select now()');
res.end(rows[0]['now'].toString());
} catch (e) {
res.end(e.stack);
}
});
const port = 3000;
server.listen(port, '0.0.0.0', () => {
console.log(`Server running at http://localhost:${port}/`);
});
DBに接続し、現在日時をselectした結果を表示する単純なプログラムです。ブラウザをリロードしてみると、現在日時が表示されるはずです。Docker Composeで複数のDockerコンテナを起動した場合、Dockerコンテナ同士はそのコンテナ名をホスト名としてお互いを知っています。そのためここでは#1の部分でDBのホスト名としてsample.dbとすることでDBに接続することができます。
最後に
これで一通りの開発環境が構築できました。最後にもう一度docker-compose.ymlを見てみましょう。
version: '3.3'
volumes:
sample.db.volume:
services:
sample.app:
container_name: sample.app
image: node:8.4.0
volumes:
- ./app:/root/app
working_dir: /root/app
command: bash -c 'npm install && npm start'
ports:
- "3000:3000"
sample.db:
container_name: sample.db
image: postgres:9.6.4
volumes:
- sample.db.volume:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=sample
たったの22行!で開発環境を構築することができました。この1ファイルが入ったリポジトリをgit cloneし、docker-compose upするだけですぐにアプリケーションを実行できるようになりました。一度やってみれば開発環境としてのDockerの利用はとても簡単です。みなさんも今すぐ自分のプロジェクトに導入してみましょう!(また、developブランチには完成形のプロジェクトが入っていますので、手っ取り早く試してみたいかたはそちらからどうぞ。)
ヌーラボではこのように新しいメンバーを安心して迎え入れられるように準備をしています。さくっと開発環境を構築して、今すぐサービス開発に取り掛かりたい、そんな方を募集しています!