CTS-KB
情シス・社内IT 📚 シリーズ 4/10

Ubuntu 開発環境セットアップ — Docker ネイティブ + DevContainer + Claude Code を安全に動かす Linux 側の前提

⏱ 約 42 分で読めます
#脱Microsoft #Ubuntu #Ubuntu 26.04 LTS #Docker #Docker Engine #DevContainer #VS Code #Claude Code #Agent Teams #tmux #bypassPermissions #開発環境 #情シス

🎯 はじめに:この記事のスコープ

本記事はシリーズ 「脱Microsoft・OSS移行」の第 4 回 です。第 2 回 Windows 11 + Ubuntu デュアルブート構築で OS の土台、第 3 回 Ubuntu PC セットアップ で NVIDIA ドライバ・サスペンド無効化・周辺セットアップまで完了している前提です。本記事では、その上に 開発スタック を積み上げて、「数時間放置できる開発体験」 が成立するところまでを実録ベースで解説します。

範囲含む含まない
本記事Docker Engine ネイティブ / VS Code / DevContainer / Claude Code / tmux / Agent Teams / bypassPermissions 安全運用ハードウェア・OS インストール(第 2 回)/ NVIDIA・サスペンド・周辺セットアップ(第 3 回)/ Microsoft 365 移行(第 6 回以降)
前提Ubuntu 26.04 LTS + KDE Plasma + NVIDIA ドライバ導入済み(第 3 回完了)

💡 本記事の到達点: Ubuntu 端末を 1 つ開けば、docker exec → tmux → claude の 3 ステップで Agent Teams が立ち上がり、bypassPermissions の確認クリック地獄なく数時間放置できる開発環境が完成します。

🧱 全体スタック:6 層の積み上げ

開発スタックは下から 6 層に整理できます。L4(DevContainer 隔離)が安全装置として効くことが本記事の最大のポイントです。

Ubuntu 26.04 LTS 開発スタック層構造図(L0 ハードウェアから L5 Claude Code 実行環境まで、ネイティブ Docker と DevContainer 隔離による bypassPermissions 安全運用)

内容この記事で扱う節
L0ハードウェア(M.2 SSD x2 物理分離 / NVIDIA GPU)前回
L1Linux カーネル + Ubuntu 26.04 LTS + KDE Plasma前回
L2ホスト直アプリ(VS Code / Chrome / Git / Node.js / 日本語入力)§5-2, §5-3
L3Docker Engine(ネイティブ)§5-1
L4DevContainer(隔離レイヤー)§5-3
L5Claude Code + tmux + Agent Teams§5-4, §5-5

🐳 Phase 5-1: Docker Engine をネイティブ導入する

最初に入れるのは Docker Engine です。Windows + WSL2 + Docker Desktop の 二重仮想化(Hyper-V → WSL2 → Docker) が消えるため、メモリと I/O の両方が大幅に改善します。

Engine と Desktop の違いを再確認

項目Docker Engine(Linux)Docker Desktop(Windows / macOS)
仮想化レイヤーカーネル直結Hyper-V → WSL2 → Docker(二重)
メモリ消費軽量(ベース 200〜500 MB)30 GB+ ベース消費(実機計測)
I/O パフォーマンスネイティブ速度ファイル共有越しで遅延
ライセンスOSS(Apache 2.0)完全無料大企業は有料(従業員 250 名超)

💡 「Linux 上の Docker は Docker ではない、ただの Linux カーネル機能」 — Docker Engine は cgroups と namespaces のフロントエンドに過ぎません。Linux 上では仮想化レイヤーがゼロなので、「Docker を入れる」というより「Docker コマンドを叩けるようにする」 に近い感覚です。

インストール手順(公式 apt リポジトリ)

Ubuntu の apt install docker.io ではなく、Docker 公式 apt リポジトリを使います。最新版が即座に手に入り、docker compose v2 もまとめて入ります。

# 1. 古いバージョンの削除(クリーン導入)
sudo apt remove -y docker docker-engine docker.io containerd runc 2>/dev/null

# 2. 公式 GPG キーと apt リポジトリを追加
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 3. Docker Engine + Compose plugin を導入
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

sudo なしで docker を叩けるようにする

毎回 sudo docker は煩雑なので、自分のユーザーを docker グループに入れます。

sudo usermod -aG docker $USER
# ログアウト → ログインし直して反映
docker run hello-world

⚠️ docker グループは実質 root 権限です。コンテナ経由で / をマウントすればホストのファイルが何でも読み書きできるため、信頼できないユーザーには付与しないこと。個人開発機では問題なし。

systemd で自動起動

sudo systemctl enable docker
sudo systemctl start docker
sudo systemctl status docker  # active (running) を確認

Ubuntu の systemd 上では、Docker Desktop のような GUI クライアントは不要です。docker コマンドと VS Code の Dev Containers 拡張だけで完結します。

💡 Docker Desktop for Linux は基本不要。 Linux 上では Docker Engine が直接動くため、Desktop の出番はほぼありません。GUI でコンテナを眺めたい場合は Lazydocker のような軽量 TUI を入れる方が筋が良いです。

🛠️ Phase 5-2: 開発常備ツールをまとめて導入

次は開発で毎日使うツール群です。Ubuntu の apt で入る最新 LTS を中心に揃えます。

Git / GitHub CLI / 基本ユーティリティ

sudo apt install -y git curl wget jq tree htop unzip build-essential
gh --version 2>/dev/null || {
  # GitHub CLI(公式リポジトリ)
  curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
    sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
  echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \
    https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list
  sudo apt update && sudo apt install -y gh
}
git config --global user.name "your-name"
git config --global user.email "your@email"

Node.js / pnpm(Volta 経由)

apt 版の Node.js はバージョンが古く、複数バージョン併用もできないため Volta で入れます。nvm より速く、Rust 製で安定。

curl https://get.volta.sh | bash
# .bashrc / .zshrc を再読み込みしてから
volta install node@lts
volta install pnpm
node -v && pnpm -v

VS Code(公式 deb)

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | \
  gpg --dearmor | sudo tee /usr/share/keyrings/packages.microsoft.gpg > /dev/null
echo "deb [arch=amd64,arm64,armhf signed-by=/usr/share/keyrings/packages.microsoft.gpg] \
  https://packages.microsoft.com/repos/code stable main" | \
  sudo tee /etc/apt/sources.list.d/vscode.list > /dev/null
sudo apt update && sudo apt install -y code

💡 VS Code は脱 Microsoft の例外。 VS Code 自体は MIT ライセンスのオープンソース(コア部分)で、Microsoft の認証が必須なのは Marketplace 拡張のみ。.NET MAUI 公式拡張も VS Code で動くため、Visual Studio を捨てる代わりに VS Code は残すのが現実解です。気になる場合は VSCodium(テレメトリ無効版)も選択肢。

Chrome(Google 公式 deb)

Wayland セッションでも安定動作するのは Google Chrome です。Chromium 系の Edge / Brave も同様に動きます。

wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install -y ./google-chrome-stable_current_amd64.deb

日本語入力(Mozc + fcitx5)

Ubuntu 26.04 + KDE Plasma で安定する組み合わせは fcitx5 + Mozc です。

sudo apt install -y fcitx5 fcitx5-mozc fcitx5-config-qt
im-config -n fcitx5
# ログアウト → ログインで有効化

KDE Plasma の System Settings → Input Method で fcitx5 を選択し、Ctrl + Space で日英切替が標準動作です。

⚠️ iBus は推奨しません。 Wayland + KDE Plasma 6 系では fcitx5 が最も実績豊富です。iBus を入れると VS Code / IntelliJ 系で日本語入力が崩れる事例が残っています。

Ctrl + Space 切替キーの確実な設定(HHKB US 配列など半角/全角キーがない環境)

開発者がよく使う HHKB US 配列 は、半角/全角キーや macOS の英かなキーが物理的に存在しません。fcitx5 のデフォルトトリガーは Ctrl+Space なので多くの場合追加設定なしで動きますが、ディストロや KDE Plasma の組み合わせで別バインド(Zenkaku_Hankaku 等、US 配列に存在しないキー)になっている場合があります。確実に Ctrl + Space で切り替えられるよう設定しておきます。

方法 A: GUI(最短)
fcitx5-configtool
  1. Global Options タブ
  2. Trigger Input Method をクリック → 既存バインドを削除 → Ctrl+space を割り当て
  3. (任意)Enumerate Input MethodCtrl+space / BackwardCtrl+Shift+space
  4. Apply
方法 B: 設定ファイル直接編集(dotfile 管理派向け)
mkdir -p ~/.config/fcitx5
cat > ~/.config/fcitx5/config << 'EOF'
[Hotkey]
TriggerKeys=Control+space
EnumerateForwardKeys=Control+space
EnumerateBackwardKeys=Control+Shift+space
EOF

