Skip to content

Instantly share code, notes, and snippets.

@tamaina
Last active May 22, 2026 20:38
Show Gist options
  • Select an option

  • Save tamaina/116c7287943a7370aab504667c3bed5a to your computer and use it in GitHub Desktop.

Select an option

Save tamaina/116c7287943a7370aab504667c3bed5a to your computer and use it in GitHub Desktop.
CodexをDockerコンテナ内で起動→ remote_control でスマホから開発✨

CodexをDockerコンテナ内で起動→ remote_control でスマホから開発✨

ChatGPT先生の丸パクリです

導入

  1. ホスト: Dockerを導入
  2. ホスト: 専用ディレクトリ追加(例: ~/codex-docker)
  3. ホスト: このgistにあるファイルを全て追加
    devcontainer.jsonは.devcontainer/に入れる
  4. ホスト: 専用ディレクトリ~/codex-dockerでターミナルを開く (cd ~/codex-docker)
  5. ホスト: chmod +x setup.sh entrypoint.sh exec-codex.sh start-remote.sh stop-remote.sh status-remote.sh
  6. chatgpt.com: 設定 - セキュリティ - Codex に対してデバイスコード認証を有効にする をオンに
  7. (リモートコントロールしたい場合) ChatGPTアプリのCodexを開き、「デスクトップで接続済み」を押しておく
  8. ホスト: ./setup.sh
  9. ホスト: ./exec-codex.sh codex login --device-auth
    → ログインしてください

ghコマンドでログイン

./exec-codex.sh gh auth login

リモートコントロールを開始

./start-remote.sh

リモートコントロールでトラブルが出たら

まずDevice Codeでログインし直します。

./stop-remote.sh
./exec-codex.sh codex login --device-auth # ログインし直す
./start-remote.sh
それでも直らない場合

Codexのローカル状態DB、auth.jsonを退避して再起動します。

./stop-remote.sh

./exec-codex.sh bash -lc '
cd "$HOME/.codex"
for f in state_*.sqlite; do
  [ -e "$f" ] || continue
  mv "$f" "$f.disabled.$(date +%Y%m%d-%H%M%S)"
done
'
./exec-codex.sh bash -lc '
mv "$HOME/.codex/auth.json" "$HOME/.codex/auth.json.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true
'
./start-remote.sh

コンテナ内でコマンドを実行

例: codex --version

./exec-codex.sh codex --version

VS Codeで開く

既存コンテナにAttachする場合

  1. VS Codeでコマンドパレットを開く
  2. Dev Containers: Attach to Running Container...
  3. codex-dev を選択
  4. コンテナ内で /home/codex/work を開く

devcontainer.jsonを使う場合

  1. VS Codeで ~/codex-docker を開く
  2. Dev Containers: Reopen in Container を実行
