1時間でさわって学ぶDocker
Dockerを触りながら学んでいく、初心者向けの記事です。
用語の解説等はほとんどせずに、とにかく触ってみることを目的としています。
- Phase 1: Dockerの基本
コマンドを試しながら基本操作を学びます。 - Phase 2: Dockerfileをもう少し書いてみる
Dockerfileを使って、簡単なWebアプリケーションを作成します。 - Phase 3: Docker Composeを使ってみる
Docker Composeを使って、複数のコンテナを一括管理します。
Phase 1: Dockerの基本
Dockerの基本的な操作を試してみます。
プロジェクトフォルダの作成
次のコマンドを実行して、プロジェクトフォルダを作成します。
mkdir docker-hands-on
cd docker-hands-on
最もシンプルなコンテナの作成と実行
以下のコマンドを実行してみましょう。
docker run -it alpine:latest
このコマンドは、Docker Hubからalpineイメージをダウンロードして実行します。
-it
オプションはインタラクティブモードを指定し、コンテナ内で対話的にコマンドを実行できます。
> echo "hello"
hello
> exit # コンテナの外に出る
Dockerfileの作成
自分自身のDockerイメージを作成するためにはDockerfileを使用します。
以下はとってもシンプルなDockerfileの例です。
touch Dockerfile
vim Dockerfile
作ったDokcerfileに以下の内容を記述します。
FROM alpine:latest
-
FROM
はベースイメージを指定するためのキーワード -
alpine:latest
はベースイメージの名前
Dockerfileの詳細については、Phase 2で学びます。
Dockerイメージのビルドと実行
次に、ビルドして実行してみましょう。
docker build -t myalpine .
-t
オプションはタグ付けを行うためのものです。
タグを指定しない場合は、ランダムな文字列がタグとして付与されます。
タグを指定して、コンテナを実行してみましょう。
docker run -it myalpine
Alpine Linuxが正しく動作しているかを確認してみましょう。
cat /etc/os-release
確認が終わったら、コンテナから出ましょう。
exit
コンテナの停止と削除
Docker DesktopのContainersをチェックすると、コンテナを確認することができます。
この記事通りにやっていた場合、alipine
とmyalpine
というIMAGEで作られたコンテナが停止しているはずです。
これらコンテナは、GUIでもCLIでも削除することができます。
また、コンテナを停止した時に自動で削除したい場合は、--rm
オプションを使用します。
docker run --rm -it myalpine
exit
した後にコンテナは削除されているはずです。
--rm
オプションを付けずにコンテナを実行や停止を繰り返していると、動いていないコンテナがどんどん溜まっていくので注意して下さい。
バックグラウンドでの実行とコンテナへのアクセス
バックグラウンドでコンテナを実行することも可能です。以下のコマンドを実行します。
docker run --rm -d -it myalpine
-d
オプションはデタッチモード(バックグラウンドで実行)を指定します。
そして、docker exec
コマンドでコンテナにアクセスすることができます。
docker exec -it <container id> sh
-it
オプションは、docker run
の時と同様に、インタラクティブモードを指定します。
コンテナIDを確認するには、以下のコマンドを実行します。出力される一覧から<container id>を確認します。
docker ps
バックグラウンドのコンテナを停止/削除するには、まず、コンテナを停止します。--rm
オプションを使用していた場合、コンテナは停止と同時に自動的に削除されます。
docker stop <container id>
コンテナを手動で削除するには、docker rm
コマンドを使用します。
docker rm <container id>
Phase 2: Dockerfileをもう少し書いてみる
今度は、Dokcer上でexpressを使った簡単なWebアプリケーションを作成してみましょう。
mkdir front
cd front
npm init -y
npm install express
index.js
を作成します。
// Expressライブラリをインポート
const express = require('express');
// Expressを初期化
const app = express();
// リッスンするポートを定義
const port = 3000;
// ルートパス('/')へのGETリクエストのルートハンドラを定義
app.get('/', (req, res) => {
res.send('Hello, Docker!');
});
// 指定したポートでアプリをリッスン
app.listen(port, () => {
console.log(`App running on http://localhost:${port}`);
});
Dockerfileを作成します
先ほどのDockerfileと違い、さまざまなコマンドが実行されています。
ただ、やっていることは単純なので、コピペした後にそれぞれのコメントを読んでみてください。
# ベースイメージとしてNode.js 14のイメージを選択
FROM node:14-slim
# コンテナ内の作業ディレクトリを/appに設定
WORKDIR /app
# package.jsonとpackage-lock.jsonをコンテナ内の作業ディレクトリにコピー
COPY package*.json ./
# アプリケーションの依存関係をインストールするためにコンテナ内でnpm installを実行
RUN npm install
# アプリケーションのソースコードの残りを作業ディレクトリにコピー
COPY . .
# アプリケーションはポート3000でリッスンするので、Dockerにこのポートを公開するように指示
EXPOSE 3000
# アプリケーションを起動するコマンド
CMD [ "node", "index.js" ]
.dockerignoreファイルを作成します
これは、Dockerイメージをビルドする際に無視するファイルを指定するためのものです。
node_modules
npm-debug.log
それでは、Dockerイメージをビルドしてみましょう。
docker build -t front-demo .
次に、コンテナを実行してみましょう。
今回は、-p
オプションを使用して、ホストのポート3000をコンテナのポート3000にマッピングします。
こうすることで、ホストからコンテナにアクセスすることができます。
docker run -d --rm -p 3000:3000 front-demo
localhost:3000にアクセスしてみましょう。
確認できたら、CLIがGUIでコンテナは停止しといて下さい。
Phase 3: Docker Composeを使ってみよう
Dokcerでは、1プロセスのみを実行するコンテナを作成することが推奨されています。
ここでは、先ほどのExpressアプリケーションに加えて、ChatGPTを使ったAPIを実行するコンテナを作成して、複数のコンテナを同時に実行する方法を試してみます。
OpenAIのAPIを使ったアプリですが、使う必要は全くないので適宜書き換えて下さい。
まずは、プロジェクトルートに戻って、API用のフォルダを作成します。
cd ../
mkdir back
cd back
Flaskを使った簡単なWebAPIを作成してみましょう。
まずは、requirements.txtを作成し、必要なPythonパッケージをリストアップします。
flask
openai
ここでは、pip install
する必要はありません。
Dockerfileで自動的にインストールされるようにこのあと記述します。
次に、app.pyを作成します。
from flask import Flask, Response, request
import openai
import os
import json
app = Flask(__name__)
@app.route('/', methods=['GET'])
def generate():
openai.api_key = os.getenv('OPENAI_KEY')
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "user",
"content": f"Please generate one sentence of a joke in Japanese."
}
],
temperature=1,
max_tokens=256,
top_p=1,
frequency_penalty=0,
presence_penalty=0
)
result = json.dumps(response.choices[0].message.content.strip(), ensure_ascii=False)
return Response(result, content_type='application/json; charset=utf-8')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=4000)
最後にDockerfileを作成します。
# ベースとなるイメージの設定
FROM python:3.11-slim
# ワーキングディレクトリの設定
WORKDIR /app
# 依存関係リストのコピー
COPY requirements.txt requirements.txt
# 依存関係のインストール
RUN pip install -r requirements.txt
# ソースコードのコピー
COPY . .
# ポート4000を公開
EXPOSE 4000
# 実行コマンド
CMD [ "python", "app.py" ]
Dockefileが出来上がったら、ビルドです。
docker build -t back-demo .
実行コマンドでは、OpenAIのAPIを使うためにAPIキーを渡さなければなりません。
環境変数をコンテナに渡すためには、-e
オプションをdocker run
コマンドと共に使用します。
docker run -p 4000:4000 -d --rm -e OPENAI_KEY=your_openai_key back-demo
もしくは、シェル変数からAPIキーを読み込む場合は以下のようにします:
docker run -p 4000:4000 -d --rm -e OPENAI_KEY=${OPENAI_API_KEY} back-demo
にアクセスしてみましょう。
日本語の粋なジョークが表示されるはずです。
次に進む前に、コンテナを停止しておいて下さい。
次に、フロントエンド部分を手直しします。
ホストマシンで、front
フォルダに移動し、axios
をinstallして下さい。
cd ../
cd front
npm install axios
次に、index.jsを書き換えます。
docker-hands-on/front/index.js
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
app.use(express.static('public'));
app.post('/api', async (req, res) => {
try {
const response = await axios.get('http://api:4000');
res.send(response.data);
} catch (error) {
console.error(`Error: ${error}`);
}
});
app.listen(port, () => {
console.log(`App running on http://localhost:${port}`);
});
frontディレクトリの中に、publicディレクトリを作成し、その中にindex.html
を作成します。
docker-hands-on/front
mkdir public
touch public/index.html
vim public/index.html
docker-hands-on/front/public/index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World Page</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@1.6.1"></script>
</head>
<body class="grid items-center justify-items-center h-screen gap-4 bg-gray-200">
<div id="response" class="text-center">Waiting for response...</div>
<button class="px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700" hx-post="/api" hx-trigger="click" hx-swap="innerHTML" hx-target="#response">Click me!</button>
</body>
</html>
フロントエンドアプリケーションの内容を書き換えたので、Dockerイメージを作り直します。
docker-hands-on/front
docker build -t front-demo .
最後に、フロントエンドとバックエンドのコンテナを同じDockerネットワークに接続します。これにより、フロントエンドからバックエンドのAPIを呼び出すことができます。
以下のコマンドでDockerネットワークを作成します:
docker network create network-demo
そして、以下のコマンドでフロントエンドとバックエンドのコンテナを同じネットワークに接続します:
docker run --rm -d -p 3000:3000 --network network-demo --network-alias web front-demo
docker run --rm -d -p 4000:4000 -d -e OPENAI_KEY=${OPENAI_API_KEY} --network network-demo --network-alias api back-demo
やっとここまで来ました。
最後にこのPhaseのメインディッシュです。
dockerコマンドのオプションが多くて大変になってきたので、Docker Composeを使用して、これらをより簡単に管理できるようにしてみましょう。
プロジェクトルートにdocker-compose.yml
というファイルを作ります。
コメントより、修正。
現在は、docker-compose.yml
ではなく、compose.yml
が推奨とのこと。
version: '3.8' # 修正;versionは(optional)となっており、必須ではない。
services:
front:
build:
context: ./front
ports:
- 3000:3000
networks:
app-network:
aliases:
- web
back:
build:
context: ./back
ports:
- 4000:4000
environment:
- OPENAI_KEY=${OPENAI_API_KEY} #環境変数に設定してない場合は、直でAPIキーを書いて下さい。
networks:
app-network:
aliases:
- api
networks:
app-network:
name: network-demo
compose.yml
ファイルを作成したら、以下のコマンドでコンテナを起動します:
コメントより、修正。
以前は、docker-compose うんたらかんたら
でしたが、現在は、docker compose うんたらかんたら
が推奨とのこと。
docker compose up -d
http://localhost:3000にアクセスしてみましょう。
ボタンを押すたびに、小粋なジョークが展開されるはずです。
ちなみに、Dockerfileを変更した場合、以下のコマンドでイメージを再ビルドする必要があります
docker compose build
もしくは、以下のコマンドでイメージを再ビルドしてコンテナを再起動します:
docker compose up -d --build
最後に、コンテナを停止しておきましょう。
docker compose down
以上でこの記事は終了です。
最後までお読みいただきありがとうございます。
Discussion
docker-compose
コマンドは、古い方法ですdocker compose
を使う方法をググってみてくださいdocker-compose.ymlは
いまはcompose.ymlが推奨されてて
version
キーは必要ありませんご指摘ありがとうございます。
こちらにそのことについて、書かれてました。