1-2. docker run - コンテナを動かして体感する
所要時間: 40-50分(がっつりなら2セッション分) ゴール:
docker runの主要オプションを使いこなし、コンテナがプロセスであることを体感する コミット内容: 操作ログを~/log/docker_day02.logに保存。お試しで作ったコンテナは全部消す
このレッスンのゴール
-
docker run -d -p ... --name ... <イメージ>を指が覚えて打てる -
-it--rm-v-eの意味と使いどころが言える - コンテナの起動・停止・削除・ログ確認・exec の流れを止まらず一巡できる
- 「コンテナ=プロセス」の感覚で
docker psをpsの延長として読める
なぜ学ぶか(実務悩みベース)
- 「DB だけサッと立ち上げて検証したい」が
docker runで30秒でできる - 同僚に「postgres 立てたいんだけど」と聞かれたら即答できる
- CI/CD やローカル開発で
docker runを呼ぶ箇所が頻出 - 「コンテナが起動しない」「ログが見えない」初動デバッグの土台になる
前章とのつながり
1-1 では「コンテナ=namespaces+cgroups で隔離された Linux プロセス」と理屈で押さえた。今回はその理屈を 手で打って確かめる 回。docker exec が「動いているプロセスの namespace に入る」操作だと体で分かれば、もう抽象論は不要になる。
大前提: 手を動かしてコンテナの感覚を掴む
前回はコンテナ=隔離プロセスという話をした。今日はその「プロセス」を実際に起動・停止・覗き込む。
このレッスンの裏テーマは:
「コンテナはプロセスである」を体で覚える
具体的には:
docker runでコンテナが起動 = Linux プロセスがフォークされる、と同じ感覚docker ps=psコマンドのDocker版docker exec= 動いているプロセスに「入る」(実態は新しいプロセスを namespace 内に作る)docker stop= プロセスに SIGTERM を送る
ここを押さえれば、後の Dockerfile やボリュームも「プロセスを扱う延長」として理解できる。
セッション①: docker run の基本(25-30分)
0. 録画スタート
mkdir -p ~/log
script ~/log/docker_day02.log1. 一番シンプルな docker run
# Hello World 的なコンテナ
docker run hello-world
docker runの本質「イメージを元にコンテナを作って起動する」 コマンド。1コマンドで実は4つの操作をしている:
- ローカルにイメージがあるか確認(無ければ Docker Hub から pull)
- イメージを元にコンテナを作成(
docker createと同等)- コンテナのプロセスを起動(
docker startと同等)- 出力をターミナルに繋ぐ(
-itがある場合)使い所: 「ちょっと動かしたい」「お試し」に最適。本番運用では Compose や K8s 経由で起動するが、開発・検証では
docker runを毎日叩く。
# 起動したコンテナ一覧(停止中も含めて)
docker ps -a
docker psの本質
ps(Linuxのプロセス一覧コマンド)の Docker 版。起動中のコンテナ一覧 を表示する。
オプション 意味 (無し) 実行中のコンテナだけ -a停止中も含めて全部 -qコンテナIDだけ(スクリプトで使う) --formatカスタム表示(JSON出力など) 実務ユースケース
- 「あれ、さっき起動したコンテナどこ?」→
docker ps- 「停止したコンテナの残骸を整理したい」→
docker ps -aで確認 →docker rmで削除- スクリプトで「動いてるかチェック」→
docker ps -q --filter name=myapp
2. 終了後に自動削除(—rm)
# --rm を付けると終了したコンテナが自動で消える
docker run --rm hello-world
docker ps -a # さっきと違って残骸が増えない
--rmの意味と使い所コンテナが終了した瞬間に
docker rmを自動実行する。使い所
- お試し・ワンショット実行: 「
alpineイメージでlsを実行してすぐ捨てたい」など- CI のテスト用コンテナ: テスト終了したら残骸を残さない
- 対話セッション:
docker run --rm -it ubuntu bashでシェルに入って、抜けたら自動削除落とし穴
- 本番では使わない。停止しただけで消えてしまうので、コンテナのログを後から見られない
- データが消える: コンテナ内のファイルは全部消える。永続化は必ずボリュームで(後の章で扱う)
3. nginx を立ち上げてみる
# nginx を「バックグラウンド」で起動、ポート8080をコンテナの80に繋ぐ
docker run -d -p 8080:80 --name web1 nginxブラウザで http://localhost:8080 を開く。nginx のデフォルトページが見えれば成功。
-d(detach) の本質バックグラウンド起動。コンテナをフォアグラウンドで動かす(ターミナル占有)のではなく、デーモンとして動かす。
-d無しでdocker run nginxを実行すると、nginx のログがターミナルに流れ続けて、Ctrl+C で止めるとコンテナも止まる。使い所: サーバー系のコンテナ(nginx, postgres, redis)はほぼ常に
-dを付ける。逆にbashを起動する時は-d付けない(対話したいので)。
-p(publish) の本質ホストのポートをコンテナのポートに転送する。書式は
-p ホスト側:コンテナ側。-p 8080:80 │ │ │ └─ コンテナ内のプロセスが LISTEN しているポート(nginx は 80) └─ ホスト(あなたのMac)からアクセスする時のポートこれが無いとコンテナにアクセスできない。コンテナは隔離されたネットワーク空間にいるため、外(ホスト)から見えない。
-pでホストの穴を開けて転送する。複数ポートも書ける:
-p 8080:80 -p 8443:443ユースケース
- 開発中: ホストの 3000 番をコンテナの 80 番に向ける →
localhost:3000で確認- 本番(Linuxの場合): 80 と 443 をホストに公開してリバースプロキシなしで運用も可
-pの落とし穴
- ホスト側ポートが既に使われていると起動失敗: 別のプロセスが 8080 を使っているとエラー。
lsof -i :8080で誰が使ってるか調査- ホスト側を省略すると全インターフェイスで公開:
-p 80ではなく-p 8080:80のように明示する。本番でうっかり全公開すると事故docker port web1で確認できる: 何のポートが何にマップされているかを後から確認可能
--nameの意味コンテナに名前を付ける。指定しないと Docker が自動生成(
affectionate_einsteinのような可愛い名前)。名前を付けておくと:
docker stop web1のようにIDではなく名前で操作できる- スクリプトでハードコードしやすい
落とし穴: 同名のコンテナは1つしか作れない。
docker rm web1で消してから再作成 or--rmで自動削除。
4. コンテナの中身を見る(logs / exec)
# nginx のアクセスログを見る
docker logs web1
# リアルタイムで追従
docker logs -f web1
# コンテナの中に入って bash を起動
docker exec -it web1 bash
# 中で色々試す
ls /
ps aux # nginx だけ動いていることを確認
cat /etc/os-release # コンテナ内は Debian だったりする
exit # コンテナから出る
docker logsの本質コンテナの標準出力・標準エラーを表示する。
Docker は「コンテナ内の PID 1 プロセスの stdout/stderr」を自動収集している。これを
docker logsで取り出す。
オプション 意味 -f(follow)リアルタイム追従( tail -fと同じ感覚)--tail 100末尾100行だけ --since 1h1時間前から -t(timestamps)タイムスタンプ付き 実務ユースケース: 本番でアプリのエラー調査の入口。「あれ、エラー出てる?」→
docker logs -f --tail 100 myapp落とし穴: アプリがログをファイルに書いている場合は
docker logsには出ない。stdout/stderr に出すのが原則。これは Dockerfile のベストプラクティスでも触れる。
docker execの本質動いているコンテナの中で、追加でコマンドを実行する。
重要な勘違い: 「コンテナに入る」というより「コンテナの namespace の中で新しいプロセスを起動する」が正しい。だから
docker execで起動した bash を抜けても、コンテナ本体は動き続ける。
オプション 意味 -i(interactive)標準入力を繋ぐ -t(tty)疑似端末を割り当てる(プロンプトが表示される) -it上記2つの組み合わせ。シェル起動の鉄板セット 実務ユースケース
- デバッグ: 動いているコンテナの中で
lsやcat /etc/config.ymlを確認- DB のシェルに入る:
docker exec -it db psql -U postgres- ワンショットコマンド:
docker exec web1 nginx -t(設定ファイルの構文チェック)
docker execの落とし穴
- コンテナが停止していると使えない: 停止中のコンテナには exec できない。
docker startしてから- 新規プロセスなので、シェルで設定した変数は残らない: 抜けたら消える。永続的な変更はイメージ側に入れる
- 本番では極力避ける: 「コンテナを手で改造する」のは事故の元。設定変更はイメージ再ビルドが正道
5. コンテナを止める・消す
# 停止
docker stop web1
# 起動中のものを確認(web1 はもう出てこない)
docker ps
# 停止中も含める
docker ps -a # web1 が "Exited" 状態で残ってる
# 削除
docker rm web1
# 一気に: 動いてるコンテナを止めずに削除(強制)
docker rm -f web1
# 停止中のコンテナを全部一掃
docker container prunestop / kill / rm の違い
コマンド 何をする プロセスへの影響 docker stopグレースフルに停止(SIGTERM → 10秒待ち → SIGKILL) アプリが終了処理を実行できる docker kill即座に強制終了(SIGKILL) 終了処理は走らない docker rmコンテナを削除(停止中のみ。 -fで起動中も)プロセス無し、ファイルシステムも削除 stop と kill の違いは本番で重要:
- DB 系コンテナを
killするとデータ破損のリスク- 通常は
stop(10秒で終了するなら綺麗に)、緊急時のみkill
docker container pruneでお掃除停止中のコンテナをすべて削除する。本番では使うべきではないが、開発機ではしょっちゅう叩く。
関連:
docker image prune: 未使用イメージ削除docker volume prune: 未使用ボリューム削除docker system prune -a: 全部一括(コンテナ・イメージ・ネットワーク・ビルドキャッシュ)。Mac のディスクを開放する時の鉄板
セッション②: 環境変数とインタラクティブ実行(20分)
6. 環境変数を渡す(-e)
# postgres を起動。パスワードを環境変数で渡すのが定番
docker run -d \
--name db1 \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_USER=myuser \
-e POSTGRES_DB=mydb \
-p 5432:5432 \
postgres:16
# 動いているか確認
docker logs db1
# DB に入ってみる
docker exec -it db1 psql -U myuser -d mydb
# 抜ける: \q
-e(env) の本質と「12factor」コンテナに環境変数を渡すオプション。
なぜ環境変数で渡すか:
- 設定をコードから分離する という設計思想(The Twelve-Factor App の “Config” 原則)
- イメージは1個、環境ごとに変数だけ変えれば動く(開発はパスワード
secret、本番は強い値、など)- パスワード等を Dockerfile に書かない(イメージに焼き付くと漏洩リスク)
書き方バリエーション
-e KEY=VALUE: 直接指定-e KEY: ホストの環境変数をそのまま転送(同名で)--env-file .env: ファイルから一括読み込み落とし穴: 環境変数はコンテナ起動時に固定される。後から変えるには再起動が必要。
7. インタラクティブにシェルを使う
# ubuntu イメージで bash を起動、終了したら自動削除
docker run --rm -it ubuntu bash
# 中で色々試す
apt list --installed 2>/dev/null | head
echo "ここはコンテナの中"
exit
-itの正体
-i(interactive) +-t(tty) の組み合わせ。
-i: 標準入力を繋ぐ(あなたのキー入力をコンテナに渡す)-t: 疑似端末を割り当てる(プロンプトが表示される、補完が効く)両方無いと: コマンドを打っても反応しない、Ctrl+C も効かない、というハマりが起きる シェル起動時は必ず
-it: bash, sh, psql, mysql などインタラクティブなコマンドはこのセット逆に
-dと-itは同時にあまり使わない(バックグラウンド × 対話は矛盾)
8. 「コンテナはプロセスである」を体感する
# ターミナル1: nginx をフォアグラウンドで起動
docker run --rm --name fg_nginx nginx
# (ログがターミナルに流れる)
# ターミナル2(別タブで開く):
docker ps # fg_nginx が動いてる
ps aux | grep nginx # ホスト側からも見える!
docker exec -it fg_nginx ps aux # コンテナ内のプロセス一覧観察ポイント
- ホストの
ps aux | grep nginxで、nginx プロセスがホスト側にも見えている → カーネル共有の証拠- コンテナ内の
ps auxでは nginx だけ(PID 1)。ホストの他プロセスは見えない → namespace 隔離の証拠- ホスト側でも見える、しかし内側からはホストが見えない → これがコンテナの隔離
ターミナル1で Ctrl+C → コンテナ停止 → --rm で自動削除。
9. お試し用の便利コマンド集
# alpine(5MB の極小Linux)で ls だけして終了
docker run --rm alpine ls /
# alpine の sh に入る
docker run --rm -it alpine sh
# Python のワンライナー
docker run --rm python:3.12-alpine python -c "print('Hello from container')"
# Node の REPL
docker run --rm -it node:20-alpine nodeイメージタグについて
nginxだけだとnginx:latestと同じ意味。postgres:16、node:20-alpineのように バージョンとベースOSを明示する のが本番では鉄則。
latestは時間とともに中身が変わる → 「数ヶ月後にビルドしたら壊れた」事故-alpine付きは Alpine Linux ベースで超軽量(10-50MB)- 詳細は 次回 で
練習課題
# 1. nginx を「web2」という名前で 8081 にマップして起動
docker run -d --name web2 -p 8081:80 nginx
# 2. ブラウザで http://localhost:8081 を確認
# 3. コンテナの中に入って、index.html がどこにあるか調べる
docker exec -it web2 sh
# 中で: find / -name index.html 2>/dev/null
# 中で: cat /usr/share/nginx/html/index.html | head
# 抜ける: exit
# 4. コンテナのログを表示
docker logs web2
# 5. ホスト側からアクセスして、ログがリアルタイムで増えるか確認
docker logs -f web2 &
curl http://localhost:8081/
curl http://localhost:8081/no-such-page # 404 が出るはず
# 6. お片付け
docker stop web2
docker rm web2
# 7. 念のため、起動中・停止中とも何も残っていないか
docker ps -a締め: 振り返り(10分)
1. セッション録画を終了
exit # script を抜ける2. 今日の発見
- 「コンテナはプロセス」を体感できた瞬間:
- -p / -d / -it / --rm でどれが一番直感的じゃなかった?:
- docker exec で何ができることが分かった?:
- 詰まったところ:
- 次回やりたいこと:
やらかし事例: docker run でハマる定番
事例1:
-pを忘れてブラウザから見えない
docker run -d nginxで起動したのにlocalhost:80にアクセスできない。コンテナのポートはホストに自動で開かない。-p 8080:80が必須。
事例2:
docker execでbashがない
docker exec -it alpine-container bash→executable file not found。Alpine には bash が入ってない(shのみ)。ベースイメージのシェルは何かを確認する習慣 を持つ。
事例3: 残骸が積もって disk full
--rmを付けずに毎回docker runしていると、停止コンテナが溜まり続けて数GB食う。docker ps -aで確認し、docker container pruneで定期掃除。
事例4: 名前衝突で起動失敗
--name webで起動したコンテナをdocker stopだけしてdocker rmし忘れ、再度--name webで起動 →Conflict. The container name "/web" is already in use。-f付き削除か、--rmで自動掃除。
事例5:
-itを忘れて Ctrl+C が効かない
docker run nginxをフォアグラウンドで起動 → Ctrl+C で抜けようとしても効かない。-itで TTY を割り当てるか、-dでデタッチが正解。
対比表: docker run のオプション組み合わせパターン
| 用途 | 典型コマンド | 解説 |
|---|---|---|
| ちょい試し(1回限り) | docker run --rm -it alpine sh | 終了で自動削除、対話シェル |
| 常駐サービス | docker run -d -p 8080:80 --name web nginx | バックグラウンド、ポート公開、名前付き |
| ファイル共有開発 | docker run -it -v $(pwd):/app node sh | カレントディレクトリをマウント |
| 環境変数で設定注入 | docker run -d -e DB_URL=... myapp | 12 Factor App パターン |
| ワンライナー実行 | docker run --rm python:3.12 python -c "print(1+1)" | 言語処理系を1コマンド呼出 |
自己評価チェックリスト
知識レベル
-
-d/-it/--rmの使い分けが言える -
-p HOST:CONTAINERの左右どちらがホスト側か即答できる -
docker execとdocker runの違いを説明できる
実行レベル
-
docker run -d -p 8080:80 nginxでブラウザから見えた -
docker psとdocker ps -aの違いが分かった -
docker exec -it <名前> bashでコンテナの中に入れた -
docker logs -fでログをリアルタイム追従した -
docker stopとdocker rmを順番に実行できた -
--rmの挙動を理解した - お試しコンテナを全部消した(
docker ps -aが空)
メタ認知
- 「打ったコマンド」と「裏で起きた namespace 操作」を結び付けて考えられた
- 詰まった時に「どのオプションが原因か」を切り分ける癖がついた
詰まった時のチートシート
| やりたいこと | コマンド |
|---|---|
| イメージから起動 | docker run <イメージ> |
| バックグラウンド起動 | docker run -d <イメージ> |
| ポート公開 | docker run -p 8080:80 <イメージ> |
| 名前を付ける | docker run --name web1 <イメージ> |
| 環境変数 | docker run -e KEY=VALUE <イメージ> |
| 終了後自動削除 | docker run --rm <イメージ> |
| 対話シェル | docker run -it <イメージ> bash |
| 一覧(実行中) | docker ps |
| 一覧(全部) | docker ps -a |
| 中に入る | docker exec -it <名前> bash |
| ログ | docker logs <名前> |
| ログ追従 | docker logs -f <名前> |
| 停止 | docker stop <名前> |
| 削除 | docker rm <名前> |
| 強制削除 | docker rm -f <名前> |
| 残骸一掃 | docker container prune |
「実務OK」基準
docker run -d -p ... --name ... <イメージ>が指が覚えてるdocker logs / exec / stop / rmが呼吸のように出る- コンテナがプロセスである感覚を持っている
-itを bash 起動時につけ忘れない
ここを越えれば、開発中の「ちょっと postgres 立ち上げて検証」が30秒でできるようになる。次は イメージ の構造を覗く回。
次のレッスン
1-3. イメージとレイヤー - Copy-on-Write の仕組み へ。
コンテナの元になる「イメージ」が何なのか、なぜキャッシュが効くのか、なぜ alpine が軽いのか、を解き明かす。