services:
codex:
build:
context: .
args:
UID: ${HOST_UID:-1000}
GID: ${HOST_GID:-1000}
container_name: codex-dev
hostname: codex-dev
init: true
tty: true
stdin_open: true
working_dir: /home/codex
command: ["sleep", "infinity"]
restart: unless-stopped
volumes:
- codex-home:/home/codex
- ~/.ssh:/home/codex/.ssh:ro
environment:
- USERNAME=codex
- HOME=/home/codex
- USER=codex
- LOGNAME=codex
- XDG_CONFIG_HOME=/home/codex/.config
- TZ=Asia/Tokyo
- SHELL=/bin/bash
- EDITOR=vim
- PATH=/home/codex/.local/bin:/home/codex/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
extra_hosts:
- host.docker.internal:host-gateway
volumes:
codex-home:
{
"name": "codex-dev",
"dockerComposeFile": "../compose.yml",
"service": "codex",
"workspaceFolder": "/home/codex",
"remoteUser": "codex",
"shutdownAction": "none",
"overrideCommand": false,
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"files.exclude": {
"**/.cache": true,
"**/.codex": true,
"**/.config": true,
"**/.local": true,
"**/.vscode-server": true,
"**/.ssh": true
},
"search.exclude": {
"**/.cache": true,
"**/.codex": true,
"**/.config": true,
"**/.local": true,
"**/.vscode-server": true,
"**/.ssh": true
},
"files.watcherExclude": {
"**/.cache/**": true,
"**/.codex/**": true,
"**/.config/**": true,
"**/.local/**": true,
"**/.vscode-server/**": true,
"**/.ssh/**": true
}
},
"extensions": [
"ms-azuretools.vscode-docker"
]
}
}
}
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
bash \
ca-certificates \
curl \
wget \
git \
git-lfs \
jq \
less \
nano \
vim \
ripgrep \
fd-find \
tree \
htop \
procps \
psmisc \
lsof \
file \
unzip \
zip \
xz-utils \
tar \
gzip \
build-essential \
make \
gcc \
g++ \
python3 \
python3-pip \
python3-venv \
pkg-config \
openssh-client \
sudo \
gosu \
bubblewrap \
&& mkdir -p -m 755 /etc/apt/keyrings \
&& wget -nv -O /tmp/githubcli-archive-keyring.gpg https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& cat /tmp/githubcli-archive-keyring.gpg > /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& mkdir -p -m 755 /etc/apt/sources.list.d \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \
&& apt-get update \
&& apt-get install -y gh \
&& rm -f /tmp/githubcli-archive-keyring.gpg \
&& rm -rf /var/lib/apt/lists/*
RUN ln -s /usr/bin/fdfind /usr/local/bin/fd || true
ARG USERNAME=codex
ARG UID=1000
ARG GID=1000
RUN groupadd --gid ${GID} ${USERNAME} \
&& useradd --uid ${UID} --gid ${GID} -m -s /bin/bash ${USERNAME} \
&& echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USERNAME} \
&& chmod 0440 /etc/sudoers.d/${USERNAME} \
&& mkdir -p /home/${USERNAME} \
&& chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}
COPY --chmod=0755 entrypoint.sh /usr/local/bin/codex-entrypoint
ENV PATH="/home/${USERNAME}/.local/bin:/home/${USERNAME}/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
USER root
WORKDIR /home/${USERNAME}
ENTRYPOINT ["codex-entrypoint"]
CMD ["/bin/bash"]
#!/usr/bin/env bash
set -euo pipefail
USERNAME="${USERNAME:-codex}"
USER_HOME="/home/${USERNAME}"
export HOME="$USER_HOME"
export USER="$USERNAME"
export LOGNAME="$USERNAME"
export XDG_CONFIG_HOME="$USER_HOME/.config"
export SHELL="${SHELL:-/bin/bash}"
export PATH="$USER_HOME/.local/bin:$USER_HOME/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
mkdir -p \
"$USER_HOME/.codex" \
"$USER_HOME/.local/bin" \
"$USER_HOME/.local/share" \
"$USER_HOME/.cache" \
"$USER_HOME/.config" \
"$USER_HOME/.config/git" \
"$USER_HOME/.config/gh" \
"$USER_HOME/.vscode-server" \
"$USER_HOME/repos"
# /home/codex は永続volumeとして扱う。
# dot dirs は権限ズレが起きやすいので補正する。
chown -R "$USERNAME:$USERNAME" \
"$USER_HOME/.codex" \
"$USER_HOME/.local" \
"$USER_HOME/.cache" \
"$USER_HOME/.config" \
"$USER_HOME/.vscode-server" || true
chown "$USERNAME:$USERNAME" "$USER_HOME" "$USER_HOME/repos" || true
if [ "$(id -un)" != "$USERNAME" ]; then
exec gosu "$USERNAME" "$0" "$@"
fi
export HOME="$USER_HOME"
export USER="$USERNAME"
export LOGNAME="$USERNAME"
export XDG_CONFIG_HOME="$USER_HOME/.config"
export SHELL="${SHELL:-/bin/bash}"
export PATH="$USER_HOME/.local/bin:$USER_HOME/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ensure_bashrc() {
touch "$HOME/.bashrc"
if ! grep -q 'mise activate bash' "$HOME/.bashrc"; then
{
echo ''
echo '# mise'
echo 'if [ -x "$HOME/.local/bin/mise" ]; then'
echo ' eval "$("$HOME/.local/bin/mise" activate bash)"'
echo 'fi'
} >> "$HOME/.bashrc"
fi
if ! grep -q 'XDG_CONFIG_HOME="$HOME/.config"' "$HOME/.bashrc"; then
{
echo ''
echo '# local environment'
echo 'export USER="${USER:-codex}"'
echo 'export LOGNAME="${LOGNAME:-codex}"'
echo 'export XDG_CONFIG_HOME="$HOME/.config"'
echo 'export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH"'
} >> "$HOME/.bashrc"
fi
}
ensure_mise_and_node() {
if [ ! -x "$HOME/.local/bin/mise" ]; then
echo "Installing mise..."
curl -fsSL https://mise.run | sh
fi
ensure_bashrc
export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH"
if ! command -v node >/dev/null 2>&1; then
echo "Installing Node.js via mise..."
"$HOME/.local/bin/mise" use -g node@lts
fi
if command -v corepack >/dev/null 2>&1; then
corepack enable || true
fi
}
install_codex_if_needed() {
if command -v codex >/dev/null 2>&1 && codex --version >/dev/null 2>&1; then
return 0
fi
echo "Installing Codex standalone..."
curl -fsSL https://chatgpt.com/codex/install.sh -o /tmp/codex-install.sh
sh /tmp/codex-install.sh < /dev/null
rm -f /tmp/codex-install.sh
}
ensure_git_config_is_safe() {
# ホスト由来の絶対パス include があると、git/gh が Permission denied で落ちることがある。
# 既知の Netlify helper include だけ削る。
if [ -f "$HOME/.gitconfig" ] && grep -q '/home/ubuntu/.config/netlify/helper/git-config' "$HOME/.gitconfig"; then
echo "Removing stale Netlify Git config include from ~/.gitconfig..."
cp "$HOME/.gitconfig" "$HOME/.gitconfig.bak.$(date +%Y%m%d-%H%M%S)"
python3 - <<'PY'
from pathlib import Path
path = Path.home() / ".gitconfig"
lines = path.read_text().splitlines()
out = []
i = 0
while i < len(lines):
stripped = lines[i].strip()
if stripped.startswith("# This next lines include Netlify"):
i += 1
continue
if stripped == "[include]" and i + 1 < len(lines):
next_stripped = lines[i + 1].strip()
if next_stripped == "path = /home/ubuntu/.config/netlify/helper/git-config":
i += 2
continue
out.append(lines[i])
i += 1
path.write_text("\n".join(out).rstrip() + "\n")
PY
fi
}
ensure_mise_and_node
install_codex_if_needed
ensure_git_config_is_safe
exec "$@"
#!/usr/bin/env bash
set -eu
docker compose exec -u codex \
-e HOME=/home/codex \
-e USER=codex \
-e LOGNAME=codex \
-e XDG_CONFIG_HOME=/home/codex/.config \
-e SHELL=/bin/bash \
codex bash -lc '
export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
exec "$@"
' bash "$@"
#!/usr/bin/env bash
set -eu
export HOST_UID="$(id -u)"
export HOST_GID="$(id -g)"
docker compose up -d --build
echo "Waiting for Codex CLI..."
for i in $(seq 1 120); do
if docker compose exec -T -u codex \
-e HOME=/home/codex \
-e USER=codex \
-e LOGNAME=codex \
-e XDG_CONFIG_HOME=/home/codex/.config \
-e SHELL=/bin/bash \
codex bash -lc '
export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
command -v codex >/dev/null 2>&1
codex --version >/dev/null 2>&1
'
then
echo "Codex CLI is ready."
break
fi
if [ "$i" -eq 120 ]; then
echo "Codex CLI did not become ready." >&2
docker compose logs codex --tail=200 >&2 || true
exit 1
fi
sleep 1
done
docker compose exec -u codex \
-e HOME=/home/codex \
-e USER=codex \
-e LOGNAME=codex \
-e XDG_CONFIG_HOME=/home/codex/.config \
-e SHELL=/bin/bash \
codex bash -lc '
set -e
export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
echo "user=$(whoami)"
echo "pwd=$(pwd)"
echo "HOME=$HOME"
echo "XDG_CONFIG_HOME=$XDG_CONFIG_HOME"
echo "codex=$(command -v codex)"
codex --version
echo "node=$(command -v node || true)"
node --version || true
echo "mise=$(command -v mise || true)"
mise --version || true
echo "gh=$(command -v gh || true)"
gh --version | head -n 1 || true
echo "git=$(command -v git || true)"
git --version || true
echo
echo "Recommended workspace directory: /home/codex/repos"
'
#!/usr/bin/env bash
set -eu
export HOST_UID="$(id -u)"
export HOST_GID="$(id -g)"
docker compose up -d --build
./exec-codex.sh bash -lc '
set -e
export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
if ! codex login status >/dev/null 2>&1; then
echo "Codex is not logged in, or the token is invalid."
echo "Run: ./exec-codex.sh codex login --device-auth"
exit 1
fi
codex features enable remote_control || true
codex remote-control stop 2>/dev/null || true
codex remote-control start
echo
echo "Remote control processes:"
ps -eo user,pid,ppid,cmd | grep -E "app-server|remote-control" | grep -v grep || true
'
#!/usr/bin/env bash
set -eu
./exec-codex.sh bash -lc '
echo "== login =="
codex login status || true
echo
echo "== remote-control processes =="
ps -eo user,pid,ppid,cmd | grep -E "app-server|remote-control" | grep -v grep || true
echo
echo "== codex files =="
ls -la "$HOME/.codex" | sed -n "1,80p"
echo
echo "== state files =="
ls -la "$HOME/.codex"/state_*.sqlite 2>/dev/null || true
'
#!/usr/bin/env bash
set -eu
./exec-codex.sh bash -lc '
codex remote-control stop 2>/dev/null || true
pkill -f "codex app-server daemon" 2>/dev/null || true
pkill -f "codex app-server --remote-control" 2>/dev/null || true
'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment