Docker
〜 ひとつの機能に一つのコンテナ 〜
2026-03-03 作成 福島
TOP > tips > docker-basics
[ TIPS | TOYS | OTAKU | LINK | MOVIE | CGI | AvTitle | ConfuTerm | HIST | AnSt | Asob | Shell | GBC | LLM ]

Docker とは

Docker(ドッカー) とは、機能を配布する手段です。

「Docker イメージ」というパッケージを作成し、これを起動することにより機能を達成します。
多くのことは (無茶をしない限り) できません。一つの Docker イメージにつき、一つの機能を持たせます。
複数の機能が必要なときは、複数の Docker イメージを用意します。

普通のサーバ構成とは逆の考え方をします。
× ひとつの OS を用意して複数の機能をインストールする。← ふつうはこっち
機能ごとに OS を用意し、複数の OS を走らせる。← Docker はこっち
Docker は仮想化技術のひとつである「コンテナ機能」を利用しています。
親 OS に仮想化機能が無い場合は Docker を使用できません。
(LXC のようなコンテナの中からも起動できるらしいけど、リソースの開放が必須では意味がない)

また、内蔵する機能は一つだけなので、別の機能である sshd や telnetd を混在できません。
このため、通常の OS で日常的に使用する ps や ping を Docker にログインして実行することはできません。
(半分うそ。/bin/bash を指定して run させればコマンド実行できるし、無茶をすれば複数の機能を詰め込める)

パッケージの配布と利用を最優先とし、インストールした機能をメンテナンスすることなく運用するのが Docker の特徴と言えます。
これが Docker の使い方です。この使い方ができないものは Docker に向いていません。


1. 動作環境の用意

1-1. Docker エンジンをインストールする。
Docker エンジンとは下記 a~c を含んだセットを指し、Docker デーモンの上で Docker イメージを動作させる。
Docker イメージは LXC の中から起動させない。Docker エンジンは実機や KVM 等のコンテナ以外の OS にインストールする。
  1. Docker デーモン
  2. API
  3. docker コマンド (CLI)
$ su
# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# systemctl enable docker --now
1-2. サンプルで動作確認する。
docker コマンドは、デーモンやローカルリポジトリ*1に直接アクセスするため、管理者権限が必要。
*1公式には「レジストリ(保管場所)」と「リポジトリ(イメージの集合)」が区別されるが、
  本稿ではローカルのイメージ保存領域を便宜上「ローカルリポジトリ」と呼称する。


# docker run hello-world

初回は hello-world のダウンロードが実行される。
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
ea52d2000f90: Download complete
Digest: sha256:ef54e839ef541993b4e87f25e752f7cf4238fa55f017957c2eb44077083d7a6a
Status: Downloaded newer image for hello-world:latest
Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
1-3. プロセスを確認する。
hello-world は表示するとすぐに終了するため、プロセスが見えない。
# docker ps
CONTAINER ID IMAGE       COMMAND  CREATED        STATUS                    PORTS   NAMES              
 --- hello-world のプロセスは表示されない ---

終了したプロセスを確認
# docker ps -a
CONTAINER ID IMAGE       COMMAND  CREATED        STATUS                    PORTS   NAMES
bc4c49cb10c4 hello-world "/hello" 19 seconds ago Exited (0) 18 seconds ago         condescending_jones

# exit
$


2. Docker イメージの作成と動作確認

現在時刻を返すサーバ機能を作成してみる。
標準的な OS のコマンドだけを使用するため、プログラムは作成しない。

2-1. Docker イメージを作成する。
2-1-1. 定義ファイルを作成する。
$ mkdir daytime
$ cat > ./daytime/Dockerfile << EOF
FROM almalinux:9-minimal

# ncat をインストール
RUN microdnf install -y nmap-ncat && microdnf clean all

# リッスンポート (省略可。Docker desktop では必須)
EXPOSE 130

# ポート130で待ち受け、接続されたら現在時刻を返して終了する設定
# -lk: 待機を続ける, 130: ポート番号, -e: 接続時に実行するコマンド
CMD ["ncat", "-lk", "130", "-e", "/usr/bin/date"]
EOF
2-1-2. Docker イメージをビルドする。
$ su
# docker build -t daytime ./daytime/
[+] Building 2.5s (6/6) FINISHED                                 docker:default
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 422B                                       0.0s
 => [internal] load metadata for docker.io/library/almalinux:9-minimal     2.0s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 2B                                            0.0s
 => [1/2] FROM docker.io/library/almalinux:9-minimal@sha256:5ad6bf379aa6b  0.0s
 => => resolve docker.io/library/almalinux:9-minimal@sha256:5ad6bf379aa6b  0.0s
 => CACHED [2/2] RUN microdnf install -y nmap-ncat && microdnf clean all   0.0s
 => exporting to image                                                     0.3s
 => => exporting layers                                                    0.0s
 => => exporting manifest sha256:e33c58c4dd5ee8c6a5647386ba84a018391a8d32  0.0s
 => => exporting config sha256:dfd1ba639f5a22e930276b206122d3107561c1e636  0.0s
 => => exporting attestation manifest sha256:9bcd48a21a2ff7f467ef4db6335e  0.0s
 => => exporting manifest list sha256:ae77ea3ee1394d00dd758812b831445f645  0.0s
 => => naming to docker.io/library/daytime:latest                          0.0s
 => => unpacking to docker.io/library/daytime:latest                       0.1s

# docker images daytime
                                                           i Info →   U  In Use
IMAGE            ID             DISK USAGE   CONTENT SIZE   EXTRA
daytime:latest   8df6b92ba1dd        146MB         38.6MB    U 
イメージが実行中の場合は EXTRA の列に  U  が表示される。

Docker イメージのリポジトリは下記コマンドにより確認できるが、実際の格納場所を確認することは出来ない。
# docker info | egrep '(Docker Root Dir|Storage Driver)'
 Storage Driver: overlayfs
 Docker Root Dir: /var/lib/docker 

# exit
$


2-2. 動作確認をする。

2-2-1. コンテナを起動する。
ここでは、自ホストの 1300 番ポートを経由してコンテナにアクセスする。
また、テストのためフォアグラウンドで実行する。(-d を付けない)

$ su
# docker run -p 1300:130 --rm --init --name daytime-0 daytime
確認 (下記 2-2-2) ができたら、 で停止させる。
-p : ホストポートとコンテナポート*2を指定する。
--rm : 実行時のワークを削除する。(一時ファイルやログ)
--init : 終了シグナルの受け口を設けて正常終了を可能にする。(Ctrl + C を使用する場合は必須)
--name : 実行インスタンスの名称を指定する。(ひとつのイメージから複数のインスタンスを実行可能)
*2大抵は、ターゲットポートと呼ばれる。
# exit
$

2-2-2. 別の端末から確認する。(同じコンピュータ)
$ ncat localhost 1300 -i 0.1s
Tue Mar  3 12:50:53 UTC 2026
Ncat: Idle timeout expired (100 ms).

コンテナが停止しない場合はこちら。
$ su
# docker stop daytime-0
daytime-0

# exit
$


2-3. Docker イメージの削除

2-3-1. イメージの存在を確認する。
$ su
# docker images daytime
                                                           i Info →   U  In Use
IMAGE            ID             DISK USAGE   CONTENT SIZE   EXTRA
daytime:latest   8df6b92ba1dd        146MB         38.6MB    U 
2-3-2. イメージを削除する。
# docker image rm daytime
Untagged: daytime:latest
Deleted: sha256:25fcd04435568dc92bb60ee6008c608d104e60da7ebb0a37d3a46ca58d356797
2-3-3. イメージの削除を確認する。
# docker images daytime
                                                           i Info →   U  In Use
IMAGE            ID             DISK USAGE   CONTENT SIZE   EXTRA
2-3-4. ワークを削除する。
既に終了したコンテナのワークをすべて削除する。(一時ファイルやログ)
# docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y 
Deleted Containers:
cb0a8215a0a7d916e3a2bc1b753a3dd50fe460fb050ceb235efa0efc7707d344
bc4c49cb10c49e20984f9eb7d3edcb1f03d6b88153d5f7f37b78315de16c7ee1

Total reclaimed space: 0B

# exit
$


3. じゃんけんシステムの作成

3 つのじゃんけんサーバを動作させ、それぞれの勝敗をカウントする。
じゃんけんサーバはグー・チョキ・パーを返すだけとし、進行役コンテナが勝敗を取りまとめる。
それらを相互接続するために、ユーザ定義ブリッジネットワークを作成する。

プログラムは Python を使用し、Flask を使ったじゃんけんサーバ 1 つ*3と、MC クライアント*4を作成する。
*3 1 つのイメージを、名前を変えて 3 つ起動する。
*4 MC: 司会進行役のこと。イベントを進行させたり、盛り上げたりする。

3-1. じゃんけんサーバを作成する。
3-1-1. プレイヤーのプログラムを作成する。
$ mkdir ./janken/
$ cat > ./janken/player.py << EOF
#!/usr/bin/env python3
from flask import Flask, jsonify
import os, random

MY_NAME = os.getenv('MY_NAME', 'unknown')
app = Flask(__name__)

@app.route('/hand')
def get_hand():
    # ランダムに手を返す
    hand = random.choice([
        'R',    # Rock      グー
        'P',    # Paper     パー
        'S',    # Scissors  チョキ
    ])
    return jsonify({"name": MY_NAME, "hand": hand})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
EOF
3-1-2. プレイヤーの Dockerfile として "player" を作成する。
$ cat > ./janken/player << EOF
# 材料(OS)の指定
FROM almalinux:9

# 作業ディレクトリの指定
WORKDIR /app

# 道具のインストール(ビルド時にネットワークが必要)
RUN dnf install -y python3 python3-pip
RUN pip3 install flask

# リッスンポート (省略可。Docker desktop では必須)
EXPOSE 8080

# プログラムをコンテナの中へ入れる
COPY player.py /app/player.py

# 起動時に実行するコマンド (AUTOEXEC)
ENTRYPOINT ["python3"]
CMD ["player.py"]
EOF
3-1-3. プレイヤーをビルドする。
$ su
# docker build -t janken-player -f ./janken/player ./janken/
3-1-4. プレイヤーの動作を確認する。
-d でバックグラウンド動作させる。

# docker run -d -p 8080:8080 --name jp1 -e MY_NAME=jp1 janken-player
5af15fa686ef575c0685f90e501e0e68de5b2e857c408845fe58518d888567f2 ← インスタンス ID (毎回異なる)

# curl localhost:8080/hand ; curl localhost:8080/hand
{"hand":"R","name":"jp1"}
{"hand":"S","name":"jp1"}

# docker stop jp1
# exit
$

3-2. MC クライアントを作成する。
3-2-1. クライアントのプログラムを作成する。
$ cat > ./janken/mc.py << EOF
#!/usr/bin/env python3
import requests, time

time.sleep(3) # プレイヤーの起動を待つ

nodes = [
  'http://jp1:8080/hand',
  'http://jp2:8080/hand',
  'http://jp3:8080/hand',
]

results = {'jp1': 0, 'jp2': 0, 'jp3': 0}

def judge(h_a, h_b, h_c):
    hands = set([h_a, h_b, h_c])

    # 3種類出た、または1種類だけなら「あいこ」
    if len(hands) == 3: return []
    if len(hands) == 1: return []
    
    # 勝敗が決まる場合(2種類のみ存在)
    win_map = {
      'R': 'S',   # グー : チョキ
      'S': 'P',   # チョキ : パー
      'P': 'R',   # パー : グー
    }

    # どの手が勝っているか特定
    h1, h2 = list(hands)
    winner_hand = h1 if win_map[h1] == h2 else h2
    
    # 勝った手を出した人をリストで返す
    winners = []
    if h_a == winner_hand: winners.append('jp1')
    if h_b == winner_hand: winners.append('jp2')
    if h_c == winner_hand: winners.append('jp3')

    return winners

for i in range(10):

    print(f'Round {i+1: >2}:', end='')
    try:
        # 3人から手を取得
        h_a = requests.get(nodes[0]).json()['hand']
        h_b = requests.get(nodes[1]).json()['hand']
        h_c = requests.get(nodes[2]).json()['hand']
        
        winners = judge(h_a, h_b, h_c)
        for w in winners:
            results[w] += 1
            
        print(f'jp1:{h_a}, jp2:{h_b}, jp3:{h_c} -> 勝ち: {winners}')
    except Exception as e:
        print(f'Error: {e}')

print('-----')
print(f'結果: {results}')
EOF
3-2-2. MC クライアントの Dockerfile として "mc" を作成する。
$ cat > ./janken/mc << EOF
# 材料 (OS) の指定
FROM almalinux:9

# 作業ディレクトリを設定
WORKDIR /app

# Python と HTTPリクエスト用のライブラリをインストール
# (ビルド時にネットワークが必要)
RUN dnf install -y python3 python3-pip && \
    pip3 install requests

# MC のプログラムをコンテナ内にコピー
COPY mc.py /app/mc.py

# 起動時に実行するコマンド (AUTOEXEC)
ENTRYPOINT ["python3", "mc.py"]
EOF
3-2-3. MC クライアントをビルドする。
$ su
# docker build -t janken-mc -f ./janken/mc ./janken/

3-3. 動作を確認する。
3-3-1. ユーザ定義ブリッジネットワークを janken-net の名称で作成する。
# docker network create janken-net
3e13c3a3f0eeba0cbc21d5cf7b61528749b67c2b7cbcf54e1350c6225d767b75 ← インスタンス ID (毎回異なる)
3-3-2. じゃんけんプレイヤーを 3 つ起動する。(janken-net に接続)
# docker run -d --rm --init --name jp1 --network janken-net -e MY_NAME=jp1 janken-player
# docker run -d --rm --init --name jp2 --network janken-net -e MY_NAME=jp2 janken-player
# docker run -d --rm --init --name jp3 --network janken-net -e MY_NAME=jp3 janken-player
3-3-3. MC クライアントを実行する。(janken-net に接続)
# docker run --rm --init --name jmc --network janken-net janken-mc
Round  1:jp1:S, jp2:S, jp3:S -> 勝ち: []
Round  2:jp1:P, jp2:S, jp3:S -> 勝ち: ['jp2', 'jp3']
Round  3:jp1:R, jp2:S, jp3:R -> 勝ち: ['jp1', 'jp3']
Round  4:jp1:P, jp2:R, jp3:P -> 勝ち: ['jp1', 'jp3']
Round  5:jp1:R, jp2:P, jp3:S -> 勝ち: []
Round  6:jp1:S, jp2:R, jp3:R -> 勝ち: ['jp2', 'jp3']
Round  7:jp1:R, jp2:P, jp3:P -> 勝ち: ['jp2', 'jp3']
Round  8:jp1:P, jp2:S, jp3:R -> 勝ち: []
Round  9:jp1:R, jp2:R, jp3:R -> 勝ち: []
Round 10:jp1:P, jp2:R, jp3:S -> 勝ち: []
-----
結果: {'jp1': 2, 'jp2': 3, 'jp3': 5}
3-3-4. じゃんけんプレイヤーを 3 つ停止させる。
# docker stop jp1 jp2 jp3
3-3-5. ユーザ定義ブリッジネットワーク janken-net を削除する。
# docker network rm janken-net

# exit
$


4. Docker イメージの受け渡し

Docker イメージはローカルリポジトリを基本として動作するため、他の環境へ移すにはエクスポート・インポートの作業が必要になる。
tar 形式だが、エクスポート時の圧縮は不要。
Docker イメージは、ビルド直後に圧縮してローカルリポジトリに格納されている。
(docker run は自動的に解凍して実行される)

4-1. イメージをまとめてエクスポートする。
$ su
# docker save janken-player janken-mc > janken.tar
# exit

$ tar -xOf janken.tar manifest.json | jq -r '.[].RepoTags[]'
janken-player:latest
janken-mc:latest
4-2. イメージをまとめてインポートする。
$ su
# docker load < janken.tar
Loaded image: janken-player:latest
Loaded image: janken-mc:latest

# exit
$