# 反映
fcitx5 -r
動作確認
# キーバインド確認
grep -A 3 "\[Hotkey\]" ~/.config/fcitx5/config
# → TriggerKeys=Control+space が出ていれば設定 OK

# fcitx5 プロセス確認
pgrep -af fcitx5

ターミナル / VS Code / Chrome 等で Ctrl + Space を押して、タスクトレイの fcitx5 インジケータが「あ」⇔「A」で切り替われば成功。Mozc 自体の変換キー設定(変換 / 無変換 / Henkan モード等)は fcitx5-mozc-tool で別途調整できます(「ことえり風」「ATOK 風」「MS-IME 風」のプリセットあり)。

💡 macOS / Windows と切替キーを揃える価値 — 業務開発で複数 OS を行き来する場合、Ctrl + Space で統一すると 筋肉記憶のコンフリクトがゼロになります。HHKB US ユーザーなら必須、JIS 配列ユーザーでも揃えておくのが楽。

ターミナルの選定(Ptyxis → Terminator / Konsole)

第 3 回 Ubuntu PC セットアップ でターミナル選定の指針には触れましたが、本記事の主題である docker exec → tmux → claude の起動経路で安定動作させる観点から、Terminator の最小設定だけはこの記事でも整理しておきます(VS Code 統合ターミナルから tmux 起動は公式に非対応のため、ホスト OS のターミナルアプリは独立して整える必要があります。詳細は §5-5)。

sudo apt install -y terminator

設定 → Profiles → General で 3 項目:

設定推奨効果
PuTTY style pasteON中クリック / 右クリックで PRIMARY からペースト
Copy on selectionONマウス選択で自動コピー
Background → Transparentお好みでWayland でも安定動作

⚠️ PuTTY style paste を ON にすると、右クリックがペースト動作に変わるため、Terminator 本来の「右クリック → コンテキストメニュー(設定 / プロファイル切替 / 分割 / プロファイルの編集 …)」が呼べなくなります。 回避策は F10 キーでコンテキストメニュー表示。設定画面に入れずハマる前に覚えておくべき一手です。

⚠️ tmux / vim の中では右クリックが奪われる — tmux や vim はマウスイベントを自分で処理するため、PuTTY style paste の右クリックペーストが Terminator まで届きません。Shift+右クリック(Shift 押下中はマウスイベントが Terminator にバイパスされる)または Shift+Ctrl+V でペーストします。Agent Teams 運用では tmux 上が常時のため、Shift+Ctrl+V を基本動作に固定するのが筋。bash / tmux / vim / SSH 先すべてで動作が揃います。

Agent Teams 運用で使い倒す Terminator ショートカット

docker exec → tmux → claude の起動経路を複数本走らせるため、タブ追加と分割は手に馴染ませる価値があります。

操作ショートカット使い所
新規タブShift+Ctrl+T別 DevContainer / 別プロジェクトに入る時に頻発
縦分割(左右)Shift+Ctrl+Etmux split-pane の代わりにホスト側で分けたい時
横分割(上下)Shift+Ctrl+Oログ tail と CLI を縦に並べる
現在のターミナルを閉じるShift+Ctrl+W分割を畳む
タブ間移動Ctrl+PageUp / Ctrl+PageDownAgent Teams 監視用と編集用を切替
分割ペイン間移動Shift+Ctrl+矢印ペインフォーカス移動
全画面切替F11プレゼン / レビュー時
コンテキストメニュー(PuTTY paste ON 時)F10設定 / プロファイル切替
ペーストShift+Ctrl+Vtmux / vim / SSH 越し全環境共通(右クリックペーストが効かない場面の基本動作)
検索Shift+Ctrl+Fログから特定文字列を探す

💡 Shift+Ctrl+T で新規タブ → docker exec → tmux a -t claude で既存セッションにアタッチ、というフローを覚えると、Agent Teams を放置しているセッションへの再接続が一瞬で済みます。

複数テーマを切り替えたい場合は GoghTokyo Night(CTS で実運用中・推奨)/ Catppuccin Mocha などを一括導入:

sudo apt install -y dconf-cli uuid-runtime
bash -c "$(wget -qO- https://git.io/vQgMr)"
# プロンプトで番号を空白区切り指定(例: 93 97 265)→ プロファイル名を分けて保存
テーマ背景色印象推奨度
Tokyo Night#1A1B26 紺紫クール・目に優しい(彩度ひかえめ + 適度なコントラスト)◎ 業務開発で実運用中
Catppuccin Mocha#1E1E2E 暗紫柔らかいパステル
Dracula#282A36 暗紫コントラスト強め・華やか
Gruvbox#282828 暗茶暖色レトロ
Campbell#0C0C0CWindows Terminal 標準○ Windows 出身者

Tokyo Night を選ぶ実利は **「数時間 Agent Teams を監視し続けても目が疲れない」**こと。彩度を抑えた紺紫背景に、Claude Code の出力(緑 / シアン / 黄系)が眩しすぎないコントラストで乗ります。Dracula のような高コントラスト系は最初は見やすく感じても、放置運用で長時間眺める用途では負荷が大きくなりがちです。

ターミナル画面で 右クリック → プロファイル → [テーマ名](PuTTY style paste ON 時は F10)で即時切替できます。Agent Teams の split-pane を見続ける開発では、暗背景 + Claude のシンタックスハイライトの相性が目の疲れに直結するため、最初に固めておく価値があります。

💡 PRIMARY と CLIPBOARD の使い分け — Linux X11 / XWayland には 2 つのクリップボードがあり、マウス選択で PRIMARY、Ctrl+C で CLIPBOARD に入ります。Ctrl+Shift+V は CLIPBOARD からのペーストで、bash / tmux / vim / SSH 先のすべてで共通動作。Smart copy ON で両方に同時コピーされるため、Windows 出身者は迷わずこれを設定しておくと混乱が消えます。

公式 apt 未対応ツールの回避策パターン

Ubuntu LTS のリリース直後(2026 年 4 月の 26.04 など)は、サードパーティツールの公式 apt リポジトリ対応が数週間〜数か月遅れることがあります。CTS 実機で動作確認した回避策を、優先度順に整理します。

回避策の早見表

#パターン代表例利点欠点
1公式インストールスクリプトcurl ... | bashrclone / Claude Code / Volta / uv最新版が即手に入る、署名込みスクリプトの安全性は自己確認
2Docker 公式イメージで動かすpgAdmin4 / Portainer隔離・撤退容易、永続化容易コンテナ運用の基礎が必要
3AppImage + AppArmor + Gear LeverObsidian / Cursor / Insomnia / Postmanフル GUI、Electron 系標準第 5 回の AppArmor 設定必須
4Snap / Flatpak(公式配信あれば)code (snap) / gear lever (Flatpak)標準パッケージシステムサンドボックス制約あり
5ソースビルドtmux 3.5+(§5-4 参照)最新版確実ビルド依存解決の手間

実例 1: pgAdmin4 を Docker で動かす(apt 26.04 未対応の代表例)

pgAdmin4 の公式 apt は 2026-05 時点で Ubuntu 26.04(resolute)に未対応です(PostgreSQL 本体の apt は対応済みだが、pgAdmin4 は別リポジトリ管理)。Docker での運用が現実解:

# .docker/pgadmin/docker-compose.yml
services:
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PW}
      PGADMIN_CONFIG_SERVER_MODE: "False"
    ports:
      - "5050:80"
    volumes:
      - pgadmin-data:/var/lib/pgadmin
    network_mode: host
    restart: unless-stopped

volumes:
  pgadmin-data:
PGADMIN_PW=<your-password> docker compose -f .docker/pgadmin/docker-compose.yml up -d
# ブラウザで http://localhost:5050
# Server を新規登録: Host=localhost / Port=5432 / User=<your-pg-user>
設計ポイント理由
network_mode: hostホスト側 docker-compose の PostgreSQL に localhost:5432 で直接接続(§5-3 の TEST_POSTGRESQL_CONNECTION_STRING と同じ筋)
volumes: pgadmin-dataサーバー登録情報を永続化(コンテナ再作成しても接続が消えない)
${PGADMIN_PW}パスワードは .env で外出し、.gitignore でリポジトリ除外
PGADMIN_CONFIG_SERVER_MODE: "False"ローカル運用は False で初回ログイン簡略化

実例 2: 公式スクリプトで導入(rclone / Claude Code / Volta / uv)

本シリーズで扱う以下のツールは、公式 apt ではなく公式スクリプトを直接叩く運用です:

ツール取得方法扱う章
rclonecurl https://rclone.org/install.sh | sudo bash第 5 回 §5-1
Claude Codecurl https://claude.ai/install.sh | bash第 4 回 §5-4
Volta(Node.js バージョン管理)curl https://get.volta.sh | bash第 4 回 §5-2
uv(Python パッケージ)curl https://astral.sh/uv/install.sh | sh第 4 回 §5-4

