前回、UGREEN NASync DXP2800GT上のDockerコンテナでClaude Codeを動かし、スマホのClaudeアプリから /rc でアクセスする環境を作りました。今回はその続きです。

しばらく運用してみて、はっきりした不満がひとつありました。

ターミナルを閉じると、起動元のCLI画面に戻れなくなる。

誤解のないように書いておくと、Claude Codeのセッション自体が消えるわけではありません。デスクトップアプリやスマホからのリモートコントロールは問題なく操作できます。

消えるのは「そのプロセスを起動した端末とのつながり」です。Portainerの管理画面からコンテナのConsoleを開いて作業していると、タブを閉じた時点でそのCLI画面には二度と戻れません。処理の途中経過も、それまでのやりとりの表示も追えなくなる。別経路から操作はできるのに、手元の画面だけが失われる——この片手落ちの状態がストレスでした。

解決策は tmux です。今回はその導入から、実際にハマった落とし穴まで含めて記録します。

tmuxとは何か

ひとことで言えば 「ターミナルの中に、閉じても保持され続ける仮想の画面を作るソフト」 です。

通常、ターミナルで起動したプログラムは、その端末と結びついています。接続が切れれば端末は失われ、そこに映っていた画面にはもう戻れません。

tmuxはこの間に1枚挟まります。

【従来】 ターミナル ── Claude Code
          ↑ここが切れたら画面は戻ってこない

【tmux】 ターミナル ── tmux ── Claude Code
          ↑切っても平気        ↑画面ごと保持される

tmuxは「サーバー」としてコンテナ内に常駐し続け、画面の内容をまるごと抱えています。こちらが接続を切っても、tmuxは何事もなかったように動き続けます。後からまた繋ぎ直せば、離れた時のままの画面が出てきます。

喫茶店で席を立つときに荷物を置いて「離席中」の札を立てておくようなイメージです。戻れば同じ席、同じ状態。

screen という似たツールもありますが、tmuxの方が新しく、設定も素直なので今回はこちらを使います。


準備:NASのSSHを有効にする

まずNAS本体にSSHで入れるようにします。DXP2800GTは初期状態でSSHが無効です。

UGOSにログインして、コントロールパネル → 接続とアクセス → ターミナル を開きます。

SSH の「有効」にチェックを入れて、右下の「適用」。ポートは22のままで構いません。

Telnetは有効にしないでください。 通信が暗号化されないため、いまどき使う理由がありません。

ここで見落としがちなのが 「オートオフ」 です。既定は30分後になっていて、一定時間経つとSSHサービス自体が勝手に止まります。毎回コントロールパネルから入れ直すことになるので、私は6時間に変更しました。

あわせて セキュリティ → 自動ブロック も有効にしておくことをおすすめします。ログインに連続で失敗したIPを遮断してくれます。もちろん、ルーターで22番ポートを外部公開するようなことはしないでください。あくまで自宅LAN内での利用です。

接続してみる

Windowsなら、PowerShellをそのまま使えます。

ssh 管理者ID@192.168.1.26

初回は「このホストを信頼するか」と聞かれるので yes。パスワードはUGOSのログインと同じものです。

管理者ID@DXP2800GT:~$ と出れば成功です。

コンテナの中に入る

NASにログインしただけでは、まだClaude Codeのいるコンテナの外側です。

docker exec -it claude-code bash

おそらくこう怒られます。

permission denied while trying to connect to the Docker API at unix:///var/run/docker.sock

管理者ID がdockerグループに属していないためです。sudo を付ければ通ります。

sudo docker exec -it claude-code bash

プロンプトが root@b52ee35b867f:/workspace# のような表示に変われば、コンテナの中です。以降のコマンドはすべてこの中で実行します。

豆知識usermod -aG docker 管理者ID でsudoなしにもできますが、これは実質root権限を渡す操作です。個人利用のNASとはいえ、素直にsudoを付ける方が無難でしょう。


インストール

コンテナ内で実行します。

apt-get update && apt-get install -y --no-install-recommends tmux
tmux -V

tmux 3.3a のようにバージョンが返れば成功です。1分もかかりません。

--no-install-recommends は「推奨パッケージまでは入れない」というオプションです。付けないと使わないものが大量に入ってコンテナが太るので、習慣として付けておくといいでしょう。


設定ファイルは「どこに置くか」が重要

ここが今回いちばんの勘所です。

tmuxの設定は普通 ~/.tmux.conf(= /root/.tmux.conf)に置きます。しかしDockerコンテナでは、この場所に置いたファイルはコンテナを作り直した瞬間に消えます。

コンテナには「永続化される場所」と「されない場所」があります。私の環境ではこうなっていました。

コンテナ内のパス実体作り直しで
/workspaceNASの /volume1/claude-projects残る
/root/.claudeDockerボリューム残る
それ以外(/root/ 直下など)コンテナ内部消える

確認するにはこのコマンドが使えます。

grep claude /proc/mounts

なので、設定ファイルは永続化される側に置いて、起動時に明示的に読み込ませます。私はObsidian Vaultの中に置きました。

mkdir -p /workspace/second-brain/ops
cat > /workspace/second-brain/ops/tmux.conf <<'EOF'
set -g default-terminal "screen-256color"
set -g history-limit 50000
set -g base-index 1
setw -g pane-base-index 1
bind r source-file /workspace/second-brain/ops/tmux.conf \; display "reloaded"
set -g status-left "[#S] "
EOF
cat /workspace/second-brain/ops/tmux.conf

各行の意味です。

  • default-terminal … 色の扱い。tmux-256color を指定する解説が多いですが、slim系の軽量イメージには対応データが入っておらず起動に失敗します。screen-256color が安全です
  • history-limit 50000 … スクロールで遡れる行数。Claude Codeは出力が長いので多めに
  • base-index 1 … ウィンドウ番号を1から始める(既定は0で、キーボードの並びと合わずに押し間違えます)
  • bind rCtrl-br で設定を再読み込み
  • status-left … 画面下のバーにセッション名を表示

注意<<'EOF' のシングルクォートは必須です。外すと \;#S がシェルに解釈されて設定が壊れます。作成後は必ず cat で中身を確認してください。

マウス操作(set -g mouse on)を入れる解説もよく見かけますが、私は入れていません。Claude Codeが独自にスクロールを扱うため競合しやすく、ターミナル本来のテキスト選択も潰れてしまうからです。必要を感じてから足す方がいいと思います。


起動と基本操作

tmux -f /workspace/second-brain/ops/tmux.conf new -s brain

-f で設定ファイルを指定、-s brain はセッションに付ける名前です。用途がわかる名前にしておくと後で楽です。

tmux起動直後、下部に緑のステータスバーが出た画面

画面下端に緑のバーが現れ、左端に [brain] 1:bash* と表示されました。これがtmuxの中にいる証拠です。

覚えるべきキー操作

tmuxの操作は、すべて Ctrl-b を押して離してから、次のキー という2段階で行います。同時押しではありません。ここを最初に間違える人が多いです。

やりたいことキー
抜ける(デタッチ)Ctrl-bd
新しいウィンドウCtrl-bc
ウィンドウ切り替えCtrl-b1 / 2
セッション一覧から選ぶCtrl-bs
スクロール(コピーモード)Ctrl-b[q で抜ける
設定リロードCtrl-br

コマンドで使うのはこのあたりです。

tmux ls                  # セッション一覧
tmux attach -t brain     # 指定セッションに戻る
tmux new -s <名前>        # 新規セッション

★ 絶対に exit を打たないこと

これが最重要です。

exit はシェルそのものを終了させるコマンドなので、tmuxのウィンドウごと消えます。 最後のウィンドウで打てばセッションも消滅し、tmuxを入れた意味がなくなります。

抜けるときは必ず Ctrl-bd(デタッチ)。あるいは、何も操作せずターミナルを閉じてしまっても構いません。


動作確認

本当に効いているか確かめます。まず目印を作ります。

echo "テスト $(date)" > /tmp/tmux-test.txt
cat /tmp/tmux-test.txt

Ctrl-bd でデタッチ。

tmux ls

brain: 1 windows (created ...) と返れば、セッションは生きています。

tmux attach -t brain

先ほどの出力が残った画面に戻りました。

本番のテスト

ここからが本題です。デタッチせずに、PowerShellのウィンドウを × で閉じてください。 SSHが強制切断されます。

そのうえで、もう一度接続します。

ssh 管理者ID@192.168.1.26
sudo docker exec -it claude-code tmux attach -t brain

画面が戻ってくれば成功です。これで「ターミナルを閉じても、元のCLI画面に戻れる」環境が手に入りました。


つまずいた話①:日本語が _____ に化ける

順調に見えたのですが、ここで問題が出ました。tmuxの中で日本語を表示すると、こうなります。

______ Sun Aug 16 16:15:09 JST 2026

「テスト」の3文字がアンダースコアに置き換わっています。

切り分けの手順

慌ててファイルが壊れたと思わないことです。順に確かめました。

1. tmuxの外で見てみる

Ctrl-bd でデタッチして、同じファイルを表示します。

cat /tmp/tmux-test.txt

正しく「テスト」と出ました。ファイルは壊れていない。 表示の問題だと確定します。

2. 環境変数を確認

echo "LANG=[$LANG] LC_ALL=[$LC_ALL]"
LANG=[] LC_ALL=[]

両方とも空でした。これが原因です。

node:22-slim のような軽量イメージは、サイズを削るためロケール(言語・文字コードの設定)が入っていません。文字コードが不明なのでtmuxがマルチバイト文字を描画できず、_ に置き換えていたわけです。

3. さらに厄介な点

export LANG=C.UTF-8 を実行してもすぐには直りませんでした。すでに起動しているtmuxサーバーの環境は変わらないからです。

しかも決定的だったのは、一度は正しく表示された行が、入り直したら化けた という現象。ここから、表示を決めているのはサーバー側ではなく 接続する側(tmux attach を実行するプロセス)の環境 だと分かりました。

対処

その場しのぎなら -u オプション(UTF-8を明示)で解決します。

tmux -u attach -t brain

ただ毎回付けるのは面倒なので、後述するStack設定で根本から直しました。


コンテナを作り直しても消えない状態にする

さて、ここまでの作業には大きな欠点があります。

tmuxをapt-getで入れただけなので、コンテナを作り直したら全部消えます。

Dockerのコンテナは、使い捨てできることが利点です。しかし毎回tmuxを入れ直すのでは本末転倒。そこで、Stack(docker-compose)の定義に組み込んでしまいます。

変更前

    environment:
      - TZ=Asia/Tokyo
    command: >
      bash -c "
      apt-get update &&
      apt-get install -y git curl &&
      npm install -g @anthropic-ai/claude-code &&
      tail -f /dev/null
      "

変更後

    environment:
      - TZ=Asia/Tokyo
      - LANG=C.UTF-8
      - LC_ALL=C.UTF-8
    command: >
      bash -c "
      apt-get update &&
      apt-get install -y --no-install-recommends git curl tmux tree ripgrep &&
      npm install -g @anthropic-ai/claude-code @tobilu/qmd &&
      tail -f /dev/null
      "

変えたのは2箇所だけです。

  • environmentLANG / LC_ALL を追加 → 文字化けが根本解決
  • command のインストール対象に tmux などを追加 → 作り直しても自動で揃う

ついでに、毎回入れ直していた treeripgrepqmd も一緒に含めました。

適用手順

Portainerの Stacks → 対象のスタック → Editor を開きます。

作業前に、現在の定義を全文コピーしてテキストファイルに保存してください。 失敗したらこれを貼り戻すだけで元に戻せます。保険は必ず用意しておきましょう。

Vaultなど大事なデータのバックアップも取っておくと安心です。

sudo tar czf /volume1/claude-projects/backup-$(date +%Y%m%d).tar.gz -C /volume1/claude-projects second-brain

編集したら Update the stack。確認ダイアログの「Re-pull image」はオフのままで構いません。

コンテナが削除・再作成され、apt-getとnpmが走ります。2〜5分ほどかかります。

sudo docker logs -f claude-code

ログを読むときの注意restart: unless-stopped を設定していると、過去の起動分のログも混ざって表示されます。私は末尾に出ていた Temporary failure resolving 'deb.debian.org' を見て「インストールに失敗した」と早合点しましたが、実際は前半で完走済みでした。判断はログではなく、実物のバージョン確認でやるべきです。

sudo docker exec -it claude-code bash -c 'echo "LANG=$LANG"; tmux -V; tree --version; rg --version; qmd --version'

すべて返ってくれば成功。LANG=C.UTF-8 も効いており、これ以降は -u なしでも日本語が正しく表示されるようになりました。


つまずいた話②:設定ファイルが1つだけ消えた

コンテナを作り直した後、Claude Codeを起動したらこんな画面が出ました。

Claude configuration file not found at: /root/.claude.json

テーマ選択から始まる初回セットアップ。過去の会話履歴にアクセスできません。

原因は永続化の落とし穴でした。ボリュームでマウントされているのは /root/.claudeディレクトリ)で、設定本体の /root/.claude.json/root/ 直下のファイル。1階層違うだけで対象外だったのです。

