- Codex CLI (OpenAI) chạy được ở chế độ headless với cờ --full-auto, không cần REPL tương tác.
- Dev solo cắm task dài (refactor 200 file, viết test, migrate framework) cho Codex chạy trên VPS qua đêm.
- Tmux + systemd unit + queue đơn giản giúp xếp hàng nhiều task, không cần để laptop mở suốt đêm.
- Sáng dậy review diff, merge/discard, log đầy đủ trong /var/log/codex và Git branch riêng.
- Cost kiểm soát qua budget per session, max_turn, model_routing - đảm bảo không cháy 100 USD trong 1 đêm.
Bạn là dev solo đang build SaaS. 10 giờ tối, ý tưởng đến: "phải migrate cả codebase từ Pages Router sang App Router". Việc này 1 mình làm thủ công mất 2 tuần. Bạn nghe Codex CLI có thể tự sửa code bằng GPT-5. Nhưng nếu mở Codex trên Macbook ngồi tương tác, bạn phải thức cả đêm. Đi ngủ thì máy sleep, task dừng. Câu trả lời: chạy Codex ở chế độ headless trên 1 VPS, tách hoàn toàn khỏi máy local. Sáng dậy ssh vào, xem log, review diff.
Bài này hướng dẫn setup Codex CLI ở chế độ headless trên Cloud VPS, tích hợp tmux để giữ session, systemd để auto-restart, kèm cấu trúc queue đơn giản cho dev solo xếp task. Hướng vào use case dev cá nhân, không phải team enterprise. Tham khảo trải nghiệm thực tế của nhiều solo dev đang xài VPS TND làm "AI worker" cá nhân, ngoài giờ làm việc nó tự cày.
Headless là gì và khi nào cần?
Codex CLI có 3 chế độ: interactive (REPL hỏi-đáp), non-interactive (chạy 1 lệnh rồi exit), và full-auto (tự duyệt mọi quyền, không hỏi gì). Headless ở đây nghĩa là chế độ full-auto + chạy trong tmux/systemd, không cần TTY người dùng. Bạn ssh vào, gõ lệnh khởi tạo, ngắt ssh, task vẫn chạy.
Khi nào nên dùng headless thay vì REPL?
- Task dài quá 30 phút (migrate framework, port code, refactor architecture).
- Cần chạy nhiều task song song hoặc tuần tự (queue 5 ticket Jira cho Codex tự cày).
- Không muốn tốn pin laptop và RAM Macbook cho task background.
- Cần audit log đầy đủ về mỗi prompt, mỗi diff, mỗi tool call.
- Workflow CI/CD muốn trigger Codex từ Github webhook (sửa lỗi auto-fix).
Cấu hình VPS cho Codex worker
Codex CLI nhẹ về CPU và RAM, nhưng chạy lint/build/test thì có thể ngốn. Khuyến nghị tối thiểu:
| Workload | RAM | vCPU | SSD |
|---|---|---|---|
| 1 task nhỏ (Python script) | 2 GB | 2 vCPU | 20 GB |
| Refactor Next.js / React | 4 GB | 2-4 vCPU | 40 GB |
| Monorepo lớn + test suite | 8 GB | 4 vCPU | 80 GB |
| Multi-task song song + LLM local | 16 GB | 6-8 vCPU | 120 GB |
OS Ubuntu 24 LTS hoặc AlmaLinux 9 đều OK. Cài Node 22 (Codex CLI là npm package), git, tmux, jq. Tạo user thường codex, không chạy bằng root.
Cài Codex CLI và config API key
# Cài Node 22 LTS qua nvm hoặc fnm
curl -fsSL https://fnm.vercel.app/install | bash
exec $SHELL
fnm install 22
fnm use 22
# Cài Codex CLI
npm i -g @openai/codex
# Verify
codex --version
Set API key trong file ~/.codex/config.toml:
model = "gpt-5"
approval_policy = "never"
[providers.openai]
api_key = "sk-proj-your-key"
[shell_environment_policy]
inherit = "all"
approval_policy = never là điểm mấu chốt cho headless: Codex sẽ không hỏi xin phép từng action (đọc file, chạy lệnh, ghi file). Cực mạnh nhưng nguy hiểm, vì vậy hãy chạy trong sandbox và branch riêng.
Cấu trúc thư mục worker
/home/codex/
├── repos/ # clone repo của bạn vào đây
│ └── my-saas/
├── tasks/ # mỗi task là 1 file YAML
│ ├── 001-migrate-app-router.yaml
│ ├── 002-add-tests-auth.yaml
│ └── 003-refactor-db-models.yaml
├── runs/ # log từng lần chạy
│ ├── 001-2026-06-04-23-10/
│ └── 002-2026-06-04-23-45/
├── bin/ # scripts queue runner
│ ├── run-task.sh
│ └── worker-loop.sh
└── .codex/
└── config.toml
Mỗi task có file YAML mô tả: prompt, repo, branch, model, max_turn, timeout. Worker đọc thư mục tasks/ theo thứ tự, chạy lần lượt, ghi kết quả vào runs/. Đơn giản, không cần Celery/Redis Queue cho dev solo.
File task YAML mẫu
id: "001-migrate-app-router"
repo: "/home/codex/repos/my-saas"
base_branch: main
work_branch: "codex/migrate-app-router"
model: gpt-5
max_turns: 80
timeout_minutes: 180
sandbox: workspace-write
prompt: |
Migrate this Next.js project from Pages Router (pages/) to App Router (app/).
- Keep existing API routes working.
- Convert getServerSideProps to Server Components + fetch.
- Migrate _app.tsx logic to root layout.tsx.
- Update next.config.js if needed.
- Run npm run build at the end and fix any error.
- Commit small chunks per concern (auth, dashboard, settings, api).
success_check: "npm run build"
Script run-task.sh: chạy 1 task
#!/usr/bin/env bash
set -euo pipefail
TASK_FILE="$1"
if [ -z "$TASK_FILE" ]; then
echo "Usage: run-task.sh tasks/001-migrate.yaml"
exit 1
fi
TASK_ID=$(yq -r '.id' "$TASK_FILE")
REPO=$(yq -r '.repo' "$TASK_FILE")
BASE_BRANCH=$(yq -r '.base_branch' "$TASK_FILE")
WORK_BRANCH=$(yq -r '.work_branch' "$TASK_FILE")
MODEL=$(yq -r '.model' "$TASK_FILE")
MAX_TURNS=$(yq -r '.max_turns' "$TASK_FILE")
TIMEOUT_MIN=$(yq -r '.timeout_minutes' "$TASK_FILE")
PROMPT=$(yq -r '.prompt' "$TASK_FILE")
SUCCESS=$(yq -r '.success_check' "$TASK_FILE")
RUN_DIR="/home/codex/runs/${TASK_ID}-$(date +%F-%H-%M)"
mkdir -p "$RUN_DIR"
cd "$REPO"
git fetch origin
git checkout -B "$WORK_BRANCH" "origin/$BASE_BRANCH"
echo "Starting task $TASK_ID at $(date)" | tee "$RUN_DIR/start.log"
timeout "${TIMEOUT_MIN}m" codex exec
--model "$MODEL"
--sandbox workspace-write
--approval-mode never
--output-format json
--max-turns "$MAX_TURNS"
--output-last-message "$RUN_DIR/last.md"
--json-log "$RUN_DIR/codex.jsonl"
"$PROMPT" 2>&1 | tee "$RUN_DIR/codex.log"
echo "Task done at $(date)" | tee -a "$RUN_DIR/start.log"
# Verify success
if eval "$SUCCESS" > "$RUN_DIR/success.log" 2>&1; then
echo "SUCCESS" > "$RUN_DIR/STATUS"
git push origin "$WORK_BRANCH"
else
echo "FAILED" > "$RUN_DIR/STATUS"
fi
# Move task out of queue
mv "$TASK_FILE" "/home/codex/tasks-done/"
Script này gói gọn: pull base branch, tạo work branch, gọi Codex headless với timeout, log mọi thứ, chạy success_check (vd npm run build), push branch nếu OK, di chuyển task ra khỏi queue. Cẩn thận quyền: sandbox workspace-write giới hạn Codex chỉ ghi trong repo, không động được file ngoài.
Worker loop liên tục
#!/usr/bin/env bash
# /home/codex/bin/worker-loop.sh
set -u
QUEUE_DIR="/home/codex/tasks"
SLEEP_SEC=10
while true; do
TASK=$(ls "$QUEUE_DIR"/*.yaml 2>/dev/null | head -n 1)
if [ -n "$TASK" ]; then
echo "[$(date)] Picking $TASK"
/home/codex/bin/run-task.sh "$TASK" || echo "Task failed: $TASK"
else
sleep "$SLEEP_SEC"
fi
done
Systemd unit để worker tự khởi động
Đặt file /etc/systemd/system/codex-worker.service:
[Unit]
Description=Codex headless task worker
After=network-online.target
[Service]
Type=simple
User=codex
WorkingDirectory=/home/codex
Environment="HOME=/home/codex"
Environment="PATH=/home/codex/.fnm/aliases/default/bin:/usr/local/bin:/usr/bin:/bin"
ExecStart=/home/codex/bin/worker-loop.sh
Restart=always
RestartSec=15
StandardOutput=append:/var/log/codex/worker.log
StandardError=append:/var/log/codex/worker.err
[Install]
WantedBy=multi-user.target
Enable: sudo systemctl daemon-reload && sudo systemctl enable --now codex-worker. Worker chạy 24/7, mỗi 10s scan queue, gặp task thì xử lý ngay. Drop file YAML vào /home/codex/tasks/ là task tự chạy.
Submit task từ Macbook qua scp
# Tạo task local
cat > /tmp/004-add-pricing-table.yaml
Bạn submit task xong tắt máy, đi chơi. Sáng dậy, ssh vào, xem ls /home/codex/runs/004-*, đọc STATUS. Nếu SUCCESS thì git fetch + checkout branch codex/add-pricing-table để review trên Macbook. Diff vài file, merge hoặc bỏ.
Sandbox và an toàn
Chạy Codex full-auto trên server là rủi ro: nó có thể chạy rm -rf, gọi network, đọc env. Codex CLI có 3 sandbox level:
- read-only: chỉ đọc, không ghi gì. Dùng cho task review code.
- workspace-write: chỉ ghi trong cwd, không vào /etc /home khác. Mặc định cho coding task.
- danger-full-access: toàn quyền, chỉ dùng khi container/VM cô lập.
Khuyến nghị bổ sung: chạy worker trong systemd với SystemCallFilter, ProtectSystem=strict, NoNewPrivileges, ReadOnlyPaths cho /etc và /usr. Để paranoid hơn, chạy trong Docker container với volume mount giới hạn:
docker run --rm -it
-v /home/codex/repos:/work
-v /home/codex/.codex:/home/node/.codex:ro
--network bridge
--memory 4g
--cpus 2
node:22 bash -c "npm i -g @openai/codex && codex exec --sandbox workspace-write ..."
Cost control
GPT-5 input 5 USD / 1M token, output 15 USD / 1M token (giá tham khảo, kiểm tra trang OpenAI cho số mới nhất). 1 task refactor lớn có thể tốn 5-10 USD. Để không cháy budget:
- Set
max_turnstrong YAML (vd 80). Codex sẽ tự dừng sau N turn. - Set
timeout_minutesvớitimeoutbinary của Linux, force kill nếu quá lâu. - Route task nhỏ sang gpt-5-mini hoặc gpt-5-nano qua field
modeltrong YAML. - Xem chi tiêu hằng ngày qua OpenAI Dashboard, set spend limit toàn account.
- Cache prompt context bằng OpenAI Prompt Caching: hệ thống prompt cố định được cache, giảm 50% input cost.
Audit log và review diff
Sau mỗi task, thư mục runs/{id}/ chứa:
codex.jsonl: JSON log mỗi event (tool call, model response, file write).codex.log: stdout/stderr text-friendly.last.md: tin nhắn cuối cùng Codex viết (summary).success.log: output của lệnh success_check.STATUS: file 1 dòng SUCCESS hoặc FAILED.
Review nhanh bằng grep + jq:
# Đếm số lần Codex chạy lệnh shell
jq -r 'select(.type == "tool_use" and .name == "shell") | .input.command' runs/001-*/codex.jsonl | wc -l
# Liệt kê file đã được sửa
jq -r 'select(.type == "tool_use" and .name == "edit_file") | .input.path' runs/001-*/codex.jsonl | sort -u
# Xem 10 lệnh shell cuối
jq -r 'select(.type == "tool_use" and .name == "shell") | .input.command' runs/001-*/codex.jsonl | tail -10
Bẫy thường gặp
| Triệu chứng | Nguyên nhân | Cách fix |
|---|---|---|
| Codex dừng giữa chừng không lý do | Vượt max_turn hoặc timeout | Tăng max_turn lên 100-150, tăng timeout |
| Cost cháy 50 USD trong 1 task | Prompt quá rộng, Codex đọc cả repo | Giới hạn prompt: chỉ chỉ định folder cụ thể |
| npm install bị block | Sandbox không cho network | Pre-install trước khi gọi Codex, hoặc dùng sandbox open-network |
| Worker chết im lặng | Out of memory khi build | Thêm swap 4GB, hoặc nâng RAM VPS |
| Branch conflict khi push | Code base branch thay đổi giữa chừng | Rebase trước push, hoặc dùng work branch isolated |
Có 1 con VPS làm AI worker chạy đêm thay laptop
Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Gói 4-8GB RAM thoải mái cho Codex CLI + tmux + monorepo build, dev solo không cần để Macbook mở qua đêm.
Xem 8 cấu hình Cloud VPS →FAQ
Codex CLI khác Claude Code thế nào?
Codex là CLI agent của OpenAI dùng GPT-5/GPT-5-mini, Claude Code là CLI agent của Anthropic dùng Claude Opus/Sonnet. Cấu trúc workflow tương tự (đọc file, chạy shell, edit file), cú pháp lệnh khác nhau. Codex mạnh khi task code Python/JS phổ biến, Claude Code thường viết code clean hơn cho refactor lớn. Có thể chạy cả 2 trên cùng VPS với 2 worker khác nhau, mỗi worker dùng API key riêng.
Có cần Pro subscription không hay chỉ cần API key?
Codex CLI chạy được với chỉ API key OpenAI (pay-per-token), không bắt buộc ChatGPT Plus/Pro. Tuy nhiên nếu có ChatGPT Pro thì có quota Codex riêng (tham khảo trang OpenAI). Với dev solo chạy task qua đêm, API pay-per-use thường linh hoạt hơn vì cost theo task thay vì flat fee.
Có cách nào dừng task đang chạy nửa chừng?
SSH vào VPS, gõ ps aux | grep codex, lấy PID rồi kill PID. Hoặc sudo systemctl stop codex-worker để dừng cả worker loop. Để hủy task chưa chạy, xóa file YAML khỏi /home/codex/tasks/. Worker scan lại 10s sau là không thấy nữa, sẽ ngủ.
Codex có thể commit và push trực tiếp không?
Có. Nếu prompt yêu cầu commit, Codex sẽ chạy git add + git commit. Push thì cần cấu hình deploy key trên VPS có quyền write vào repo. Khuyến nghị: chỉ push lên branch riêng (codex/...) chứ không push thẳng vào main. Mở PR trên Github để bạn review trước khi merge.
Có thể chạy nhiều Codex song song trên 1 VPS không?
Có. Tạo nhiều systemd unit (codex-worker-1, codex-worker-2, ...) mỗi unit chỉ vào 1 queue dir riêng. Hoặc đơn giản hơn: 1 worker xử lý tuần tự cũng đủ cho dev solo, vì sáng dậy bạn cũng chỉ review nổi 2-3 PR. Chạy song song chỉ tiết kiệm thời gian, không tiết kiệm cost (vẫn trả token theo task).

