第12章:書き込み先の設計:/tmp・アップロード・ログ置き場を分ける🧺🗂️
「アプリが“どこにでも書ける”」状態って、便利そうで実は事故りやすいです😇💥 だからこの章では、書いていい場所を“最小の3か所”に固定して、被害半径をギュッと縮めます✂️🔒
0) 今日のゴール🎯
- ✅ アプリが書いていい場所を **3つの“カゴ”**に分けられる
- ✅
EACCES/EROFS(書けない!)が出たとき、原因が即わかる - ✅
read_only(ルートFSを読取り専用)でも動く 安全寄りテンプレが作れる
1) まず“書き込み地図”を作ろう🗺️✍️
コンテナの中の書き込み先は、基本この3つで足ります👇😊
- 一時作業:
/tmp(消えてOK)🧊 - ユーザー生成データ:アップロードなど(残したい)📦
- ログ:できれば標準出力へ(必要なら専用の置き場)🧾
そして「それ以外」は原則 書けないようにして守ります💪
Compose だと read_only: true で コンテナのファイルシステムを読取り専用にできます。(Docker Documentation)
2) “3つのカゴ”設計(おすすめパス)🧺✨
A. 一時作業カゴ:/tmp 🧊
- 画像変換の途中ファイル、解凍作業、テンポラリなど
- コンテナ再作成で消えてOKなやつ
ここは tmpfs(メモリ上の一時FS)にすると、ディスクに残りにくいし、管理もラクです😊
Compose なら tmpfs が使えて、uid/gid/mode まで指定できます。(Docker Documentation)
Docker でも tmpfs mount の仕組みが公式で説明されています。(Docker Documentation)
B. 残すデータカゴ:/data/uploads 📦
- ユーザーのアップロード
- 生成物(ユーザーが後でDLするファイル)
- 画像キャッシュ(残したいなら)
ここは named volume を使うのが定番です(環境依存が減る)🙂 Compose の volume 定義は公式リファレンスにまとまっています。(Docker Documentation)
🪟Windowsあるある注意:bind mount で Windows 側のパスを大量に触ると遅くなりがち… Docker Desktop の WSL ベストプラクティスでは、bind-mount するデータは Linux 側ファイルシステムに置くのが推奨されています。(Docker Documentation)
C. ログカゴ:まずは stdout/stderr 🧾➡️📺
基本は ファイルに書かず、console.log などで標準出力へが強いです💡
Docker の logging driver はコンテナの stdout/stderr を集める前提で動きます(例:local driver の説明)。(Docker Documentation)
どうしても「ログファイルが必要」なときだけ、/var/log/app みたいな専用置き場を volume で切ります🧾📦
(=ログの増え方でコンテナ内部がパンパンにならない)
3) 図で一発:安全な“書き込み地図”🧠✨
[イメージ(読取り専用)] ← read_only: true
├─ /app (コード・依存) ✋基本書かない
├─ /etc (設定) ✋書かない
├─ /tmp (一時) ✅ tmpfs(消えてOK)
├─ /data/uploads (ユーザーデータ) ✅ volume(残す)
└─ /var/log/app (ログ任意) ✅ volume(必要な時だけ)
4) 実装:Compose で“3つのカゴ”を作る🧩🐳
ここでは Node/TS アプリを例に、最小構成で作ります🙂✨ ポイントはこれ👇
read_only: trueで「基本書けない」を作る(Docker Documentation)tmpfs:で/tmpを用意(uid/gid も指定可)(Docker Documentation)volumes:で/data/uploadsだけ書けるようにする(Docker Documentation)
services:
app:
build: .
read_only: true
tmpfs:
- /tmp:mode=1777,uid=1000,gid=1000
volumes:
- uploads:/data/uploads
ports:
- "3000:3000"
volumes:
uploads:
mode=1777は/tmpっぽい権限(誰でも作れるけど、他人のは消せない)でよく使われます🙂🔐uid/gidは “コンテナ内で動かすユーザー” に合わせます(この章では 1000 想定)
🧷bind mount を使うなら「ro を基本」に! Docker の bind mount は
ro/readonlyオプションで 書き込み禁止にできます。(Docker Documentation)
5) 実装:Dockerfile 側で“書ける場所だけ”所有権を渡す🧤🛠️
やることはシンプル😄
書く場所だけ mkdir して chown しておきます。
## 例:Node系のイメージ想定
WORKDIR /app
## ここは「書いていい場所」だけ作る
RUN mkdir -p /data/uploads /var/log/app /tmp \
&& chown -R 1000:1000 /data/uploads /var/log/app /tmp
## 以降、非rootで動かす(第11章の流れ)
USER 1000:1000
CMD ["node", "dist/index.js"]
✅
/app配下をむやみにchownしない 「アプリがどこでも書ける」を作りやすいからです😇💥 書くなら/data/uploadsや/tmpみたいな “カゴ”の中だけに限定します🧺
6) アプリ側(TypeScript):書く場所を“定数で固定”する📌🧠
アプリが迷子になりがちな原因はだいたいコレ👇
- 適当に
./uploadsに保存してる(=どこ?)😵 - ライブラリが勝手に
/appとかにキャッシュを作ろうとする😇
だから、保存先は環境変数 or 定数で固定して、カゴに入れます📦✨
import { promises as fs } from "node:fs";
import path from "node:path";
const UPLOAD_DIR = process.env.UPLOAD_DIR ?? "/data/uploads";
const TMP_DIR = process.env.TMP_DIR ?? "/tmp";
export async function saveUpload(fileName: string, bytes: Uint8Array) {
await fs.mkdir(UPLOAD_DIR, { recursive: true });
const p = path.join(UPLOAD_DIR, fileName);
await fs.writeFile(p, bytes);
console.log("saved:", p); // ログはstdoutへ🧾
}
ログは console.log でOK(まずは stdout へ)🧾📺
Docker は stdout/stderr を logging driver で扱う前提なので、観測しやすいです。(Docker Documentation)
7) 動作確認:VS Code で“書ける/書けない”を体で覚える💻🧪
起動👇
docker compose up -d --build
中に入ってテスト👇
docker compose exec app sh
id
touch /tmp/ok.txt
touch /data/uploads/ok.txt
touch /app/ng.txt
期待する結果はこれ😊
- ✅
/tmp:作れる - ✅
/data/uploads:作れる - ❌
/app:Read-only file systemとかPermission deniedで落ちる(それでOK!)🎉
read_onlyは “アプリが変な場所に書いてないか” を一発であぶり出せます。(Docker Documentation)
8) ミニ演習(超おすすめ)🏋️♀️✨
演習1:アップロードが“残る”のを確認📦
/data/uploads/ok.txtを作るdocker compose down→up- まだ残ってるか確認✅ → named volume だから残るはず😄(volumes の考え方)(Docker Documentation)
演習2:/tmp は“消える”のを確認🧊
/tmp/ok.txtを作る- コンテナ作り直し(
down→up) - 消えるのを確認✅ → 一時作業は “消えてOK” のカゴに入れてるから安心😄 (tmpfs の考え方)(Docker Documentation)
演習3:ログを docker compose logs で追う🧾
docker compose logs -f app
stdout に出しておくと、まずこれで追えるので強いです💪 (logging driver が stdout/stderr を扱う前提)(Docker Documentation)
9) よくある詰まりポイント集😵💫🧯
① EACCES: permission denied
原因:書き込み先の owner / 権限が合ってない 対策:
- Dockerfile で
mkdir+chownを “カゴだけ” にやる🧤 tmpfsのuid/gidを実行ユーザーに合わせる(Compose のtmpfsで指定可)(Docker Documentation)
② EROFS: read-only file system
原因:/app や / 側に書こうとしてる
対策:
- 保存先を
/data/uploads//tmpに固定📌 - ライブラリのキャッシュ先も環境変数で誘導(例:
TMPDIR=/tmp)
③ Windows の bind mount が遅い🐢
原因:Windows 側のパスを Linux コンテナに bind mount して I/O 多発 対策:
- プロジェクト/データを **Linux 側(WSL のファイルシステム)**に置くのが推奨(Docker Documentation)
10) AI拡張を使うときの“事故防止”🤖🛡️
GitHub の AI や OpenAI 系ツールに設定を作らせると、たまにこういう提案が混ざります😇💣
./uploadsとか曖昧パスで保存(消える/権限で詰む)/app配下にキャッシュ/ログを書かせる(read_onlyで即死)- 便利そうに
bind mount rwを広く貼る(被害半径がデカい)
対策はシンプル👇😄
✅ 「書く場所は3か所だけ」ルールに反してないかを見る(1分レビュー)👀
✅ read_only: true を入れて、変な書き込みを早期発見する(Docker Documentation)
まとめ:第12章の“合格チェック”✅🎉
- ✅
/tmp(tmpfs):一時作業だけ - ✅
/data/uploads(volume):残すデータだけ - ✅ ログはまず stdout(必要なら
/var/log/appを volume)(Docker Documentation) - ✅ それ以外は 書けない(
read_only: true)(Docker Documentation) - ✅ Windows では bind mount する場所に注意(WSL 側推奨)(Docker Documentation)
次の第13章(read-only root filesystem を本格運用するやつ📖🔒)に行く前に、もし希望があればこの第12章の内容をベースに「Node/TSの簡易アップロードAPI(multer等)付きミニ教材プロジェクト」まで一気に形にできますよ😄✨