復旧方法

幸い、Claude Codeは設定ファイルを自動でバックアップしていました。

ls -la /root/.claude/backups/
44286  Aug 16 12:01  .claude.json.backup.1786849285407
44317  Aug 16 13:14  .claude.json.backup.1786853693316   ← これを使う
   50  Aug 16 16:50  .claude.json.backup.1786866620763   ← 空同然

必ずサイズを見てください。 一番新しいものが50バイトしかありません。これは未復元のまま起動してしまった際、初期状態が保存されたものです。これを戻しても何も復旧しません。

40KB前後の実データを選びます。

cp /root/.claude/backups/.claude.json.backup.1786853693316 /root/.claude.json

無事に会話履歴が戻りました。

セッションが見つからないときは

Claude Codeの履歴は 起動したディレクトリごとに分かれています。 /workspace で起動すると /workspace/second-brain での作業履歴は出てきません。

cd /workspace/second-brain
claude --resume

一覧画面では Ctrl+A で全プロジェクト横断表示、Space で内容のプレビューができます。

なお大きなセッションを開くと「要約から再開するか、フル復元するか」を聞かれます。フル復元は利用枠をかなり消費するので、通常は要約(推奨と表示される方)で十分です。


実践:複数ウィンドウが便利

tmuxを入れて一番効いたのが、この使い方でした。

Claude Codeを動かしたまま、別のシェルで作業したい場面があります。/exit すると会話が終わってしまう。そんなとき:

Ctrl-bc

新しいウィンドウが開きます。ステータスバーが [brain] 1:claude- 2:bash* に変わりました。

  • 1:claude- … Claude Codeは動いたまま
  • 2:bash* … いまここ

Ctrl-b1 でClaude Codeに戻り、Ctrl-b2 でシェルに戻る。行き来自由です。

私はこれでgitの操作をしました。Claude Codeに作業させながら、隣のウィンドウでコミット状況を確認する、といった使い方ができます。


日常の使い方

最終的に、こういう運用に落ち着きました。

NAS側の ~/.bashrc にエイリアスを登録しておきます。

echo "alias brain='sudo docker exec -it claude-code tmux -u attach -t brain'" >> ~/.bashrc
source ~/.bashrc

朝の始業:

ssh 管理者ID@192.168.1.26
brain

以上です。昨日の画面がそのまま出てきます。

終わるとき:Ctrl-bd、あるいは何もせずウィンドウを閉じるだけ。


まとめ

導入前導入後
タブを閉じるとCLI画面に戻れない閉じても入り直せる
ネットワークが切れると画面を失う切れても復帰できる
別作業のたびにClaude Codeを終了ウィンドウを分けて併用
コンテナ作り直しで環境が飛ぶStackで自動復元
日本語が化ける正常表示

作業時間は1時間ほど。tmuxのインストール自体は5分ですが、文字化けの切り分けと、Stackへの組み込みに時間を使いました。

とりあえず覚える操作方法

覚えるキーは3つで足ります。 Ctrl-b d(抜ける)、Ctrl-b c(新しいウィンドウ)、Ctrl-b 数字(切り替え)。他は必要になってから調べれば十分です。

exit を打たない習慣だけは最初に身につけないと。 これだけがtmuxを台無しにする操作です。

エラーが出たら、まず切り分けを。 今回の文字化けも、「tmuxの外で見たら正常だった」という一手で原因の範囲が一気に狭まりました。闇雲に設定をいじる前に、どこまでが正常でどこから異常なのかを確かめる方が結局は早道です。


次回は、この環境でObsidian Vaultをどう運用しているか、arscontextaというプラグインを使ったメモの蓄積について書く予定? です。