⚠️ curl ... \| bash の安全性確認 — サードパーティ提供のスクリプトを実行する以上、信頼できるドメイン(公式)からの HTTPS 取得であることを必ず確認。組織ポリシーで禁止されている場合は、curl > install.sh でダウンロード → cat install.sh でレビュー → bash install.sh の 3 ステップに分けます。

実例 3: AppImage + AppArmor(GUI アプリ向け)

Obsidian / Cursor / Insomnia / Postman / VS Code AppImage 配布版などの Electron 系は AppImage で配布されており、Ubuntu 24.04+ 特有の起動エラー(libfuse2t64apparmor_restrict_unprivileged_userns)への対処が必要です。詳細は **第 5 回「Phase 5-3: Obsidian を AppArmor で安全に動かす」**で扱っています(同じパターンが他の Electron 系にも横展開可能)。

公式 apt 対応後への戻し方

新規 LTS 用の apt リポジトリが公開されたら、Docker / スクリプト導入から apt に戻すことも可能:

# 戻す例(pgAdmin4 が将来 resolute 対応した場合)
docker compose -f .docker/pgadmin/docker-compose.yml down -v
sudo apt install -y pgadmin4-desktop

💡 業務開発機では「動くから変えない」も合理的 — Docker 運用が確立している場合、apt に戻す利点は限定的です。バージョン固定・複数同居・撤退容易という Docker の強みを失う側面もあります。新規 LTS リリース直後の数か月は Docker 運用、安定期に入ってから判断、というスタンスが現実的。

Windows との ZIP ファイル交換 — 7-Zip 公式版(7zz)に統一する

Linux ホストから Windows ユーザーへ ZIP を渡す場面で、Kubuntu 標準の ArkDolphin の右クリック「圧縮」)が作る ZIP には 2 種類の互換性問題があります。

問題 1: 日本語ファイル名の文字化け

Linux 側は UTF-8 でファイル名を書き込むのに対し、Windows のエクスプローラ標準解凍は Shift_JIS(CP932) を期待するため、日本語ファイル名が文字化けします。Windows 10 1809 以降の解凍 UI は UTF-8 を解釈できる経路もありますが、ZIP ヘッダの UTF-8 フラグ(汎用目的ビット 11)が立っていないと CP932 にフォールバックするため、Ark が出力する ZIP では症状が再発しがちです。

問題 2: パスワード付き ZIP の互換性エラー

Ark がデフォルトで使う暗号化方式(ZipCrypto / AES の組み合わせ)が、Windows 標準解凍機能の対応範囲と噛み合わないことがあり、「ファイルが破損しています」「展開できません」といった汎用エラーで開けない事例が出ます。

解決策: 7-Zip 公式 Linux 版(7zz)を使う

p7zip 系(7za / 7zr / 7zp)は 古い移植版で開発が止まっているため、現行運用では 公式 7-Zip の Linux ビルド 7zz を使うのが正解です。

コマンドパッケージ状態
7za / 7zpp7zip-full(古い移植版)開発終了
7zrp7zip(古い移植版・最小版)開発終了
7zz7zip-standalone(公式の最新版)これを使う

インストール(Ubuntu 26.04):

sudo apt install -y 7zip-standalone

UTF-8 + パスワード付き ZIP の作成:

# -tzip: ZIP 形式、-p: パスワード入力、-scsUTF-8: ファイル名を UTF-8 で書き込む
7zz a -tzip -p -scsUTF-8 archive.zip /path/to/files

エイリアスを ~/.bashrc に登録すると日常運用が楽になります:

echo 'alias zipenc="7zz a -tzip -p -scsUTF-8"' >> ~/.bashrc
source ~/.bashrc

# 使い方
zipenc archive.zip file1.txt file2.txt
zipenc archive.zip /path/to/directory/

💡 根本回避策: 日本語ファイル名を避ける — 開発プロジェクトの成果物(ソース・ビルド出力)は 英数字ファイル名で統一するのが最も確実です。ドキュメント類でどうしても日本語ファイル名が必要な場合に 7zz を使います。

🧰 Phase 5-2.5: ホスト側カーネルチューニング(ファイル監視 / FD 上限)

WSL2 / macOS / DevContainer 内では問題にならないのに、Ubuntu ネイティブだけで踏むハマりがあります。inotify のウォッチャー数とファイルディスクリプタ上限です。中規模リポジトリ(モノレポや Astro / Next.js のフルスタック)を VS Code で開くと、SCM パネルにファイル変更が反映されない・保存しても自動再起動が動かない、という症状が出ます。

既知の症状

VS Code 起動時 / リポジトリオープン時に:

Unable to watch for file changes in this large workspace folder.
Please follow the instructions link to resolve this issue.

Ctrl+Shift+P → "Developer: Show Logs..." → “Main” で:

unable to add filesystem watcher: ENOSPC: System limit for number of file watchers reached

なぜ Mac / Windows / DevContainer では起きないのか

環境ファイル監視機構デフォルト上限
macOSFSEvents / kqueueリポジトリ単位で監視(実質無制限)
WindowsReadDirectoryChangesWリポジトリ単位(実質無制限)
Linux ネイティブinotify(ファイル単位)fs.inotify.max_user_watches=8192(古いデフォルト)/ 数万
Dev Container 内inotify(ホスト共有)コンテナ起動時に --ulimit nofile で別途設定済

Linux の inotify は ファイル単位で監視枠を消費するため、node_modules.git 配下を含む大規模リポジトリですぐに上限に当たります。

対処 1: ファイルディスクリプタ(fd)上限の引き上げ

/etc/systemd/system.conf/etc/systemd/user.conf

sudo bash -c 'cat >> /etc/systemd/system.conf << EOF

DefaultLimitNOFILE=524288
EOF'

sudo bash -c 'cat >> /etc/systemd/user.conf << EOF

DefaultLimitNOFILE=524288
EOF'

シェル側 /etc/security/limits.conf

sudo bash -c 'cat >> /etc/security/limits.conf << EOF

*       soft    nofile  524288
*       hard    nofile  524288
EOF'

確認:

grep DefaultLimitNOFILE /etc/systemd/system.conf /etc/systemd/user.conf
# → 両方が DefaultLimitNOFILE=524288

対処 2: inotify 上限の引き上げ

⚠️ /etc/sysctl.conf に書いても再起動でデフォルトに戻る罠(Ubuntu 26.04)

Web 上の多くの解説記事が /etc/sysctl.conf への追記を案内していますが、Ubuntu 26.04 では sysctl.conf は後方互換のためだけに残されており、起動時の読み込み対象から外れています。手動で sudo sysctl -p した直後は値が反映されているように見えるため気づきにくく、再起動するとデフォルト値(max_user_watches=65536 / max_user_instances=128)に戻り File Watcher 警告が再発します。/etc/sysctl.d/ 配下に専用ファイルを作成するのが確実です。

コマンド読み込み対象
sudo sysctl -p/etc/sysctl.conf のみ
sudo sysctl --system/etc/sysctl.conf/etc/sysctl.d/*.conf/run/sysctl.d/*.conf/usr/lib/sysctl.d/*.confすべて再読込

/etc/sysctl.d/99-inotify.conf

# 99- プレフィックスで読み込み順を最後にする
sudo tee /etc/sysctl.d/99-inotify.conf > /dev/null << 'EOF'
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024
EOF

# 即時反映(/etc/sysctl.d/ も読むため --system が必須)
sudo sysctl --system

確認:

sysctl fs.inotify.max_user_watches    # 524288
sysctl fs.inotify.max_user_instances  # 1024

💡 watches と instances の違い: watches は「監視できるファイル / ディレクトリの総数」、instances は「監視を作成できるプロセス数」。VS Code は内部で複数の inotify インスタンスを使うため両方の上限が必要で、Your open applications want to watch too many files for changes! の警告は instances が枯渇した時に出ます。

反映の手順

limits.confsystemdDefaultLimitNOFILEログインセッション全体に効くため、一度ログアウト → 再ログインするか、再起動するのが確実です。

# シェルの fd 上限(再ログイン後)
ulimit -n
# → 524288

# systemd ユニットの fd 上限
systemctl show --property DefaultLimitNOFILE
# → DefaultLimitNOFILE=524288

⚠️ 再起動後の inotify 値の確認は必須/etc/sysctl.conf 罠の検知手段)

上記の罠(sysctl.conf だけに書いて再起動でデフォルトに戻る)を踏んでいないかは、再起動後に以下で確認できます。1024 / 524288 のままなら OK、128 / 65536 に戻っていたら /etc/sysctl.d/99-inotify.conf の作成に失敗しています。

cat /proc/sys/fs/inotify/max_user_instances    # 1024 になっているか
cat /proc/sys/fs/inotify/max_user_watches      # 524288 になっているか

トラブルシューティング

設定変更後も VS Code でエラーが出続ける場合:

# VS Code を完全終了してから
pkill -f "code"
# 再ログイン後に VS Code を再起動

それでも改善しなければ、code --status で起動プロセスの fd 上限を確認:

code --status | grep -i "open files"

💡 524288 は妥当か — Chromium / Electron 系のドキュメント推奨値で、Mac / Windows のデフォルト挙動とほぼ同等の余裕を確保できます。RAM 64 GB 機なら問題なし。低スペック機でメモリ消費が気になる場合でも 262144 程度までは下げて構いません。

🪟 Phase 5-3: VS Code + DevContainer 拡張

ホストに VS Code、リポジトリ側に .devcontainer/ 一式を置く構成にします。ホスト OS の認証情報・SSH 鍵をどうコンテナに持ち込むか、そして DevContainer Features で言語ランタイムを宣言的に管理するのが、Linux 移行で押さえる勘所です。

必須拡張

拡張役割
Dev Containersms-vscode-remote.remote-containersDevContainer の起動・接続
Dockerms-azuretools.vscode-dockerコンテナ・イメージの可視化
GitLens / Git GraphGit 履歴の可視化

.devcontainer/ の標準構成

最小構成は次の 3 ファイルです。

.devcontainer/
├── devcontainer.json        # Features・mounts・customizations
├── docker-compose.yml       # サービス定義(DB やキャッシュも一緒に立てる)
└── post-create.sh           # コンテナ生成後に走る初期化スクリプト

devcontainer.json の典型構成(CTS で実運用している構成をベースに汎化):

{
  "name": "cts-ec",
  "dockerComposeFile": "docker-compose.yml",
  "service": "devcontainer",
  "workspaceFolder": "/workspace/cts-ec",

  // 言語ランタイムは Features で宣言的に
  "features": {
    "ghcr.io/devcontainers/features/dotnet:2": { "version": "10.0" },
    "ghcr.io/devcontainers/features/node:1":   { "version": "22" },
    "ghcr.io/devcontainers/features/python:1": { "version": "3.12" }
    // ⚠ docker-outside-of-docker は Ubuntu 26.04 (resolute) 未対応のため使わない(§次節)
    // Docker CLI は post-create.sh で `get.docker.com` から手動導入する
  },

  // ホスト OS から持ち込む認証情報・設定
  "mounts": [
    "source=${localEnv:HOME}/.aws,target=/home/vscode/.aws,type=bind,readonly",
    "source=${localEnv:HOME}/.config/gcloud,target=/home/vscode/.config/gcloud,type=bind,readonly",
    "source=${localEnv:HOME}/.config/glab-cli,target=/home/vscode/.config/glab-cli,type=bind,readonly",
    "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly",
    "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,readonly",
    "source=${localEnv:HOME}/.microsoft/usersecrets,target=/home/vscode/.microsoft/usersecrets,type=bind,readonly",
    "source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind"
  ],

  "postCreateCommand": "bash .devcontainer/post-create.sh",

  "containerEnv": {
    "DOTNET_CLI_TELEMETRY_OPTOUT": "1",
    "ASPNETCORE_ENVIRONMENT": "Development",
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  },

  "customizations": {
    "vscode": {
      "extensions": [
        "ms-dotnettools.csdevkit",
        "ms-python.python",
        "charliermarsh.ruff",
        "ms-azuretools.vscode-docker",
        "googlecloudtools.cloudcode",
        "gitlab.gitlab-workflow",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode"
      ],
      "settings": {
        "claudeCode.env": {
          "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
        },
        "claudeCode.teammateMode": "auto"
      }
    }
  }
}

ポイントは 4 つ:

観点内容
features で言語ランタイム導入dotnet / node / python は Microsoft 公式の DevContainer Features を使うと、Dockerfile を書かずに宣言的に揃う
Docker CLI は post-create.sh で導入(§次節)docker-outside-of-docker Feature は Ubuntu 26.04 (resolute) 未対応docker-compose.yml/var/run/docker.sock を bind mount しつつ、CLI 本体は post-create.sh で get.docker.com から入れる
containerEnvclaudeCode.env二重指定VS Code から起動した Claude Code は後者、ホスト端末経由(docker exec → tmux → claude)で起動した場合は前者が効く。両方に書くのが安全
claudeCode.teammateMode: "auto"tmux 内なら split-pane、外なら in-process に自動切替(公式推奨)

⚠️ DevContainer リビルド時の docker-outside-of-docker 未対応エラー(Ubuntu 26.04 固有)

Docker Engine のアップデート等でイメージキャッシュが無効化されると DevContainer のリビルドが走ります。このときに ghcr.io/devcontainers/features/docker-outside-of-docker:1 を含めていると、Ubuntu 26.04 (resolute) を未サポートとして弾かれて Dev Container 自体が起動しなくなる罠があります。

実機で踏んだエラー:

(!) Unsupported distribution version 'resolute'. To resolve, please choose a compatible OS distribution
(!) Supported distributions include:  trixie bookworm buster bullseye bionic focal hirsute impish jammy noble plucky

Feature のシェルスクリプト内部で /etc/os-releaseVERSION_CODENAME を見てディストリビューションをハードコードチェックしているため、"moby": false"version": "none" などのオプション指定では回避できません。Feature 側が Ubuntu 26.04 を取り込むまで待つしかない構造です。

回避策:Feature を外し、post-create.sh で Docker CLI を手動インストール

// devcontainer.json から削除
"features": {
  "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}  // ❌ 削除
}

.devcontainer/post-create.sh に以下を追加(§5-4 で紹介する post-create.sh の冒頭付近が定位置):

# Docker CLI(docker-outside-of-docker Feature が resolute 未対応のため手動導入)
if ! command -v docker &> /dev/null; then
    curl -fsSL https://get.docker.com | sh
fi

Docker CLI 本体は get.docker.com の公式スクリプトで導入し、ホストの Docker daemon は docker-compose.yml/var/run/docker.sock を bind mount することでそのまま使えます。Feature が担っていた「コンテナ内から docker compose / docker run」は機能維持されます。

💡 早く気付くための予兆:Docker Engine の apt upgrade が走った日や、docker system prune を実行した日に「Reopen in Container」が落ちる場合、ほぼこの罠です。エラー画面に Unsupported distribution version 'resolute' が出ていればビンゴ。Feature を外すパッチが当たるまでこの回避策を維持してください。

ホスト → コンテナへ持ち込む認証情報・設定

DevContainer は隔離された環境ですが、開発に必要な認証情報まで作り直しになると現実的ではありません。実体はホスト側に置いたまま、コンテナには bind mount で見せるのが標準パターンです。

ホスト側パスコンテナ側mode用途
~/.aws/home/vscode/.awsbind, readonlyAWS CLI / SDK の credentials
~/.config/gcloud/home/vscode/.config/gcloudbind, readonlygcloud CLI / Application Default Credentials
~/.config/glab-cli/home/vscode/.config/glab-clibind, readonlyGitLab CLI(glab)の認証
~/.gitconfig/home/vscode/.gitconfigbind, readonlyGit の user.name / user.email / 署名鍵
~/.ssh/home/vscode/.sshbind, readonlyGitLab / GitHub / 本番 SSH
~/.microsoft/usersecrets同左bind, readonly.NET User Secrets(開発用シークレット)
~/.claude/home/vscode/.claudebind(書き込み可)Claude Code 設定・OAuth トークン(ホストと共有して再認証回避

💡 認証系はすべて readonly~/.claude だけ書き込み可。 AWS / GCP / glab / Git / SSH / .NET User Secrets は誤書き換え事故を防ぐため readonly で固定します。Claude Code はセッション情報をコンテナ内で更新するため bind mount を書き込み可にし、ホスト側 ~/.claude と直接共有します(VS Code 経由とホスト端末経由でセッションが揃う副次効果あり)。

SSH agent socket forwarding(鍵をコンテナに見せたくない場合)

~/.ssh を bind mount するのが一番シンプルですが、秘密鍵をコンテナに見せたくない運用なら、ホスト OS の ssh-agent を共有する方法もあります。

"mounts": [
  "source=${localEnv:SSH_AUTH_SOCK},target=/ssh-agent,type=bind"
],
"containerEnv": {
  "SSH_AUTH_SOCK": "/ssh-agent"
}

これでコンテナ内の git push がホスト agent 経由で署名され、秘密鍵ファイル自体はコンテナに見えません。

docker-compose.yml の最小例

services:
  devcontainer:
    image: mcr.microsoft.com/devcontainers/base:ubuntu
    volumes:
      - ..:/workspace/cts-ec:cached
      - /var/run/docker.sock:/var/run/docker.sock
    command: sleep infinity
    network_mode: host
設定役割
mcr.microsoft.com/devcontainers/base:ubuntuMicrosoft 公式の Ubuntu ベースイメージ。Features が前提とする vscode ユーザーが最初から作られている
/var/run/docker.sock の bind mountコンテナ内から ホストの Docker daemon を叩く経路。CLI は post-create.sh で get.docker.com から入れる(§前節の resolute 回避策)
network_mode: hostlocalhost:5432 などホスト側のサービスにそのままアクセスできる。Linux ホスト前提(macOS / Windows では別経路)

実例:network_mode: host 前提のテスト接続文字列

network_mode: host を採用する DevContainer + ホスト側 docker-compose の PostgreSQL という構成では、テスト側の接続文字列を 明示的に DevContainer に注入する必要があります。Testcontainers がデフォルトでコンテナを新規起動しようとして失敗するパターンです。

実機(CTS-EC リポジトリ・8682 件のテスト)で起きた症状:

Test summary: total: 8682, failed: 979, succeeded: 7674, skipped: 29

979 件すべて Failed to connect to 127.0.0.1:5432。原因は PostgreSqlFixture.csTEST_POSTGRESQL_CONNECTION_STRING 未設定時に Testcontainers でコンテナ起動を試みる設計だったこと。docker-outside-of-docker Feature 経由の Docker 操作と相性が悪く失敗します。

恒久対応として devcontainer.jsonremoteEnv に環境変数を注入:

"remoteEnv": {
  "TEST_POSTGRESQL_CONNECTION_STRING": "Host=localhost;Port=5432;Username=ctsec;Password=devpassword;Database=cts_ec"
}

これで Testcontainers ではなく ホスト側 docker-compose で起動済みの PostgreSQL に直接接続するようになります。修正後の実機計測:

Test summary: total: 8682, failed: 1, succeeded: 8652, skipped: 29

979 → 1 に改善。残り 1 件は DI 解決テストで DB とは無関係(環境非依存の問題なので別軸で対応)。

💡 network_mode: host を採用したら必ず remoteEnv に接続文字列を書く。DevContainer の Testcontainers 系テストフィクスチャは「コンテナ環境では Testcontainers でコンテナ起動」をデフォルト挙動にしているライブラリが多いため、明示的に「ホスト側 DB を使え」と指示する必要があります。

DevContainer 起動と動作確認

cd ~/Projects/cts-ec
code .
# VS Code 内で Cmd/Ctrl+Shift+P → "Dev Containers: Reopen in Container"

または別端末から直接:

docker compose -f .devcontainer/docker-compose.yml up -d

起動後、コンテナ内で認証情報が見えるか確認:

# DevContainer 内
ls -la ~/.aws ~/.config/gcloud ~/.gitconfig ~/.claude
git config user.email
aws sts get-caller-identity 2>/dev/null || echo "AWS 未認証"
gcloud auth list 2>/dev/null || echo "GCP 未認証"

すべて通れば bind mount は成功です。ls -la の結果が ??? ??? のように壊れていたら、ホスト側のパスが存在しないか、(Google Drive 上に置く運用などで)ホストのファイルシステムから直接読めない場所を指している可能性があります。その場合は実体ファイルをホストの通常パス(~/.aws 等)に置き直してください。

VS Code 側のカスタマイズ(Windows 出身者向け)

Windows + VS Code から移ってきた場合に最初に詰まりやすい操作感ギャップを、移行後すぐに潰しておくと体感が大きく変わります。

統合ターミナルの選択コピー / 右クリックペースト

settings.jsonCtrl+, → 右上のアイコンで JSON モード):

{
  // 選択即コピー(Windows ライク)
  "terminal.integrated.copyOnSelection": true,

  // 右クリックでペースト(Windows ライク)
  "terminal.integrated.rightClickBehavior": "paste",

  // 統合ターミナルの inotify 警告抑制(§5-2.5 で根本対処済みなら不要)
  "files.watcherExclude": {
    "**/node_modules/**": true,
    "**/.git/objects/**": true,
    "**/dist/**": true
  }
}

KDE Plasma + Wayland でエッジダブルクリック縦/横最大化を有効化

Agent Teams の長い出力やコードレビューを 縦に最大化して一気に読みたい場面が頻発します。KDE Plasma 6.1.3 以降は、ウィンドウのリサイズエッジ(↕ カーソルが表示される領域)をダブルクリックすると その方向にのみ最大化される機能が標準搭載されていますが、VS Code はクライアントサイドデコレーション(CSD)を使うためデフォルトでは効きません

settings.json に 1 行追加:

{
  "window.titleBarStyle": "native"
}

VS Code を再起動すると、KDE のネイティブタイトルバーを使うようになり、ウィンドウのリサイズエッジダブルクリックで縦/横方向のみ最大化が効きます。

アプリエッジダブルクリック縦最大化対処
Terminator✅ デフォルトで動作(SSD)不要
VS Codewindow.titleBarStyle: native で動作上記 1 行追加
Chrome / Chromium 系❌ Wayland + CSD の制約(KDE Bug 502885現時点では解決方法なし。第 3 回で示したキーボードショートカット割り当てで代替

⚠️ Chrome / Chromium 系は当面「待ち」 — Wayland セッションかつ CSD という組み合わせで、現時点では確実な解決方法がありません。Chrome 側のアップデート、もしくは KWin Bug 502885 の修正待ちが現実的な状況です。Chrome を含む全アプリで縦最大化が必要なら、第 3 回で示した KDE ショートカット割り当て(System Settings → ショートカット → KWin → 「ウィンドウを縦に最大化」)が現実解です。

💡 AI 駆動開発との相性が高い設定 — Agent Teams の出力は縦に長くなりがちで、ホスト OS のターミナル(Terminator)と VS Code の両方で縦最大化が即座にできると体感が大きく変わります。脱 Microsoft 移行時に併せて設定しておく価値があります。

Settings Sync は GitHub アカウントで統一

複数 PC(自宅 / オフィス / ノート)の VS Code 設定を揃えるなら Settings Sync を有効化。Microsoft アカウントと GitHub アカウントの 2 系統がありますが、GitHub アカウントに統一するのが脱 Microsoft の方針と整合します。

Ctrl+Shift+P → "Settings Sync: Turn On..." → "GitHub" を選択

⚠️ Microsoft アカウントと GitHub アカウントを混在させないこと。両方で Settings Sync を有効にすると、別系統で同期されて設定が分裂します。一度どちらかに決めたら、もう一方では "Settings Sync: Turn Off" で解除。

C# Dev Kit の扱い

.NET MAUI / ASP.NET Core 開発では C# Dev Kit が Visual Studio 並みの体験を提供しますが、ライセンス的に商用利用で別途判断が必要です(個人利用は無料、企業利用は有料)。脱 Microsoft の方針なら:

拡張ライセンス推奨
C# Dev Kitms-dotnettools.csdevkit商用は別途有償△ 利用可否を法務で確認
C# Extensionms-dotnettools.csharpMIT◎ 標準
Roslyn 言語サーバー(C# Extension に内包)MIT

C# Dev Kit を入れずに済ませる場合、settings.json

{
  "dotnet.preferCSharpExtension": true,
  "omnisharp.useModernNet": true
}

localhost URL を外部 Chrome で開く

VS Code の Linux 版はデフォルトで localhost URL を内蔵 Simple Browser で開きます(workbench.browser.openLocalhostLinks が既定で有効)。Windows 版は外部ブラウザが起動するため、OS をまたいで Settings Sync するチームでは挙動が割れる点に注意。Web 開発で Chrome DevTools / 拡張機能を使いたい・Windows と挙動を揃えたい場合は、明示的に外部ブラウザへ振ります。

最小設定(settings.json):

{
  // Ctrl+クリックを Simple Browser ではなくデフォルトブラウザに渡す
  "workbench.browser.openLocalhostLinks": false
}

Settings Sync 経由で Mac / Windows 側にも同期されますが、各 OS のデフォルト挙動が違うため、全マシンで同じ挙動に揃えたい場合に明示するのが安全です。

補助的に、VS Code Remote / DevContainer で外部 URI Opener を Chrome 固定にしたい場合:

{
  "remote.localPortHost": "localhost",
  "workbench.externalUriOpeners": {
    "localhost": "chrome"
  }
}

DevContainer で ポート転送時に自動でブラウザを起動したいなら devcontainer.json

"forwardPorts": [3000, 5432],
"portsAttributes": {
  "3000": {
    "label": "Web App",
    "onAutoForward": "openBrowser"
  }
}

openBrowser にすると ホスト OS のデフォルトブラウザで自動起動します(Wayland + Chrome の構成で動作確認済み)。

💡 デフォルトブラウザの確認: 外部ブラウザが起動しない場合、xdg-mime query default x-scheme-handler/http で確認。google-chrome.desktop 等が返れば OK。

⚠️ DevContainer 内ターミナルで localhost:42004201 に書き換えられて CORS が落ちる罠(Linux ホスト固有)

症状: DevContainer 内のターミナルで http://localhost:4200 を Ctrl+クリックすると、VS Code が localhost:4201 に書き換えてブラウザを開きます。「ポート」タブを見るとコンテナ内 listen の 4200 が ホスト側 4201 に自動プロキシ転送されており、Ctrl+クリック時に URL もそのプロキシ先に置き換わる挙動です。

実害: Angular dev server が localhost:4200 想定で動いていても、ブラウザのアドレスが localhost:4201 になるため、API への CORS リクエストの Origin が http://localhost:4201 となり、API 側の CORS 許可リストから外れて CORS エラーで API 呼び出しが全滅します。

Microsoft.AspNetCore.Cors.Infrastructure.CorsService[6]
  Request origin http://localhost:4201 does not have permission to access the resource.

Linux 固有である理由: network_mode: host は Linux 専用機能で、Windows / Mac の Docker Desktop では VS Code の自動転送ロジックが別経路で動作するため、本問題は Linux ホスト固有です。Windows + WSL2 から Ubuntu ネイティブに移ったタイミングで初めて踏む罠で、原因が見えにくい。

対処: .devcontainer/devcontainer.json2 設定をセットで 入れます:

{
  "customizations": {
    "vscode": {
      "settings": {
        // ターミナルで URL を Ctrl+クリックした時の URL 書き換え(4200 → 4201)を止める
        "remote.forwardOnOpen": false
      }
    }
  },
  // コンテナ内ポート listen を検知して、ポートタブに自動で転送を作る挙動を止める
  "otherPortsAttributes": {
    "onAutoForward": "ignore"
  }
}
設定役割
customizations.vscode.settings.remote.forwardOnOpen: falseターミナル URL の 書き換え を止める(4200 → 4201 を防ぐ)
otherPortsAttributes.onAutoForward: "ignore"ポートタブに 転送を作る 挙動を止める

両方が揃って初めて完全に動作します(VS Code 1.119 系で検証)。remote.forwardOnOpen 単独だと URL 書き換えは止まるがポートタブには転送が残り、中途半端な状態になります。

⚠️ 設定変更後は Rebuild Container が必要(Reopen in Container では VS Code Server に反映されません)。なお古い記事に登場する remote.autoForwardPorts: false は VS Code 1.119 系で 廃止済みで効きません。forwardPorts: [] も明示の必要はなく、挙動は変わりません。

Dev Container ワンクリック起動(Windows ジャンプリスト相当)

KDE Plasma のタスクバーから code のアイコンを右クリック → “新規ウィンドウ” のショートカットに、特定リポジトリを直接 DevContainer で開くコマンドを登録できます。

~/.local/share/applications/code-cts-ec.desktop

[Desktop Entry]
Name=VS Code — cts-ec (DevContainer)
Exec=code --folder-uri vscode-remote://dev-container%2B$(printf '%s' "$HOME/Projects/cts-ec" | xxd -p | tr -d '\n')/workspace/cts-ec
Type=Application
Icon=code
Categories=Development;
update-desktop-database ~/.local/share/applications/

KDE Plasma のアプリメニューから検索可能になり、ピン留めもできます。Windows のジャンプリストに近い使い勝手。

Ubuntu ネイティブ運用の体感差

操作Windows + WSL2 + Docker DesktopUbuntu + Docker Engine
docker compose up -d 初回30〜60 秒8〜15 秒
pnpm install(中規模)90 秒40 秒
ホットリロード(Vite / Next.js)ファイル保存後 0.8〜1.5 秒0.2〜0.4 秒
メモリ消費(アイドル)30 GB ベース2〜4 GB ベース
Agent Teams 並列稼働のハード上限(64 GB ホスト・実機)約 3 並列が限界.wslconfig で 48 GB 拡張済み)約 9 並列まで余裕

💡 数値は実機計測の概算ですが、「明らかに体感が違う」レベルの差になります。WSL2 のオーバーヘッドは「気にならない」と言われがちですが、それは比較対象がないからで、ネイティブに移ると違いがはっきりわかります。Linux 上では bind mount のネイティブ速度になり、Windows + WSL2 構成で発生していた node_modules の I/O 遅延もほぼ消えます。

⚠️ 「ハードで動かせる並列数」と「実運用での並列数」は別軸: Ubuntu ネイティブでハード上は 9 並列が動いても、Claude Code(Max 20× プラン)の 5 時間制限と週間制限が先に枯渇します。実機では 3 ステアリング × 3 Agent = 9 並列を回したら 5 時間制限が即アウト、週間制限も 1 日で 2 日分消費して運用断念した実例があります。現実的な運用上限は 3〜4 並列で、これは「Linux 側の前提」とは別軸(サブスク側の制限)です。プラン選定とレート最適化の詳細は ステアリング駆動開発 シリーズの管轄。

つまり Windows はハードで詰まる / Ubuntu はサブスクで詰まる という二段の壁が見えます。ハード側のボトルネックを Linux ネイティブで解消した上で、サブスク側の制限と向き合うのが正しい順序です。

Docker Desktop のメモリ上限引き上げ(Windows ホストの対症療法)

Windows + Docker Desktop でメモリ不足にぶつかった場合、%USERPROFILE%\.wslconfig(Windows ホーム直下)に以下を書くと WSL2 側の上限を引き上げられます:

[wsl2]
memory=48GB
swap=8GB
processors=16

設定後は PowerShell で:

wsl --shutdown
# その後 Docker Desktop を再起動
観点
64 GB ホストでの安全圏memory=48GB(Windows 側に 8〜12 GB を残す必要があるため)
Docker Desktop での認識値48 GB 割当 ≒ 約 46 GB(OS オーバーヘッドで 4〜5% 引かれる)
上限まで攻める場合memory=56GB まで上げられるが、Windows 側(VS Code / ブラウザ / Docker Desktop ホストプロセスで合計 8 GB 前後)が苦しくなる

⚠️ メモリ拡張は対症療法。 ホスト側からの割り当ては増やせますが、二重仮想化のオーバーヘッドはそのまま残るため、根本解決にはなりません。同一 64 GB ハード上で Windows + Docker Desktop(48 GB 拡張)の 3 並列 ≒ Ubuntu ネイティブの 9 並列と、3 倍以上の差が実機で出ます。Linux ネイティブへの移行が本筋です。

🤖 Phase 5-4: Claude Code 導入と post-create.sh

本記事の方針では Claude Code は DevContainer 内で動かす のが前提です(理由は §5-5)。Claude Code 本体・tmux・必要な CLI 群(gcloud / aws / glab / uv 等)は post-create.sh で都度セットアップするのが、CTS で標準化した運用です。Dockerfile に詰め込むとベースイメージの更新と各ツールのバージョン更新のたびに再ビルド待ちになるため避けます。

Claude Code は 公式ネイティブインストーラー で入れる

公式の install.sh を使うと、npm グローバル経由のインストール時に発生しがちな権限・PATH まわりのハマりを回避できます。

curl -fsSL https://claude.ai/install.sh | bash
export PATH="$HOME/.local/bin:$PATH"
claude --version

💡 npm install -g @anthropic-ai/claude-code よりも install.sh を推奨。 公式インストーラーは ~/.local/bin にバイナリを配置するため、コンテナ内 vscode ユーザーで sudo 不要。PATH.bashrc に通しておけば、ホスト端末経由 docker exec → tmux → claude のフローでも素直に呼べます。

tmux は ソースビルドで最新版

Ubuntu の apt install tmux で入るのは古めのバージョンで、Agent Teams(split-pane モード)の挙動が安定しないことがあります。tmux 3.5 以降をソースビルドするのが確実です。

TMUX_VERSION="3.6a"  # 公式リリースから最新を取得して固定
sudo apt-get install -y libevent-dev ncurses-dev build-essential bison pkg-config
curl -sL "https://github.com/tmux/tmux/releases/download/${TMUX_VERSION}/tmux-${TMUX_VERSION}.tar.gz" \
  | tar xz -C /tmp
cd /tmp/tmux-${TMUX_VERSION} && ./configure -q && make -j"$(nproc)" -s && sudo make install

~/.tmux.conf の典型設定(マウススクロール有効化・履歴拡張・Agent Teams のレイアウト整え):

set -g mouse on
set -g history-limit 50000
set -g status on
set -g default-terminal "tmux-256color"
setw -g mode-keys vi

# Agent Teams レイアウト自動調整
set -g main-pane-width '50%'
set-hook -g after-split-window "select-layout main-vertical"

post-create.sh の典型構成

#!/bin/bash
set -e

# Docker CLI(docker-outside-of-docker Feature が Ubuntu 26.04 (resolute) 未対応のため手動導入)
# /var/run/docker.sock は docker-compose.yml で bind mount 済みなので、CLI 本体だけここで入れる
if ! command -v docker &> /dev/null; then
  curl -fsSL https://get.docker.com | sh
fi

# Google Cloud SDK
if ! command -v gcloud &> /dev/null; then
  curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
    | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
  echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
    | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
  sudo apt-get update && sudo apt-get install -y google-cloud-cli
fi

# AWS CLI
if ! command -v aws &> /dev/null; then
  curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
  unzip -q /tmp/awscliv2.zip -d /tmp && sudo /tmp/aws/install
  rm -rf /tmp/awscliv2.zip /tmp/aws
fi

# GitLab CLI(glab)
# uv(Python パッケージマネージャー)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

# Claude Code(公式ネイティブインストーラー)
if ! command -v claude &> /dev/null; then
  curl -fsSL https://claude.ai/install.sh | bash
fi

# tmux(最新版をソースビルド)
# … 上記の TMUX_VERSION ブロック …

# ~/.tmux.conf 配置
# … 上記の ~/.tmux.conf を配置 ~

# Claude Code 設定
mkdir -p ~/.claude
cat > ~/.claude/settings.json << 'EOF'
{
  "skipDangerousModePermissionPrompt": true,
  "teammateMode": "auto",
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  }
}
EOF

# .bashrc に PATH と alias
ALIAS_MARKER="# Claude Code alias"
if ! grep -q "$ALIAS_MARKER" "$HOME/.bashrc" 2>/dev/null; then
  cat >> "$HOME/.bashrc" << 'EOF'

# Claude Code alias
export PATH="$HOME/.local/bin:$PATH"
export LANG=C.UTF-8
alias cc="claude --dangerously-skip-permissions"
EOF
fi

echo "post-create.sh: 完了"

💡 alias cc="claude --dangerously-skip-permissions" を入れておくと、DevContainer 内で cc の 2 文字で bypassPermissions 付きの起動になります。DevContainer 内でしか効かないため、ホスト OS で誤って打つ事故を構造的に防げます。

Agent Teams 環境変数の二重指定

devcontainer.jsoncontainerEnvcustomizations.vscode.settings.claudeCode.env両方CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 を書きます(§5-3 の例参照)。

起動経路効くのは必要な設定
VS Code 内蔵の Claude Code 拡張から起動customizations.vscode.settings.claudeCode.envこちらだけだと CLI 直叩きで効かない
ホスト端末 → docker exectmuxclaude(本記事推奨)containerEnvこちらだけだと VS Code 経由で効かない

teammateMode"auto"(tmux 内なら split-pane、外なら in-process に自動切替)が公式推奨です。

認証

DevContainer に最初に入ったときに claude を実行すると、ブラウザで OAuth ログインを促されます。Ubuntu ホストの Chrome で完了すれば、認証情報は ~/.claude に保存されます。

§5-3 で ~/.claude をホストと bind mount で共有しているため、コンテナを作り直しても再ログイン不要、かつ VS Code 経由とホスト端末経由でセッションが揃うメリットもあります。

🛡️ Phase 5-5: Linux 固有の起動経路と DevContainer 隔離

本記事の主題は Linux 固有のハマりどころです。Claude Code 本体の使い方や Agent Teams の運用ノウハウは AI 駆動開発シリーズ側にすべて書かれているため、ここでは Linux ホスト + DevContainer + tmux の起動経路だけを正確に押さえます。

なぜ DevContainer 隔離が必須か(bypassPermissions の安全装置)

bypassPermissions は破壊的操作(rm -rf, git push --force, git reset --hard 等)を 無確認実行する危険機能です。生産性は劇的に上がる代わりに、ホスト OS で動かすと事故時の被害が制御不能になります。

環境bypassPermissions の効果事故時の被害範囲
ホスト OS で claude --dangerously-skip-permissions確認なし高速実行ホスト全体(最悪 OS 再インストール)
DevContainer 内で bypassPermissions(採用)確認なし高速実行コンテナ内のみ(docker compose down -v で復旧)

⚠️ bypassPermissions をホスト OS で動かすのは絶対 NG。 一度 rm -rf ~ 系の事故を起こすと丸 1 日復旧不可になります。DevContainer に閉じ込めると コンテナを破棄して再作成(5 分) で済むため、安全装置として桁違いに効きます。これは AI 駆動開発の生産性を支える Linux 側の前提条件です。

なぜ VS Code 統合ターミナルではなく Konsole から起動するのか

VS Code の統合ターミナルから tmux を起動すると、tmux pane と VS Code のターミナル制御コードが競合して表示が崩れます。これは Claude Code 公式ドキュメントでも 環境制限として明記されています。

起動経路安定性
VS Code 統合ターミナル → docker exec → tmux❌ 公式に非対応(表示崩壊・色化け・カーソル位置ずれ)
VS Code Dev Containers の “Open in Terminal”❌ 同上
Ubuntu ホスト端末(Konsole / GNOME Terminal)→ docker exec → tmux✅ 安定(Linux + tmux は公式対応)

💡 VS Code はコード編集専用、tmux + Claude Code は別端末 が安定運用の鉄則。Ubuntu に移ったことで PowerShell + WSL の 2 段経由は不要になり、KDE Plasma の Konsole から直接 docker exec できるようになります(後述の比較表参照)。

起動フロー

Claude Code を Linux で起動するフロー図(Ubuntu ホスト Konsole → docker exec で DevContainer に入る → tmux セッション作成 → claude 起動。bypassPermissions / Agent Teams / Remote Control の運用は AI 駆動開発シリーズ参照)

起動コマンド(Linux 固有部分のみ)

# ① Konsole / GNOME Terminal を開く(VS Code 統合ターミナルではない)
# ② プロジェクトに移動
cd ~/Projects/cts-ec

# ③ DevContainer を起動(VS Code から開いていれば既に起動済み)
docker compose -f .devcontainer/docker-compose.yml up -d

# ④ DevContainer に入る
docker exec -it -u vscode cts-ec_devcontainer-devcontainer-1 bash

# === ここから DevContainer 内 ===

# ⑤ ロケール(コンテナの environment で設定済みなら不要)
export LANG=ja_JP.UTF-8

# ⑥ tmux セッション(start-directory を明示してプロジェクト直下で起動)
tmux new -s claude -c /workspace/cts-ec

# ⑦ Claude Code を起動
claude

⚠️ tmux new -c <directory> は必須 — Claude Code は 起動時の CWD(カレントディレクトリ) からプロジェクト固有の slash commands / skills(.claude/commands/ / .claude/skills/)を解決します。公式ドキュメントに「Claude Code operates within the directory where you launch it」と明記されています。

-c を省略すると、tmux 新規セッションの CWD は 呼び出し元シェルの状態に依存します(特に Tailscale + SSH 越しdocker exec → tmux → claude 経路では $HOME/home/vscode など)になりがち)。この場合 プロジェクトの slash commands が認識されない症状が出ます。tmux man page の new-session [-c start-directory] で公式保証された挙動なので、常に明示するのが安全です。

なお、既存セッションへの再アタッチは tmux a -t claude でカレントディレクトリは保持されるので、-c の指定は 新規作成時のみで OK。

📌 Windows 時代との差分: WSL 環境では PowerShell → wsl → docker exec → tmux → claude の 5 段経由でしたが、Ubuntu ホストでは Konsole → docker exec → tmux → claude3 段に短縮されます。日々の起動コストが地味に効きます。

Claude Code の使い方は AI 駆動開発シリーズへ

ここから先の以下のトピックは、本記事の主題(Linux 固有のセットアップ)から外れるため、ステアリング駆動開発」シリーズで詳述しています。

トピック参照先
12 体のサブエージェント構成 / Delegate Mode / コスト戦略 / Agent Teams 運用Agent Teams 編:12 体のサブエージェント構成とマルチエージェント並列協調
bypassPermissions + Remote Control による「スタバから監視・承認」運用Claude Code リモート操作(Tailscale + SSH)
7 層ハーネスエンジニアリングの設計指針Claude Code 7 層ハーネスエンジニアリング

💡 本記事は「Claude Code を Linux で動くようにする」までが範囲。Claude Code をどう使い倒すか」は AI 駆動開発シリーズの管轄です。重複させないことで、双方の記事をシンプルに保てます。

🛡️ リスクと対策表(Ubuntu 開発環境セットアップ 編)

リスク影響度対策
ホスト OS で bypassPermissions を有効化致命的必ず DevContainer 内のみで有効化。ホストで --dangerously-skip-permissions を打たない
VS Code 統合ターミナルから tmux 起動Konsole / Terminator / GNOME Terminal から docker exec で起動(公式に非対応)
~/.aws / ~/.ssh をコンテナ内で誤書き換えbind mount を readonly で持ち込む(~/.claude 以外はすべて readonly)
docker グループ付与の権限過大個人開発機のみ。共有サーバーでは付与しない
apt install tmux が古く Agent Teams が不安定tmux 3.5 以降を post-create.sh でソースビルド
Agent Teams 環境変数が起動経路によって効かないcontainerEnvcustomizations.vscode.settings.claudeCode.env の両方に書く
DevContainer 内の Claude 認証情報が消える~/.claude をホスト側 ~/.claude と bind mount で共有
node_modules の I/O が遅い低(解消済み)Ubuntu ネイティブで bind mount が高速化されているため WSL2 比で気にならない
VS Code SCM パネルがファイル変更を取りこぼす中(Linux ネイティブ固有)fs.inotify.max_user_watches=524288 + DefaultLimitNOFILE=524288(§5-2.5 参照)
inotify 上限が再起動でデフォルトに戻る(/etc/sysctl.conf 罠)中(Ubuntu 26.04 固有)/etc/sysctl.conf は起動時に読み込まれないため、必ず /etc/sysctl.d/99-inotify.conf に置き sudo sysctl --system で反映。再起動後 cat /proc/sys/fs/inotify/max_user_instances1024 のままか確認(§5-2.5 参照)
DevContainer 内ターミナル URL の自動書き換え(4200 → 4201)→ CORS エラー中(Linux ホスト固有)devcontainer.jsonremote.forwardOnOpen: falseotherPortsAttributes.onAutoForward: "ignore" の 2 設定を併記し Rebuild Container(§5-3 「VS Code 側のカスタマイズ」参照)
network_mode: host で Testcontainers が衝突devcontainer.jsonremoteEnvTEST_POSTGRESQL_CONNECTION_STRING を明示
docker-outside-of-docker Feature が Ubuntu 26.04 (resolute) 未対応 → リビルドで Dev Container 起動不能中(Ubuntu 26.04 固有)Feature を削除し、.devcontainer/post-create.shcurl -fsSL https://get.docker.com | sh により Docker CLI を手動導入。/var/run/docker.sockdocker-compose.yml で bind mount 維持(§5-3 参照)
localhost URL が VS Code 内蔵 Simple Browser で開かれる低(Linux デフォルト挙動)settings.jsonworkbench.browser.openLocalhostLinks: false。Settings Sync で OS 間の挙動を統一
Settings Sync が Microsoft / GitHub の両系統で分裂GitHub アカウントに統一(脱 Microsoft 方針と整合)
Ark(Dolphin の圧縮)で作成した ZIP が Windows で文字化け / 解凍エラー中(Windows 連携時)7-Zip 公式版 7zz7zip-standalone)を使う7zz a -tzip -p -scsUTF-8 で UTF-8 フラグ付き ZIP を生成、zipenc エイリアス推奨(§5-2 参照)
C# Dev Kit の商用ライセンス標準 C# Extension(MIT)で代替、または法務確認
tmux new -s <name>-c 省略 → SSH 越しに project commands が認識されないtmux new -s claude -c /workspace/<repo> で必ず start-directory を明示。Claude Code は起動時 CWD から .claude/commands / .claude/skills を解決する(公式仕様)

✅ 第 4 回まとめ

  • Docker Engine をネイティブで導入することで、WSL2 + Docker Desktop の二重仮想化が消え、メモリ・I/O が大幅に改善
  • VS Code は脱 Microsoft の例外として残すのが現実解。Dev Containers 拡張で WSL 時代と同じ DevContainer 構成がそのまま動く
  • ホスト側カーネルチューニング(fs.inotify.max_user_watches=524288 + DefaultLimitNOFILE=524288 が Linux ネイティブ固有の必須前提。WSL2 / macOS では発生しないが、Linux ネイティブで VS Code の SCM パネルがファイル変更を取りこぼす症状が出る
  • ターミナルは Terminator + PuTTY style paste + Copy on selection が Windows 出身者の操作感ギャップを最小化。Gogh で Tokyo Night など暗背景テーマを揃えると Agent Teams 監視が楽
  • 言語ランタイムは DevContainer Features で宣言的に(dotnet / node / python)。Dockerfile を書かずに済む。ただし docker-outside-of-docker Feature は Ubuntu 26.04 (resolute) 未対応のため使わず、Docker CLI は post-create.shget.docker.com から導入し、/var/run/docker.sockdocker-compose.yml で bind mount
  • 認証情報は ホスト側に置いたまま mounts で readonly bind mount~/.aws ~/.config/gcloud ~/.config/glab-cli ~/.gitconfig ~/.ssh ~/.microsoft/usersecrets)。~/.claude だけ書き込み可で bind、ホスト ↔ コンテナでセッション共有
  • network_mode: host を採用したら remoteEnvTEST_POSTGRESQL_CONNECTION_STRING を明示。Testcontainers のデフォルト挙動(コンテナ起動)と衝突する
  • Claude Code は公式ネイティブインストーラーcurl https://claude.ai/install.sh | bash)で導入。tmux3.5 以降をソースビルドして Agent Teams を安定動作させる
  • Agent Teams 環境変数は containerEnvclaudeCode.env の両方に書く。VS Code 経由とホスト端末経由のどちらの起動でも効くように
  • bypassPermissions は必ず DevContainer 内のみで運用。ホスト OS で動かすと事故時に丸 1 日復旧不可。alias cc="claude --dangerously-skip-permissions".bashrc に置けば、コンテナ内でだけ短縮起動できる
  • VS Code 統合ターミナルからは tmux NG(公式に非対応)。Konsole / Terminator / GNOME Terminal から docker exec → tmux → claude の 3 ステップが鉄則
  • tmux new -s claude -c /workspace/<repo> で start-directory を必ず明示。Claude Code は起動時 CWD からプロジェクト固有の .claude/commands/ / .claude/skills/ を解決する仕様(公式ドキュメントで明記)。-c 省略すると SSH 越しでプロジェクトコマンドが認識されない
  • VS Code Settings Sync は GitHub アカウントに統一。Microsoft アカウントとの混在は分裂の原因
  • 本記事は Linux 固有のセットアップ に絞り、Agent Teams の使い方や Remote Control によるリモート運用などの AI 駆動開発ノウハウは「ステアリング駆動開発」シリーズで詳述(重複させない)
  • Ubuntu 26.04 LTS + KDE Plasma + Docker Engine + DevContainer + Claude Code のスタックは 2031 年まで OS サポートありで、長期安定運用が可能

これで OS の土台 + 業務 PC 化 + 開発スタック までが完成しました。次回からはデータ層・コラボレーション・本番移行のフェーズに入ります。

次回「Ubuntu データ共有セットアップ — rclone Drive + QNAP NAS + Obsidian Vault」では、rclone + systemd ユーザーサービスによる Google Drive 自動マウント、QNAP NAS への NFS / SMB ハイブリッド接続、AppArmor プロファイルで Obsidian など Electron AppImage を安全に動かす手順までを扱います。

📚 シリーズ記事

#タイトル内容
1脱Microsoft 全体戦略・総論値上げ背景・ロードマップ・コスト・リスク
2Windows 11 + Ubuntu デュアルブート構築M.2 物理分離・KDE Plasma・GRUB
3Ubuntu PC セットアップNVIDIA ドライバ事後導入・サスペンド無効化・Bluetooth・ターミナル選定
4Ubuntu 開発環境セットアップ(本記事)Docker Engine ネイティブ・DevContainer 機密情報マウント・Claude Code
5Ubuntu データ共有セットアップrclone Drive + systemd・QNAP NFS/SMB・Obsidian Vault・AppArmor
6Microsoft 365 → Google Workspace 全面移行メール・SSO・MFA・MX 切替 + OneDrive → Google Drive ファイル移行・共有リンク・コスト最適化
7Ubuntu リモート開発セットアップTailscale + SSH + VS Code Remote / DevContainer・Wayland ホスト
8ActiveReports → Playwright + Scriban 移行(公開予定)帳票エンジン置換・バーコード・印刷品質 PDF
9コスト削減効果と 1 年運用レビュー(公開予定)実績ベースのコスト・運用上のハマりどころ

🔗 関連リソース

CTS-KB 内の関連記事

関連用語

  • Docker — Linux カーネル機能を活用する OSS の仮想化プラットフォーム
  • DevContainer — リポジトリに同梱する開発環境コンテナ仕様(本記事の主題)
  • Claude Code — Anthropic 公式の CLI コーディングエージェント
  • Agent Teams — Claude Code の実験的並列実行機能(運用詳細は AI 駆動開発シリーズ)
  • Ubuntu — 開発デファクトの Linux ディストロ
  • tmux — Agent Teams のバックエンドとなる端末多重化ツール
  • WSL2 — Windows での代替実行環境(移行元)
  • Wayland / X11 — Linux のディスプレイサーバー

外部リソース