まず daemon と Compose プロジェクトを特定し、コンテナ・イメージ・ボリューム・ポートを区別する。例の名前は myapp。コマンドはクライアントで実行するが、bind mount のパスは daemon 側のもの。
daemon を確認して状態を読む
接続先 daemon を確認する
docker context show
docker context ls で Endpoint と照合する。--context や DOCKER_HOST / DOCKER_CONTEXT も接続先に影響する。docker info で接続と daemon を確認し、失敗時はアドレス・サービス・権限を調べる。
コンテナと終了原因を特定する
docker ps -a --format '{{.Names}} {{.Status}} {{.Ports}}'
-a は停止済みも含む。以降の myapp はここで確認した名前に置き換える。Exited は終了、Restarting は再試行を示すだけで、原因は別途調べる。
docker inspect myapp --format '{{json .State}}'
ExitCode、OOMKilled、Error、開始・終了時刻を確認する。Health があれば Log のプローブ出力を読む。下の表で次の確認先を選ぶ。
docker logs --tail 100 --since 10m myapp
--tail は行数、--since は期間。追跡は --follow を追加し Ctrl-C で終了する。対象はログドライバー経由の標準出力・標準エラーで、アプリ独自のログファイルとは限らない。
リソースとプロセスを分けて確認する
docker stats --no-stream myapp
メモリ使用量と上限を比較する。ピークは継続観測が必要で、現在の低使用率だけでは過去の OOM を否定できない。
docker top myapp
プロセスの存在だけではログや実際のリクエスト確認の代わりにならない。
| 根拠 | 意味と次の確認 |
| ExitCode / Error | アプリの終了ログを確認。非ゼロは Docker 自体の障害とは限らない |
| OOMKilled=true | 制限・ホストメモリ・カーネルログを確認。137 単独では OOM と断定不可 |
| unhealthy | Health.Log の検査結果を見る。コンテナは稼働中の場合もある |
| Restarting | 終了原因を確認してから RestartPolicy を見る |
ポートとデータの場所を明示する
ネットワークと永続ボリュームを用意する
新規作成の例。未使用名を使い、既存なら inspect してから再利用する。作成に失敗したら先に原因を解決する。
docker network create app-net
成功するとネットワーク ID が出る。同じネットワークのコンテナは名前で通信できる。
docker volume create app-data
成功すると名前が出る。ストレージを作る操作で、アプリの保存先設定までは行わない。
ループバックにポートを公開する
docker run -d --name myapp \
--network app-net \
--publish 127.0.0.1:8080:80 \
--mount type=volume,src=app-data,dst=/app/data \
--restart unless-stopped \
--memory 512m \
nginx:stable
-d はバックグラウンド実行、--name は名前、--memory はメモリ上限、unless-stopped は再起動方針。ID が出ても正常動作の証明ではない。ps / logs と以下のポート・マウントを確認する。
作成構文の例であり、nginx が自動的に /app/data に業務データを書くわけではない。実際のアプリの保存先を合わせる。行継続の逆斜線の後にコメントを置かない。本番は必要な版または digest を固定する。
図を準備しています
container 削除後に data はどこへ残るか停止は writable layer を保持する。削除は layer を失うが named volume は残る。down -v は project の非 external volume も削除し、image は残る。
{"id":"ops-docker-lifetimes","title":"container 削除後に data はどこへ残るか","summary":"停止は writable layer を保持する。削除は layer を失うが named volume は残る。down -v は project の非 external volume も削除し、image は残る。","height":600,"html":"<main><p class=\"quiet\" data-i18n=\"scope\"></p><div class=\"cards\"><section class=\"card \"><h3 data-i18n=\"container\"></h3><strong id=\"container\"></strong></section><section class=\"card \"><h3 data-i18n=\"volume\"></h3><strong id=\"volume\"></strong></section><section class=\"card \"><h3 data-i18n=\"image\"></h3><strong id=\"image\">nginx</strong></section></div><div class=\"controls\"><button type=\"button\" data-action=\"write\" data-i18n=\"write\"></button><button type=\"button\" data-action=\"stop\" data-i18n=\"stop\"></button><button type=\"button\" data-action=\"start\" data-i18n=\"start\"></button><button type=\"button\" data-action=\"remove\" data-i18n=\"remove\"></button><button type=\"button\" data-action=\"create\" data-i18n=\"create\"></button><button type=\"button\" data-action=\"down\" data-i18n=\"down\"></button><button type=\"button\" data-action=\"reset\" data-i18n=\"reset\"></button></div><div class=\"explain\"><div class=\"stable \"><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"hint\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"invalid\"></p><p id=\"message\" aria-live=\"polite\"></p></div></div></main>\n","css":"main {\n display: grid;\n gap: 18px;\n min-width: 0;\n}\np {\n overflow-wrap: anywhere;\n}\n.controls {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n}\n.presets {\n display: flex;\n flex-wrap: wrap;\n}\n.presets button {\n flex: 1 1 85px;\n font-size: 15px;\n}\n.cards {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 10px;\n}\n.card {\n border: 1px solid var(--rule);\n border-radius: 10px;\n padding: 14px;\n min-width: 0;\n background: var(--surface);\n}\n.card h3 {\n font-size: 15px;\n color: var(--muted);\n margin-bottom: 8px;\n}\n.card strong {\n font-size: 23px;\n font-weight: 500;\n overflow-wrap: anywhere;\n}\n.code {\n font-family: var(--viz-font-mono);\n font-size: 15px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.stable {\n display: grid;\n min-width: 0;\n}\n.stable > * {\n grid-area: 1/1;\n margin: 0;\n}\n.stable > .reserve {\n visibility: hidden;\n pointer-events: none;\n}\n.explain {\n border-left: 3px solid var(--accent);\n padding: 12px 14px;\n background: var(--accent-soft);\n}\n.arrow {\n text-align: center;\n color: var(--accent);\n font-size: 25px;\n}\n.row {\n padding: 12px;\n border: 1px solid var(--rule);\n border-radius: 8px;\n min-width: 0;\n}\n.row.on {\n border-color: var(--accent);\n background: var(--accent-soft);\n}\n.row.off {\n color: var(--muted);\n}\n.bar {\n height: 18px;\n background: var(--surface);\n border: 1px solid var(--rule);\n border-radius: 5px;\n overflow: hidden;\n margin-top: 8px;\n}\n.bar > i {\n display: block;\n height: 100%;\n background: var(--accent);\n width: 0;\n transition: width 0.22s;\n}\n.checks {\n display: grid;\n gap: 8px;\n}\n.checks label {\n display: flex;\n gap: 10px;\n align-items: center;\n padding: 9px;\n border: 1px solid var(--rule);\n border-radius: 7px;\n font-size: 16px;\n}\n.checks input {\n width: 18px;\n height: 18px;\n accent-color: var(--accent);\n flex-shrink: 0;\n}\nlabel.range {\n display: block;\n font-size: 16px;\n}\nlabel.range output {\n float: right;\n font-variant-numeric: tabular-nums;\n}\n.pair {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n}\n.flow {\n display: grid;\n gap: 6px;\n}\n.quiet {\n font-size: 14px;\n color: var(--muted);\n}\n.badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 5px;\n background: var(--accent-soft);\n color: var(--accent);\n}\nbutton {\n overflow-wrap: anywhere;\n}\n.large {\n font-size: 32px;\n color: var(--accent);\n font-weight: 500;\n}\n.layers {\n display: grid;\n gap: 12px;\n}\n.layer {\n border-left: 3px solid var(--rule);\n padding-left: 12px;\n}\n.layer.on {\n border-color: var(--accent);\n}\n@media (max-width: 520px) {\n .cards,\n .pair {\n grid-template-columns: 1fr;\n }\n .card {\n padding: 12px;\n }\n .card strong {\n font-size: 22px;\n }\n}\n.card strong {\n font-size: 19px;\n display: block;\n min-height: 3.2em;\n}\n","js":"const $ = (s) => document.querySelector(s);\nconst $$ = (s) => [...document.querySelectorAll(s)];\nconst t = (k) => viz.t(k);\nconst put = (id, v) => ($(\"#\" + id).textContent = v);\nconst say = (k) => put(\"message\", t(k));\nconst state = (v) => ($(\"main\").dataset.state = JSON.stringify(v));\nconst press = (key, v) =>\n $$(\"[data-\" + key + \"]\").forEach((b) =>\n b.setAttribute(\"aria-pressed\", String(b.dataset[key] === v)),\n );\nlet exists = true,\n running = true,\n layer = false,\n volume = true,\n data = false;\nfunction draw() {\n put(\n \"container\",\n (exists ? (running ? \"running\" : \"stopped\") : \"∅\") +\n \" · \" +\n t(exists ? (layer ? \"data\" : \"empty\") : \"absent\"),\n );\n put(\"volume\", t(volume ? (data ? \"data\" : \"empty\") : \"absent\"));\n state({ exists, running, layer, volume, data });\n}\n$$(\"[data-action]\").forEach(\n (b) =>\n (b.onclick = () => {\n let a = b.dataset.action,\n ok = true;\n if (a === \"write\") {\n if (running) {\n layer = true;\n data = true;\n } else ok = false;\n }\n if (a === \"stop\" && exists) running = false;\n if (a === \"start\") {\n if (exists) running = true;\n else ok = false;\n }\n if (a === \"remove\") {\n exists = false;\n running = false;\n layer = false;\n }\n if (a === \"create\") {\n if (exists) ok = false;\n else {\n exists = true;\n running = true;\n layer = false;\n volume = true;\n }\n }\n if (a === \"down\") {\n exists = false;\n running = false;\n layer = false;\n volume = false;\n data = false;\n }\n if (a === \"reset\") {\n exists = true;\n running = true;\n layer = false;\n volume = true;\n data = false;\n }\n say(ok ? \"hint\" : \"invalid\");\n draw();\n }),\n);\nsay(\"hint\");\ndraw();\n","audio":false,"strings":{"absent":"存在なし","container":"container / layer","create":"container 作成","data":"data あり","down":"down -v","empty":"空","hint":"image は program の再構築元で稼働 data は入らない。消した volume の再作成は空になる。","image":"image","invalid":"この状態では実行できない。書込は稼働、start は存在、create は不存在が前提。","remove":"container 削除","reset":"例を reset","scope":"container 一つ、非 external named volume 一つ、image 一つ。bind mount・匿名 volume・prune は省略。","start":"起動","stop":"停止","volume":"named volume app_data","write":"両方に書込"}}| 対象 | 意味 |
| 127.0.0.1:8080:80 | daemon ホストのループバック 8080 → コンテナ 80。同一ネットワークの他コンテナはサービス名:80 |
| --mount type=bind | ホストパスの対応付け。マウント先のイメージ内データは隠れる |
| --mount type=volume | Docker 管理のデータ。コンテナとは別の寿命 |
| EXPOSE | イメージのメタデータ。ホストポートを公開しない |
ポートと保存先を検証する
docker port myapp
80/tcp が 127.0.0.1:8080 に対応することを確認する。なければ作成時の指定を調べる。
docker inspect myapp --format '{{json .Mounts}}'
Type、Name、Destination、RW と実際のアプリの書き込み先を照合する。別の場所に書けば、マウントが正しくてもコンテナ層に残る。
docker inspect myapp --format '{{json .NetworkSettings.Networks}}'
コンテナ間通信は共通ネットワークと待ち受け先を確認する。DNS 設定は別途 docker exec myapp cat /etc/resolv.conf で読む。
curl -v http://127.0.0.1:8080/
接続先・HTTP 状態・本文を確認する。リモート context ではクライアントのループバックではなく daemon ホストで実行する。
実行中の設定を取得する・コンテナに入る
docker cp myapp:/etc/nginx/nginx.conf ./nginx.running.conf
コピー元はコンテナ、先はクライアントの現在のディレクトリ。未使用または保存済みの名前を使う。設定の再読込は行わない。対話診断は別途 docker exec -it myapp sh。shell を終了しても主プロセスは止まらない。
HTTP 確認の curl は daemon ホストで実行する。リモート context では手元の localhost は別物。shell のないイメージでは exec sh が失敗するため、inspect・logs・cp などを使う。
再起動だけでは新設定にならない
Compose プロジェクトを確認する
対象の compose.yaml のディレクトリで実行する。サービス名 app は config から確認し、コンテナ名 myapp とは区別する。ディレクトリや -f / -p が違うと対象も変わる。
docker compose config --quiet
通常、成功時は出力なし。ファイルや環境変数のエラーを直してから進む。
docker compose config --services
docker compose ps -a の既存インスタンスと照合し、app が更新対象か確かめる。
取得・反映・検証の順で更新する
docker compose pull app
取得失敗は認証・通信・参照を解決してから up へ進む。ソースから作るサービスは代わりに docker compose build app。pull と build は常に両方必要ではない。
docker compose up -d app
差分によって再作成され、接続が途切れる場合がある。restart だけでは環境変数などのコンテナ設定変更は反映されない。
docker compose ps -a app
docker compose logs --tail 100 app と実リクエストも確認する。失敗時はログを保存し、記録した前のイメージ・設定を戻して up する。データ移行は自動で巻き戻らない。
正しい Compose ディレクトリで実行し、必要なら -f と -p を指定する。restart は既存コンテナを再起動するだけで環境変数の変更を反映しない。up -d は必要に応じ再作成する。config 全出力には展開済み資格情報が含まれ得る。
| コマンド | 効果 |
| docker stop -t 30 myapp | 設定された停止シグナルを送り、猶予後に強制終了 |
| docker compose down | プロジェクトのコンテナ・ネットワークを削除。通常は名前付きボリュームを保持 |
| docker compose down -v | 管理対象の名前付き・付属匿名ボリュームも削除。external は対象外 |
使用量を特定してから対象を削除
使用量から対象を選ぶ
docker system df -v
RECLAIMABLE は参照関係からの推定で、業務上削除可能という意味ではない。共有レイヤーのため単純合計もできない。
docker container ls -a --filter status=exited
logs、inspect、マウント、書き込み層の必要なファイルを確認してから削除を決める。停止済みでも不要とは限らない。
docker rm myapp
書き込み層は削除され、名前付きボリュームは残る。実行中なら上表の stop と確認を先に行う。docker ps -a で消えたことを確かめる。
docker volume inspect app-data
docker ps -a --filter volume=app-data で参照も確認し、バックアップの復元を検証する。データを破棄すると決めた場合だけ docker volume rm app-data。未参照でも不要とは限らない。
イメージの削除と移送を分ける
docker image ls
レイヤーは docker image history nginx:stable で確認する。不要と判断した IMAGE を一覧から選び docker image rm IMAGE で削除する。
docker save nginx:stable -o nginx-image.tar
既存バックアップを上書きしない保存先を選ぶ。転送後、次を転送先のクライアントで実行する。
docker load -i nginx-image.tar
表示されたタグと docker image inspect nginx:stable の ID を確認する。ボリューム、実行設定、コンテナの現在の書き込み層は含まれない。
バックアップと保持要件を確認してから rm/rmi/volume rm を個別に行う。prune は参照状態で判断し、データの価値は分からない。save/load や commit はマウント済みボリュームのバックアップにならない。
docker.sock の操作権限はホストへの強い権限を与える。chmod 666 で解消しない。"iptables": false は nftables バックエンドへの切替設定ではない。
Docker storage · Compose up · Docker nftables