Dockerを触り始めたときに一番混乱しやすいのは、「コンテナって結局なに?」「イメージと何が違うの?」「止めたら消えるってどういうこと?」あたりです。さらに、チュートリアルでよく見る docker run を打って終わりだと、運用や実務に必要な“勘所”が身につきません。
この記事では、コンテナの仕組み(概念)→ よく使う機能 → つまずきどころ → 実戦的ハンズオンの順で、文章多めに丁寧にまとめます。最後までやると、Dockerを「なんとなく」から「自分で扱える」感覚に変えられるはずです。
Dockerコンテナとは何か(イメージとの違いから理解する)
Dockerで扱う中心は イメージ と コンテナ です。この2つを曖昧なまま進むと、以降の理解がずっとフワッとします。
イメージ:実行環境の“設計図”
イメージは、アプリを動かすために必要なファイルや設定がまとまったものです。たとえばNginxのイメージには、Nginx本体と周辺設定、必要なライブラリなどが含まれています。イメージは基本的に 読み取り専用 で、変更するなら新しいイメージとして作り直します(これが再現性につながります)。
コンテナ:イメージから起動した“実体”
コンテナはイメージを元に起動したプロセス(と、その周辺の隔離された環境)です。イメージは設計図、コンテナは実際に動いている実体。
コンテナは起動すると、イメージに加えて 書き込み可能な層(コンテナ固有の変更分)を持ちます。だからコンテナ内でファイルを編集できますが、コンテナを消すとその書き込み層も消えます(後でハンズオンで体験します)。
「軽量VM」と言われる理由と、VMとの違い
Dockerコンテナが軽いのは、VMのようにゲストOSを丸ごと持つのではなく、ホストOSのカーネルを共有してプロセスとして隔離しているからです。
- VM:仮想マシンごとにOSが必要 → 重い、起動が遅い
- コンテナ:カーネル共有 → 軽い、起動が速い、密度が高い
ただし「カーネル共有」なので、VMと比べると隔離の粒度が異なります(権限やセキュリティは設計次第)。とはいえ、一般的なWebアプリ開発・配布・CI/CDでは、コンテナのメリットが圧倒的に大きい場面が多いです。
コンテナのライフサイクル(この流れが身体に入ると強い)
Dockerの操作は、だいたい次の流れに集約されます。
- 起動する(必要ならpullされる)
- 状態を確認する
- ログを見る
- 中に入って調べる(必要なら)
- 止める
- 消す(整理する)
この流れをコマンドで表すとこうです。
- 起動:
docker run - 確認:
docker ps/docker ps -a - ログ:
docker logs - 中に入る:
docker exec - 停止:
docker stop - 削除:
docker rm
これを「一度でも」実際に回すだけで、Dockerの不安はかなり減ります。
よく使うDockerコマンドの意味を、ちゃんと理解する
docker run は何をしている?
docker run は、実は複数の操作のまとめ技です。
- ローカルにイメージがなければpull
- コンテナを作成(create)
- コンテナを起動(start)
なので、実務でトラブルシュートするなら「runの裏で何が起きているか」を意識すると強いです。
docker ps と docker ps -a
docker ps:今動いているコンテナ一覧docker ps -a:停止済みも含めた一覧(落ちたコンテナも見える)
「コンテナどこ行った?」となったらまず docker ps -a。
docker logs
コンテナがすぐ落ちる時、まず見るべきはログです。Dockerは「標準出力・標準エラーにログを出す」設計と相性が良く、コンテナ運用の基本になります。
docker exec
動いているコンテナの中でコマンドを実行できます。
「設定ファイルがどこにあるか」「ファイルが配置されているか」「環境変数が入っているか」など、現場で確認する場面は多いです。
ハンズオン1:Nginxコンテナで静的サイトを配信して、コンテナの基本を掴む
ここからは実際に動かして理解します。最初は、何も作らず既存イメージで体験するのが最短です。
1) 作業ディレクトリとHTMLを用意する
mkdir docker-container-handson
cd docker-container-handson
mkdir site
site/index.html を作ります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Docker Container Hands-on</title>
</head>
<body>
<h1>Hello Docker Container 👋</h1>
<p>nginxコンテナで配信中</p>
</body>
</html>
この時点では「ただのHTMLファイル」です。Dockerの良いところは、これを ホストにNginxをインストールせず 配信できる点です。
2) Nginxコンテナを起動(ポート公開+ボリュームでマウント)
docker run --name web \
-p 8080:80 \
-v "$(pwd)/site:/usr/share/nginx/html:ro" \
nginx:alpine
ここが最重要ポイントです。
--name web:コンテナに名前を付ける(あとから操作しやすい)-p 8080:80:ホストの8080番で受けたアクセスを、コンテナの80番へ転送-v ...:ro:ホストのsite/を、コンテナのWeb公開ディレクトリへマウント(読み取り専用)
ブラウザで http://localhost:8080 を開いて表示されれば成功です。
補足(わかりやすく)
コンテナのNginxは “コンテナ内の80番” で待ち受けています。
でもホストからは直接その80番にアクセスできないので、ホスト側8080 → コンテナ側80を-pでつなぎます。
3) 状態を確認する(psとlogs)
別ターミナルを開いて、次を実行します。
docker ps
web が Up ... として表示されます。
次にログを見ます。
docker logs web
コンテナがどんな状態で動いているかのヒントは、まずログに出ます。
4) コンテナの中に入って確認する(exec)
docker exec -it web sh
中に入ったら、ファイルの存在を確認してみましょう。
ls -la /usr/share/nginx/html
cat /usr/share/nginx/html/index.html
exit
「ホストの site/ が、コンテナの公開ディレクトリに見えている」ことを目で確認できます。
5) 停止・削除して整理する
docker stop web
docker rm web
Dockerは“使い捨てできる”のが強みなので、不要になったら消す癖をつけると快適です。
ハンズオン2:「コンテナに書いたデータは消える」を体験する
次はDockerの超重要な性質を体験します。
1) コンテナ内にファイルを書いてみる
docker run --name tmp -it alpine:3.20 sh
コンテナ内で実行:
echo "hello" > /hello.txt
ls -la /
cat /hello.txt
exit
2) コンテナを削除する
docker rm tmp
3) 同じイメージで新しいコンテナを作って確認
docker run --name tmp2 -it alpine:3.20 sh
ls -la /
# /hello.txt は存在しない
exit
docker rm tmp2
ここで重要なのは、イメージは同じでもコンテナは別物だということです。
さっき作った /hello.txt は「tmpコンテナの書き込み層」に存在していたので、消したら一緒に消えます。
ハンズオン3:ボリュームで永続化する(実務で必須)
「消えるのは困る」データ(DB、アップロードファイル、永続ログなど)は、ボリュームを使って保持します。
1) 名前付きボリュームを作る
docker volume create mydata
docker volume ls
2) そのボリュームにファイルを書き込む
docker run --rm -it -v mydata:/data alpine:3.20 sh -lc \
'echo hello-volume > /data/hello.txt && ls -la /data && cat /data/hello.txt'
3) 別コンテナから同じボリュームを読む
docker run --rm -it -v mydata:/data alpine:3.20 sh -lc \
'ls -la /data && cat /data/hello.txt'
コンテナが違ってもデータが残っている=永続化できている、ということです。
4) 後片付け
docker volume rm mydata
ハンズオン4:コンテナ同士を通信させる(ネットワークの基本)
「WebコンテナからDBコンテナへ接続」など、実務では複数コンテナが当たり前です。
ここでのポイントは “localhost罠” を避けること。
- コンテナ内の localhost は、そのコンテナ自身
- 別コンテナへは 同一ネットワークに置いて、名前で接続する
1) ユーザー定義ネットワークを作る
docker network create app-net
docker network ls
2) Redisコンテナを起動(app-netへ接続)
docker run -d --name redis --network app-net redis:7-alpine
3) もう一つのコンテナからredisへ疎通
docker run --rm -it --network app-net redis:7-alpine sh -lc \
'redis-cli -h redis PING'
PONG が返れば成功です。
ここで -h redis と書ける理由は、同一ネットワーク内ではコンテナ名で名前解決できるからです。
4) 後片付け
docker rm -f redis
docker network rm app-net
実務で差がつく:コンテナ運用の“考え方”
ここからは、コマンドを覚えるより大事な考え方です。
1) 1コンテナ=1プロセス(が基本)
Dockerの哲学として、コンテナは「小さく、役割を明確に」した方が運用しやすいです。
Web、DB、バッチ、ワーカーなどは別コンテナに分ける方が、障害切り分けもスケールも簡単になります。
2) ログはコンテナに溜めない
ログをファイルに書き続けると、コンテナのディスクが膨らみます。
基本は標準出力へ出し、外側で集約(ログ基盤、クラウドログ等)するのがセオリーです。
3) 重要データはボリュームへ
コンテナは捨てやすいことがメリットです。捨てても困らない状態を作るために、
データはボリューム、設定は環境変数や外部設定、秘密情報はSecret管理、という方向で設計すると強いです。
4) 再起動ポリシーで“落ちたら復帰”を設計する
個人開発や簡易サーバでも便利です。
docker run -d --name web \
--restart=unless-stopped \
-p 8080:80 nginx:alpine
5) リソース制限は保険になる
暴走したコンテナがホスト全体を落とすのはよくある事故です。
docker run --rm --memory=256m --cpus=1 alpine:3.20 sh -lc 'echo limited'
よくあるつまずき(原因の当たりをつけるコツ)
コンテナがすぐ落ちる
まずこれ。
docker logs <container>
docker ps -a
終了コードが知りたければ docker inspect も有効です。
「接続できない」問題
- ポート公開してない:
-pを付け忘れ - アプリが0.0.0.0で待ち受けていない(127.0.0.1固定)
- コンテナ間通信で
localhostを使っている(別コンテナに繋がらない)
ボリュームが思った通りに効かない
- マウント先のパスが間違っている
- Windowsの場合、Docker Desktopの共有設定やパス表記の影響が出ることがある
:roで読み取り専用にして書けない(それは仕様)
コメントを残す