{"id":1910,"date":"2026-08-30T10:30:40","date_gmt":"2026-08-30T02:30:40","guid":{"rendered":"https:\/\/skypray.synology.me\/blog\/?p=1910"},"modified":"2026-09-09T11:00:36","modified_gmt":"2026-09-09T03:00:36","slug":"synology-nas-codex-claude-code-cli-docker","status":"publish","type":"post","link":"https:\/\/skypray.synology.me\/blog\/computer\/synology-nas-codex-claude-code-cli-docker\/","title":{"rendered":"Synology NAS\u7528\u4e00\u4efd Docker \u6620\u50cf\u540c\u6642\u8dd1 Codex CLI \u8207 Claude Code\uff1a\u5f9e\u96f6\u958b\u59cb\u7684\u96d9 AI \u7d42\u7aef\u5171\u5b58\u6559\u5b78"},"content":{"rendered":"<p><!-- Blogger \/ WordPress article fragment: paste in HTML or Code editor --><\/p>\n<style>\n.cc-guide{--cc-ink:#162033;--cc-muted:#5f6b7a;--cc-paper:#fff;--cc-wash:#f3f6fa;--cc-line:#dfe5ec;--cc-blue:#1261a6;--cc-cyan:#e9f6ff;--cc-warn:#fff5d6;--cc-danger:#fff0ef;--cc-ok:#e7f8ee;--cc-code:#101827;--cc-code-ink:#e6edf7;max-width:100%;margin:0;color:var(--cc-ink);font:17px\/1.8 system-ui,-apple-system,\"Noto Sans TC\",\"PingFang TC\",sans-serif;overflow-wrap:break-word}\n.cc-guide,.cc-guide *{box-sizing:border-box}\n.cc-guide a{color:var(--cc-blue);text-underline-offset:3px;overflow-wrap:anywhere}\n.cc-guide .cc-hero{max-width:100%;background:linear-gradient(125deg,#0d2741,#125f96);color:#fff;padding:52px clamp(18px,5vw,48px);border-radius:16px 16px 0 0}\n.cc-guide .cc-inner,.cc-guide .cc-main{width:100%;max-width:960px;margin-right:auto;margin-left:auto}\n.cc-guide .cc-eyebrow{font-size:.85rem;letter-spacing:.12em;text-transform:uppercase;color:#9ed7ff}\n.cc-guide .cc-title{margin:.3em 0;font-size:clamp(1.8rem,5vw,3.1rem);font-weight:750;line-height:1.2;color:#fff}\n.cc-guide .cc-hero .cc-lead{max-width:820px;margin-bottom:0;font-size:1.12rem;color:#d8ebfa!important}\n.cc-guide .cc-main{max-width:100%;padding:38px clamp(16px,5vw,54px);background:var(--cc-paper);border:1px solid var(--cc-line);border-top:0}\n.cc-guide h2{margin-top:2.2em;padding-top:.4em;border-top:1px solid var(--cc-line);font-size:1.65rem;line-height:1.4}\n.cc-guide h3{margin-top:1.8em;line-height:1.45}\n.cc-guide code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.92em}\n.cc-guide :not(pre)>code{padding:.15em .4em;border-radius:5px;background:#edf2f7;color:#243247;overflow-wrap:anywhere}\n.cc-guide pre{max-width:100%;margin:1.2em 0;padding:18px;border-radius:10px;background:var(--cc-code);color:var(--cc-code-ink);font-size:.84rem;line-height:1.55;white-space:pre;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.cc-guide pre code{padding:0;background:transparent;color:inherit;white-space:inherit;overflow-wrap:normal}\n.cc-guide nav{max-width:100%;padding:18px 22px;border:1px solid var(--cc-line);border-radius:12px;background:#f7faff}\n.cc-guide nav ol{margin-bottom:0;columns:2;column-gap:32px}\n.cc-guide nav li{margin:.35em 0;break-inside:avoid}\n.cc-guide .cc-note,.cc-guide .cc-warning,.cc-guide .cc-danger,.cc-guide .cc-ok{max-width:100%;margin:1.4em 0;padding:15px 18px;border-left:5px solid var(--cc-blue);border-radius:7px;background:var(--cc-cyan)}\n.cc-guide .cc-warning{border-color:#c98900;background:var(--cc-warn)}\n.cc-guide .cc-danger{border-color:#c53b31;background:var(--cc-danger)}\n.cc-guide .cc-ok{border-color:#1f9d57;background:var(--cc-ok)}\n.cc-guide table{display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.cc-guide th,.cc-guide td{padding:10px 12px;border:1px solid var(--cc-line);text-align:left;white-space:nowrap}\n.cc-guide th{background:#edf4fa}\n.cc-guide dl.cc-gloss{max-width:100%;padding:17px 20px;border:1px solid var(--cc-line);border-radius:12px;background:#f7faff}\n.cc-guide dl.cc-gloss dt{margin-top:.9em;font-weight:700}\n.cc-guide dl.cc-gloss dt:first-child{margin-top:0}\n.cc-guide dl.cc-gloss dd{margin:.2em 0 0;color:#33404f}\n.cc-guide .cc-diagram{max-width:100%;margin:20px 0;padding:18px;border:1px solid var(--cc-line);border-radius:12px;background:#fafcff;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.cc-guide .cc-diagram pre{width:max-content;min-width:100%;margin:0;padding:0;background:transparent;color:var(--cc-ink);font-size:.8rem}\n.cc-guide .cc-flow{max-width:100%;margin:20px 0;padding:24px;border:1px solid var(--cc-line);border-radius:12px;background:#fafcff;color:var(--cc-ink)}\n.cc-guide .cc-flow-source{display:flex;align-items:center;justify-content:center;gap:14px}\n.cc-guide .cc-flow-label{font-weight:700;text-align:right}\n.cc-guide .cc-flow-label small{display:block;font-weight:400;color:var(--cc-muted)}\n.cc-guide .cc-flow-arrow{font-size:1.4rem;color:var(--cc-blue)}\n.cc-guide .cc-flow-image{min-width:270px;padding:14px 18px;border:2px solid #5b6b7c;border-radius:8px;background:#fff;text-align:center}\n.cc-guide .cc-flow-image strong{display:block;margin-bottom:4px;color:var(--cc-ink)}\n.cc-guide .cc-flow-image span{display:block;color:#33404f}\n.cc-guide .cc-flow-down{display:flex;flex-direction:column;align-items:center;margin:8px 0 18px;color:#33404f;text-align:center}\n.cc-guide .cc-flow-down b{font-size:1.35rem;line-height:1;color:var(--cc-blue)}\n.cc-guide .cc-flow-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;position:relative;padding-top:18px}\n.cc-guide .cc-flow-grid:before{content:\"\";position:absolute;top:0;right:15%;left:15%;border-top:2px solid #8ba0b5}\n.cc-guide .cc-flow-service{position:relative;min-width:0;padding:14px;border:1px solid #8ba0b5;border-radius:8px;background:#fff;text-align:center}\n.cc-guide .cc-flow-service:before{content:\"\";position:absolute;top:-20px;left:50%;height:18px;border-left:2px solid #8ba0b5}\n.cc-guide .cc-flow-service h4{margin:0 0 8px;color:var(--cc-blue);font-size:1rem}\n.cc-guide .cc-flow-service ul{margin:0;padding:0;list-style:none}\n.cc-guide .cc-flow-service li{margin:3px 0;overflow-wrap:anywhere}\n.cc-guide .cc-flow-route{margin-top:12px;padding-top:10px;border-top:1px dashed var(--cc-line);color:#33404f}\n.cc-guide .cc-flow-route strong,.cc-guide .cc-flow-route code{display:block;margin-top:4px}\n.cc-guide img{display:block;width:auto;max-width:100%;height:auto;margin-right:auto;margin-left:auto}\n.cc-guide .wp-caption,.cc-guide figure{width:auto!important;max-width:100%;margin:1.5em auto;text-align:center}\n.cc-guide .wp-caption img,.cc-guide figure img{margin-bottom:8px}\n.cc-guide .wp-caption-text,.cc-guide figcaption{margin:0;color:var(--cc-muted);font-size:.9rem;line-height:1.55;text-align:center}\n.cc-guide .cc-checklist li{margin:.45em 0}\n.cc-guide .cc-step{display:inline-block;margin-right:.5em;padding:.05em .7em;border-radius:999px;background:var(--cc-blue);color:#fff;font-size:.85rem;vertical-align:.1em}\n.cc-guide .cc-footer{max-width:100%;padding:20px 24px;border:1px solid var(--cc-line);border-top:0;border-radius:0 0 16px 16px;background:var(--cc-wash);color:var(--cc-muted);text-align:center}\n.cc-guide .cc-footer p{margin:0}\n@media(max-width:680px){.cc-guide{font-size:16px;line-height:1.75}.cc-guide .cc-hero{padding:36px 18px;border-radius:10px 10px 0 0}.cc-guide .cc-main{padding:24px 14px}.cc-guide nav{padding:16px}.cc-guide nav ol{padding-left:1.35em;columns:1}.cc-guide h2{font-size:1.4rem}.cc-guide h3{font-size:1.15rem}.cc-guide pre{padding:14px;font-size:.78rem}.cc-guide th,.cc-guide td{padding:9px 10px}.cc-guide dl.cc-gloss{padding:15px}.cc-guide dl.cc-gloss dd{margin-left:0}.cc-guide .cc-diagram{padding:12px}.cc-guide .cc-flow{padding:16px 12px}.cc-guide .cc-flow-source{flex-wrap:wrap;gap:8px}.cc-guide .cc-flow-label{width:100%;text-align:center}.cc-guide .cc-flow-arrow{transform:rotate(90deg)}.cc-guide .cc-flow-image{width:100%;min-width:0}.cc-guide .cc-flow-grid{grid-template-columns:1fr;gap:26px;padding-top:0}.cc-guide .cc-flow-grid:before{display:none}.cc-guide .cc-flow-service:before{top:-27px;height:25px}.cc-guide .cc-flow-service:first-child:before{display:none}}\n<\/style>\n<article class=\"cc-guide\">\n<div class=\"cc-hero\">\n<div class=\"cc-inner\">\n<div class=\"cc-eyebrow\">Docker \u00d7 AI CLI \u5be6\u6230\uff0e\u521d\u5b78\u8005\u7248<\/div>\n<div class=\"cc-title\">\u4e00\u4efd Docker \u6620\u50cf\uff0c\u5169\u5957 AI CLI\uff1aCodex \u8207 Claude Code \u5f9e\u96f6\u5171\u5b58<\/div>\n<p class=\"cc-lead\">\u5148\u82b1\u4e94\u5206\u9418\u5efa\u7acb\u6b63\u78ba\u7684\u5fc3\u667a\u6a21\u578b\uff0c\u518d\u4e00\u6b65\u6b65\u628a\u820a\u7684 Codex Docker \u76ee\u9304\u642c\u79fb\u3001\u6539\u540d\u70ba <code>codex-claude<\/code>\uff0c\u7528\u540c\u4e00\u4efd Dockerfile \u88dd\u597d\u5169\u5957 CLI\uff0c\u8b93 Codex \u8d70 7681\u3001Claude \u8d70 7682\uff0c\u5404\u81ea\u4fdd\u7559\u767b\u5165\u72c0\u614b\uff0c\u4e26\u7528\u4e00\u500b\u53d7\u9650\u7684\u5c0f\u670d\u52d9\u5b89\u5168\u5730\u63a7\u5236\u5176\u4ed6\u5c08\u6848\u3002<\/p>\n<\/div>\n<\/div>\n<div class=\"cc-main\">\n<nav aria-label=\"\u6587\u7ae0\u76ee\u9304\">\n<strong>\u76ee\u9304<\/strong><\/p>\n<ol>\n<li><a href=\"#goal\">\u9019\u7bc7\u6587\u7ae0\u8981\u5e6b\u4f60\u5b8c\u6210\u4ec0\u9ebc<\/a><\/li>\n<li><a href=\"#model\">\u5148\u5efa\u7acb\u6b63\u78ba\u7684\u5fc3\u667a\u6a21\u578b<\/a><\/li>\n<li><a href=\"#glossary\">\u540d\u8a5e\u901f\u67e5\u8868<\/a><\/li>\n<li><a href=\"#layout\">\u6700\u7d42\u7684\u76ee\u9304\u7d50\u69cb\u9577\u4ec0\u9ebc\u6a23<\/a><\/li>\n<li><a href=\"#backup\">\u642c\u79fb\u524d\uff1a\u5099\u4efd\u820a Codex \u76ee\u9304<\/a><\/li>\n<li><a href=\"#rename\">\u505c\u6b62\u670d\u52d9\u3001\u642c\u79fb\u4e26\u6539\u540d<\/a><\/li>\n<li><a href=\"#dockerfile\">\u4e00\u4efd Dockerfile \u88dd\u5169\u5957 CLI<\/a><\/li>\n<li><a href=\"#compose-min\">\u6700\u5c0f\u53ef\u884c\u7248\uff1aCompose \u5169\u500b\u670d\u52d9<\/a><\/li>\n<li><a href=\"#compose-full\">\u5b8c\u6574\u7248\uff1a\u52a0\u5165\u53d7\u9650\u7684 project-helper<\/a><\/li>\n<li><a href=\"#env\">Port \u8a2d\u8a08\u8207 .env<\/a><\/li>\n<li><a href=\"#auth\">\u767b\u5165\u8cc7\u6599\u5206\u96e2\u8207\u6301\u4e45\u5316<\/a><\/li>\n<li><a href=\"#launch\">\u5efa\u7f6e\u4e26\u555f\u52d5<\/a><\/li>\n<li><a href=\"#verify\">\u9a57\u8b49\u3001\u9996\u6b21\u767b\u5165\u8207\u65e5\u5e38\u64cd\u4f5c<\/a><\/li>\n<li><a href=\"#security\">\u5b89\u5168\u6027\u8207\u53cd\u5411\u4ee3\u7406<\/a><\/li>\n<li><a href=\"#troubleshooting\">\u6545\u969c\u6392\u9664\u8207\u56de\u5fa9<\/a><\/li>\n<li><a href=\"#done\">\u5b8c\u6210\u6aa2\u67e5\u6e05\u55ae\u8207\u67b6\u69cb\u7e3d\u7d50<\/a><\/li>\n<\/ol>\n<\/nav>\n<section id=\"goal\">\n<h2>1. \u9019\u7bc7\u6587\u7ae0\u8981\u5e6b\u4f60\u5b8c\u6210\u4ec0\u9ebc<\/h2>\n<p>\u4f60\u539f\u672c\u5df2\u7d93\u6709\u4e00\u500b\u80fd\u7528\u700f\u89bd\u5668\u64cd\u4f5c <strong>Codex CLI<\/strong> \u7684 Docker \u74b0\u5883\uff08\u653e\u5728 NAS \u7684 <code>\/volume1\/docker\/codex<\/code>\uff09\u3002\u73fe\u5728\u4f60\u60f3\u5728<strong>\u540c\u4e00\u5957\u74b0\u5883<\/strong>\u88e1\u518d\u52a0\u4e0a <strong>Claude Code<\/strong>\uff0c\u800c\u4e14\uff1a<\/p>\n<ul>\n<li>\u5169\u5957 CLI \u5404\u81ea\u6709\u7368\u7acb\u7684\u7db2\u9801\u7d42\u7aef\uff0c\u4e0d\u6703\u4e92\u6436\u756b\u9762\uff1b<\/li>\n<li>\u5404\u81ea\u7684\u767b\u5165\u72c0\u614b\u3001\u8a2d\u5b9a\u3001\u5c0d\u8a71\u7d00\u9304\u4e92\u4e0d\u5e72\u64fe\uff1b<\/li>\n<li>\u7dad\u8b77\u6210\u672c\u4f4e\u2014\u2014\u5347\u7d1a\u7cfb\u7d71\u5957\u4ef6\u6216 CLI \u6642\u53ea\u8981\u6539\u4e00\u500b\u5730\u65b9\uff1b<\/li>\n<li>\u9806\u4fbf\u628a\u820a\u76ee\u9304\u6539\u6210\u66f4\u8cbc\u5207\u7684\u540d\u5b57 <code>codex-claude<\/code>\uff0c\u4e26\u7559\u597d\u5099\u4efd\u53ef\u96a8\u6642\u56de\u5fa9\u3002<\/li>\n<\/ul>\n<div class=\"cc-note\"><strong>\u4e0d\u9700\u8981\u5bb3\u6015\u3002<\/strong>\u6574\u500b\u904e\u7a0b\u6c92\u6709\u4efb\u4f55\u300c\u4e0d\u53ef\u9006\u300d\u7684\u6b65\u9a5f\uff1a\u6211\u5011\u6703\u5148\u5b8c\u6574\u5099\u4efd\uff0c\u4efb\u4f55\u4e00\u6b65\u51fa\u932f\u90fd\u80fd\u9084\u539f\u6210\u539f\u672c\u80fd\u8dd1\u7684 Codex \u74b0\u5883\uff08\u898b <a href=\"#troubleshooting\">\u7b2c 15 \u7bc0<\/a>\uff09\u3002<\/div>\n<\/section>\n<section id=\"model\">\n<h2>2. \u5148\u5efa\u7acb\u6b63\u78ba\u7684\u5fc3\u667a\u6a21\u578b<\/h2>\n<p>\u521d\u5b78\u8005\u6700\u5e38\u898b\u7684\u8aa4\u89e3\u662f\uff1a<em>\u300c\u4e00\u500b Docker \u8dd1\u5169\u500b CLI\u300d\u662f\u628a Codex \u548c Claude \u585e\u9032\u540c\u4e00\u500b\u7d42\u7aef\u8f2a\u6d41\u7528\u3002<\/em>\u4e0d\u662f\u9019\u6a23\u3002<\/p>\n<p>\u6b63\u78ba\u7684\u7406\u89e3\u662f\u2014\u2014<strong>\u4e00\u4efd Dockerfile\uff08\u98df\u8b5c\uff09\u2192 \u4e00\u500b Docker \u6620\u50cf\uff08\u505a\u597d\u7684\u534a\u6210\u54c1\uff09\u2192 \u7531 Compose \u555f\u52d5\u6210\u591a\u500b\u5bb9\u5668\uff08\u670d\u52d9\uff09\uff0c\u6bcf\u500b\u5bb9\u5668\u53ea\u8dd1\u4e00\u4ef6\u4e8b\u3002<\/strong><\/p>\n<div class=\"cc-flow\" role=\"img\" aria-label=\"Dockerfile \u5efa\u7acb\u5171\u540c\u6620\u50cf\uff0c\u518d\u555f\u52d5 Codex\u3001Claude \u8207 project-helper \u4e09\u500b\u5bb9\u5668\">\n<div class=\"cc-flow-source\">\n<div class=\"cc-flow-label\">Dockerfile<small>\u5171\u540c\u98df\u8b5c<\/small><\/div>\n<p><span class=\"cc-flow-arrow\" aria-hidden=\"true\">\u2192<\/span><\/p>\n<div class=\"cc-flow-image\">\n      <strong>\u5171\u540c\u6620\u50cf\uff08image\uff09<\/strong><br \/>\n      <span>Node.js 22\uff0bgit\uff0btmux<\/span><br \/>\n      <span>ttyd\uff0bripgrep<\/span><br \/>\n      <span>Codex CLI\uff0bClaude Code<\/span><\/div>\n<\/div>\n<div class=\"cc-flow-down\">\n    <b aria-hidden=\"true\">\u2193<\/b><br \/>\n    <span>\u540c\u4e00\u4efd\u6620\u50cf\uff0c\u555f\u52d5\u6210 3 \u500b\u5bb9\u5668<\/span><\/div>\n<div class=\"cc-flow-grid\">\n<div class=\"cc-flow-service\">\n<h4>codex-web<\/h4>\n<ul>\n<li>Port 7681<\/li>\n<li>tmux\uff1acodex<\/li>\n<li>\u8a2d\u5b9a\uff1a<code>.codex<\/code><\/li>\n<\/ul>\n<div class=\"cc-flow-route\">\u2193 \u700f\u89bd\u5668<strong><code>http:\/\/NAS:7681<\/code><\/strong><\/div>\n<\/div>\n<div class=\"cc-flow-service\">\n<h4>claude-web<\/h4>\n<ul>\n<li>Port 7682<\/li>\n<li>tmux\uff1aclaude<\/li>\n<li>\u8a2d\u5b9a\uff1a<code>.claude<\/code><\/li>\n<\/ul>\n<div class=\"cc-flow-route\">\u2193 \u700f\u89bd\u5668<strong><code>http:\/\/NAS:7682<\/code><\/strong><\/div>\n<\/div>\n<div class=\"cc-flow-service\">\n<h4>project-helper<\/h4>\n<ul>\n<li>\u7121\u5c0d\u5916 Port<\/li>\n<li>\u53ea\u6709\u5b83\u80fd\u78b0<\/li>\n<li><code>docker.sock<\/code><\/li>\n<\/ul>\n<div class=\"cc-flow-route\">\u2191 \u5167\u90e8\u7db2\u8def\u547c\u53eb<strong><code>project-control<\/code><\/strong><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>\u4e09\u500b\u91cd\u9ede\uff1a<\/p>\n<ol>\n<li><strong>\u6620\u50cf\u53ea\u6709\u4e00\u4efd\u3002<\/strong>Codex \u548c Claude \u88dd\u5728\u540c\u4e00\u4efd\u6620\u50cf\u88e1\uff0c\u6240\u4ee5\u300c\u5347\u7d1a\u300d\u53ea\u767c\u751f\u5728\u4e00\u500b Dockerfile\u3002<\/li>\n<li><strong>\u5bb9\u5668\u5f7c\u6b64\u9694\u96e2\u3002<\/strong><code>codex-web<\/code> \u548c <code>claude-web<\/code> \u662f\u5169\u500b\u7368\u7acb\u884c\u7a0b\uff0c\u5404\u81ea\u7d81\u4e0d\u540c\u7684\u9023\u63a5\u57e0\u3001\u4e0d\u540c\u7684 tmux session\u3001\u4e0d\u540c\u7684\u8a2d\u5b9a\u8cc7\u6599\u593e\u3002\u5b83\u5011\u5929\u751f\u4e0d\u6703\u885d\u7a81\u3002<\/li>\n<li><strong>Docker \u63a7\u5236\u6b0a\u53ea\u7d66\u4e00\u500b\u300c\u5b88\u9580\u54e1\u300d\u3002<\/strong>AI CLI \u4e0d\u61c9\u8a72\u76f4\u63a5\u78b0 <code>docker.sock<\/code>\uff08\u7b49\u65bc\u6574\u53f0\u4e3b\u6a5f\u7684 root\uff09\u3002\u6211\u5011\u628a\u63a7\u5236\u6b0a\u4ea4\u7d66\u4e00\u500b\u53ea\u6703\u505a\u4e94\u4ef6\u4e8b\u3001\u53ea\u8a8d\u767d\u540d\u55ae\u5c08\u6848\u7684\u5c0f\u670d\u52d9 <code>project-helper<\/code>\u3002\u9019\u662f\u9032\u968e\u4f46\u5f88\u91cd\u8981\u7684\u4e00\u74b0\uff0c\u7b2c 9 \u7bc0\u6703\u8aaa\u660e\uff1b\u525b\u5165\u9580\u53ef\u4ee5\u5148\u8df3\u904e\u3002<\/li>\n<\/ol>\n<\/section>\n<section id=\"glossary\">\n<h2>3. \u540d\u8a5e\u901f\u67e5\u8868<\/h2>\n<p>\u770b\u5230\u4e0d\u61c2\u7684\u5b57\u5148\u56de\u4f86\u9019\u88e1\u67e5\uff0c\u4e0d\u7528\u6b7b\u80cc\u3002<\/p>\n<dl class=\"cc-gloss\">\n<dt>Docker \u6620\u50cf (image)<\/dt>\n<dd>\u4e00\u500b\u300c\u505a\u597d\u7684\u74b0\u5883\u5feb\u7167\u300d\uff0c\u88e1\u9762\u5df2\u7d93\u88dd\u597d\u4f5c\u696d\u7cfb\u7d71\u5957\u4ef6\u548c\u7a0b\u5f0f\u3002\u7531 <code>Dockerfile<\/code> \u5efa\u7f6e\u800c\u4f86\u3002<\/dd>\n<dt>\u5bb9\u5668 (container)<\/dt>\n<dd>\u628a\u6620\u50cf\u5be6\u969b\u8dd1\u8d77\u4f86\u7684\u300c\u4e00\u500b\u57f7\u884c\u4e2d\u7684\u884c\u7a0b\u300d\u3002\u540c\u4e00\u4efd\u6620\u50cf\u53ef\u4ee5\u540c\u6642\u8dd1\u5f88\u591a\u5bb9\u5668\u3002<\/dd>\n<dt>Docker Compose<\/dt>\n<dd>\u7528\u4e00\u500b <code>compose.yaml<\/code> \u6a94\u63cf\u8ff0\u300c\u6211\u8981\u54ea\u5e7e\u500b\u5bb9\u5668\u3001\u5404\u81ea\u7684\u9023\u63a5\u57e0\u8207\u8cc7\u6599\u593e\u300d\uff0c\u7136\u5f8c\u4e00\u9375\u555f\u52d5\u5168\u90e8\u3002\u6587\u4e2d\u6bcf\u500b\u5bb9\u5668\u7a31\u70ba\u4e00\u500b<strong>\u670d\u52d9 (service)<\/strong>\u3002<\/dd>\n<dt>Volume\uff08\u639b\u8f09\uff09<\/dt>\n<dd>\u628a NAS \u4e0a\u7684\u4e00\u500b\u8cc7\u6599\u593e\u300c\u63a5\u9032\u300d\u5bb9\u5668\u88e1\u7684\u67d0\u500b\u8def\u5f91\u3002\u5bb9\u5668\u91cd\u5efa\u5f8c\u8cc7\u6599\u9084\u5728 NAS\uff0c\u6240\u4ee5\u767b\u5165\u72c0\u614b\u4e0d\u6703\u4e0d\u898b\u3002\u5beb\u6cd5\u662f <code>\u4e3b\u6a5f\u8def\u5f91:\u5bb9\u5668\u5167\u8def\u5f91<\/code>\u3002<\/dd>\n<dt>Port\uff08\u9023\u63a5\u57e0\uff09\u5c0d\u61c9<\/dt>\n<dd>\u5beb\u6cd5 <code>7682:7682<\/code>\uff0c\u5de6\u908a\u662f\u300c\u4f60\u7528\u700f\u89bd\u5668\u9023\u7684\u4e3b\u6a5f\u57e0\u300d\uff0c\u53f3\u908a\u662f\u300c\u5bb9\u5668\u88e1\u7a0b\u5f0f\u76e3\u807d\u7684\u57e0\u300d\u3002\u5de6\u908a\u53ef\u4ee5\u96a8\u610f\u6539\uff0c\u53f3\u908a\u6211\u5011\u56fa\u5b9a\u3002<\/dd>\n<dt>ttyd<\/dt>\n<dd>\u4e00\u500b\u5c0f\u7a0b\u5f0f\uff0c\u628a\u300c\u4e00\u500b\u7d42\u7aef\u6307\u4ee4\u300d\u8b8a\u6210\u7db2\u9801\u3002\u6253\u958b\u7db2\u5740\u5c31\u7b49\u65bc\u6253\u958b\u90a3\u500b\u7d42\u7aef\u3002<\/dd>\n<dt><code>ttyd -W<\/code><\/dt>\n<dd><code>-W<\/code> = \u5141\u8a31\u5728\u7db2\u9801\u88e1\u300c\u8f38\u5165\u300d\uff08\u53ef\u5beb\uff09\u3002<strong>\u5b83\u4e0d\u662f\u5bc6\u78bc\u4fdd\u8b77<\/strong>\uff0c\u53ea\u662f\u6253\u958b\u9375\u76e4\u8f38\u5165\u6b0a\u9650\u3002<\/dd>\n<dt>tmux<\/dt>\n<dd>\u7d42\u7aef\u591a\u5de5\u5668\u3002\u9019\u88e1\u7684\u7528\u9014\u662f\uff1a\u5373\u4f7f\u700f\u89bd\u5668\u65b7\u7dda\uff0cCLI \u4ecd\u5728 tmux \u88e1\u7e7c\u7e8c\u6d3b\u8457\uff0c\u91cd\u65b0\u6574\u7406\u7db2\u9801\u5c31\u63a5\u56de\u539f\u672c\u7684\u756b\u9762\u3002<\/dd>\n<dt>UID<\/dt>\n<dd>Linux \u4f7f\u7528\u8005\u7684\u6578\u5b57\u7de8\u865f\u3002NAS \u4e0a\u8cc7\u6599\u593e\u7684\u64c1\u6709\u8005 UID \u5fc5\u9808\u548c\u5bb9\u5668\u88e1\u7684\u4f7f\u7528\u8005 UID \u4e00\u81f4\uff0c\u5bb9\u5668\u624d\u5beb\u5f97\u9032\u53bb\u3002\u672c\u6587\u7528 <code>1026<\/code>\u3002<\/dd>\n<\/dl>\n<\/section>\n<section id=\"layout\">\n<h2>4. \u6700\u7d42\u7684\u76ee\u9304\u7d50\u69cb\u9577\u4ec0\u9ebc\u6a23<\/h2>\n<p>\u5148\u770b\u7d42\u9ede\uff0c\u904e\u7a0b\u5c31\u4e0d\u6703\u8ff7\u8def\u3002\u5168\u90e8\u6a94\u6848\u90fd\u5728 NAS \u7684 <code>\/volume1\/docker\/codex-claude\/<\/code>\uff1a<\/p>\n<div class=\"cc-diagram\">\n<pre>\ncodex-claude\/\n\u251c\u2500\u2500 Dockerfile              # \u4e00\u4efd\u98df\u8b5c\uff0c\u88dd Codex + Claude + \u7cfb\u7d71\u5de5\u5177\n\u251c\u2500\u2500 compose.yaml            # \u63cf\u8ff0 codex-web \/ claude-web \/ project-helper \u4e09\u500b\u670d\u52d9\n\u251c\u2500\u2500 .env                    # \u9023\u63a5\u57e0\u3001UID\u3001helper \u7684\u5bc6\u9470\uff08\u4e0d\u9032 Git\uff09\n\u251c\u2500\u2500 .gitignore              # \u5ffd\u7565 .env \u8207\u5169\u500b\u8a2d\u5b9a\u76ee\u9304\n\u251c\u2500\u2500 project-control         # \u5bb9\u5668\u5167\u7684\u53cb\u5584\u6307\u4ee4\uff08project-restart blog \u7b49\uff09\n\u251c\u2500\u2500 home\/                   # Codex \u7684\u8a2d\u5b9a\u8207\u767b\u5165\uff08\u639b\u5230\u5bb9\u5668\u7684 ~\/.codex\uff09\n\u2502   \u2514\u2500\u2500 .gitkeep\n\u251c\u2500\u2500 claude-home\/            # Claude \u7684\u8a2d\u5b9a\u8207\u767b\u5165\uff08\u639b\u5230\u5bb9\u5668\u7684 ~\/.claude\uff09\n\u2502   \u2514\u2500\u2500 .gitkeep\n\u2514\u2500\u2500 project-helper\/         # \u53d7\u9650\u7684 Docker \u63a7\u5236\u670d\u52d9\n    \u251c\u2500\u2500 Dockerfile\n    \u2514\u2500\u2500 app.py\n<\/pre>\n<\/div>\n<table>\n<thead>\n<tr>\n<th>\u9805\u76ee<\/th>\n<th>Codex<\/th>\n<th>Claude<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Compose \u670d\u52d9\u540d\u7a31<\/td>\n<td><code>codex-web<\/code><\/td>\n<td><code>claude-web<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u5bb9\u5668\u5167 ttyd Port\uff08\u56fa\u5b9a\uff09<\/td>\n<td>7681<\/td>\n<td>7682<\/td>\n<\/tr>\n<tr>\n<td>\u4e3b\u6a5f Port\uff08\u53ef\u5728 .env \u6539\uff09<\/td>\n<td><code>TTYD_PORT<\/code>\uff0c\u9810\u8a2d 7681<\/td>\n<td><code>CLAUDE_TTYD_PORT<\/code>\uff0c\u9810\u8a2d 7682<\/td>\n<\/tr>\n<tr>\n<td>tmux session \u540d\u7a31<\/td>\n<td><code>codex<\/code><\/td>\n<td><code>claude<\/code><\/td>\n<\/tr>\n<tr>\n<td>NAS \u4e0a\u7684\u8a2d\u5b9a\u76ee\u9304<\/td>\n<td><code>.\/home<\/code><\/td>\n<td><code>.\/claude-home<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u5bb9\u5668\u5167\u8a2d\u5b9a\u8def\u5f91<\/td>\n<td><code>\/home\/codexuser\/.codex<\/code><\/td>\n<td><code>\/home\/codexuser\/.claude<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u767b\u5165\u8cc7\u6599\u6a94<\/td>\n<td><code>auth.json<\/code><\/td>\n<td><code>.credentials.json<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/section>\n<section id=\"backup\">\n<h2>5. <span class=\"cc-step\">\u6b65\u9a5f 1<\/span>\u642c\u79fb\u524d\uff1a\u5099\u4efd\u820a Codex \u76ee\u9304<\/h2>\n<p>\u4ee5\u4e0b\u90fd\u4ee5 Synology NAS \u70ba\u4f8b\uff1a\u820a\u76ee\u9304 <code>\/volume1\/docker\/codex<\/code>\uff0c\u65b0\u76ee\u9304 <code>\/volume1\/docker\/codex-claude<\/code>\u3002\u5148\u770b\u770b\u76ee\u524d\u72c0\u614b\u3001\u628a\u5c55\u958b\u5f8c\u7684\u8a2d\u5b9a\u5b58\u4e00\u4efd\uff0c\u65b9\u4fbf\u4e4b\u5f8c\u6bd4\u5c0d\uff1a<\/p>\n<pre><code>cd \/volume1\/docker\/codex\nsudo docker compose ps\nsudo docker compose config &gt; \/tmp\/codex-compose-resolved.yaml<\/code><\/pre>\n<p>\u63a5\u8457\u505a\u4e00\u4efd\u5b8c\u6574\u3001\u547d\u540d\u6e05\u695a\u7684\u5099\u4efd\uff1a<\/p>\n<pre><code>cd \/volume1\/docker\nsudo cp -a codex codex-backup-before-claude<\/code><\/pre>\n<div class=\"cc-warning\"><strong>\u5099\u4efd\u4e5f\u662f\u654f\u611f\u8cc7\u6599\u3002<\/strong><code>cp -a<\/code> \u6703\u9023\u540c <code>home\/<\/code> \u88e1\u7684\u767b\u5165 token \u4e00\u8d77\u8907\u88fd\u3002\u9019\u4efd\u5099\u4efd\u4e0d\u8981\u653e\u9032\u4efb\u4f55\u516c\u958b Git \u5009\u5eab\uff0c\u4e5f\u4e0d\u8981\u4e0a\u50b3\u5230\u96f2\u7aef\u76f8\u7c3f\u4e4b\u985e\u7684\u5730\u65b9\u3002\u5148\u7528 <code>df -h \/volume1<\/code> \u78ba\u8a8d\u7a7a\u9593\u5920\u3002<\/div>\n<\/section>\n<section id=\"rename\">\n<h2>6. <span class=\"cc-step\">\u6b65\u9a5f 2<\/span>\u505c\u6b62\u670d\u52d9\u3001\u642c\u79fb\u4e26\u6539\u540d<\/h2>\n<p>\u4e00\u5b9a\u8981<strong>\u5148\u5728\u820a\u76ee\u9304\u628a Compose \u505c\u6389<\/strong>\uff0c\u518d\u6539\u540d\u3002\u5426\u5247\u57f7\u884c\u4e2d\u7684\u5bb9\u5668\u9084\u8a18\u8457\u820a\u8def\u5f91\uff0c\u6703\u51fa\u73fe\u5404\u7a2e\u5947\u602a\u932f\u8aa4\u3002<\/p>\n<pre><code>cd \/volume1\/docker\/codex\nsudo docker compose down\n\ncd \/volume1\/docker\nsudo mv codex codex-claude\ncd \/volume1\/docker\/codex-claude<\/code><\/pre>\n<p>\u6539\u540d\u5f8c\uff0c\u8a2d\u5b9a\u6a94\u88e1\u53ef\u80fd\u9084\u6b98\u7559\u820a\u7684\u7d55\u5c0d\u8def\u5f91\u3002\u7528 <code>rg<\/code>\uff08ripgrep\uff09\u641c\u51fa\u4f86\uff0c\u4f46\u5148\u6392\u9664\u5169\u500b\u8a2d\u5b9a\u76ee\u9304\uff0c\u907f\u514d\u88ab\u767b\u5165\u7d00\u9304\u5e72\u64fe\uff1a<\/p>\n<pre><code>rg -n '\/volume1\/docker\/codex([\/&quot; ]|$)' . \\\n  --glob '!home\/**' --glob '!claude-home\/**'<\/code><\/pre>\n<p>\u628a\u300c\u78ba\u5b9a\u662f\u5c08\u6848\u8def\u5f91\u300d\u7684\u90a3\u5e7e\u884c\u6539\u6210 <code>\/volume1\/docker\/codex-claude<\/code>\u3002<\/p>\n<div class=\"cc-warning\"><strong>\u4e0d\u8981\u7121\u8166\u5168\u57df\u53d6\u4ee3\u3002<\/strong>\u50cf\u5957\u4ef6\u540d\u7a31 <code>@openai\/codex<\/code>\u3001\u5bb9\u5668\u88e1\u7684 <code>.codex<\/code> \u8a2d\u5b9a\u76ee\u9304\u3001tmux session \u540d\u7a31 <code>codex<\/code> \u2014\u2014\u9019\u4e9b\u5b57\u88e1\u6709 <code>codex<\/code>\uff0c\u4f46<strong>\u4e0d\u662f<\/strong>\u820a\u5c08\u6848\u8def\u5f91\uff0c\u52d5\u5230\u5c31\u58de\u4e86\u3002<\/div>\n<h3>\u6539\u540d\u5f8c\u5fc5\u505a\uff1a\u522a\u9664\u820a\u5bb9\u5668\uff0c\u518d\u7528\u65b0\u8def\u5f91\u91cd\u65b0\u5efa\u7acb<\/h3>\n<p>Docker \u5728<strong>\u5efa\u7acb\u5bb9\u5668\u7576\u4e0b<\/strong>\u5c31\u628a bind mount \u7684\u4e3b\u6a5f\u4f86\u6e90\u8def\u5f91\u8a18\u9032\u5bb9\u5668\u8a2d\u5b9a\u3002\u8cc7\u6599\u593e\u5f9e <code>codex<\/code> \u6539\u540d\u70ba <code>codex-claude<\/code> \u5f8c\uff0c\u55ae\u7d14\u57f7\u884c <code>docker restart<\/code> \u53ea\u6703\u91cd\u555f\u540c\u4e00\u500b\u820a\u5bb9\u5668\uff0c<strong>\u4e0d\u6703\u628a\u639b\u8f09\u4f86\u6e90\u81ea\u52d5\u6539\u6210\u65b0\u8def\u5f91<\/strong>\u3002\u56e0\u6b64\u820a\u5bb9\u5668\u5fc5\u9808\u522a\u9664\uff0c\u518d\u7531\u65b0\u76ee\u9304\u88e1\u7684 Compose \u5efa\u7acb\u65b0\u5bb9\u5668\u3002<\/p>\n<p>\u524d\u9762\u7684 <code>docker compose down<\/code> \u6b63\u5e38\u60c5\u6cc1\u4e0b\u5df2\u7d93\u6703\u522a\u9664\u8a72 Compose \u5c08\u6848\u7684\u5bb9\u5668\u3002\u70ba\u907f\u514d\u820a\u7248\u66fe\u7528\u4e0d\u540c\u5c08\u6848\u540d\u7a31\u3001\u624b\u52d5 <code>docker run<\/code>\uff0c\u6216\u5148\u524d\u53ea\u505a\u904e <code>stop<\/code> \u800c\u7559\u4e0b\u6b98\u9ab8\uff0c\u6539\u540d\u5f8c\u518d\u6aa2\u67e5\u4e00\u6b21\uff1a<\/p>\n<pre><code># \u5148\u5217\u51fa\u53ef\u80fd\u6b98\u7559\u7684\u820a\u5bb9\u5668\uff1b\u78ba\u8a8d\u540d\u7a31\u5f8c\u624d\u522a\u9664\nsudo docker ps -a --filter name=codex-web \\\n  --filter name=codex-cli \\\n  --filter name=codex-project-helper\n\n# \u53ea\u522a\u9664\u78ba\u8a8d\u5c6c\u65bc\u820a\u67b6\u69cb\u7684\u5bb9\u5668\uff1b\u4e0d\u5b58\u5728\u6642\u51fa\u73fe No such container \u53ef\u5ffd\u7565\nsudo docker rm -f codex-web codex-cli codex-project-helper<\/code><\/pre>\n<div class=\"cc-warning\"><strong>\u5148\u770b\u6e05\u55ae\u518d\u522a\u9664\u3002<\/strong><code>docker rm -f<\/code> \u6703\u5f37\u5236\u505c\u6b62\u4e26\u522a\u9664\u6307\u5b9a\u5bb9\u5668\uff0c\u4f46\u4e0d\u6703\u522a\u9664\u672c\u6587\u4f7f\u7528\u7684\u4e3b\u6a5f\u8cc7\u6599\u593e\u3002\u82e5\u4f60\u7684\u5bb9\u5668\u540d\u7a31\u4e0d\u540c\uff0c\u8acb\u4ee5 <code>docker ps -a<\/code> \u986f\u793a\u7684\u820a\u5bb9\u5668\u540d\u7a31\u70ba\u6e96\uff0c\u4e0d\u8981\u7167\u6284\u4e0d\u76f8\u7b26\u7684\u540d\u7a31\u3002<\/div>\n<p>\u5b8c\u6210 Compose \u8207\u8def\u5f91\u4fee\u6539\u5f8c\uff0c\u8981\u7528 <code>--force-recreate<\/code> \u5f9e\u65b0\u76ee\u9304\u5efa\u7acb\u5bb9\u5668\uff0c\u8b93 bind mount \u771f\u6b63\u6307\u5411 <code>\/volume1\/docker\/codex-claude<\/code>\uff1a<\/p>\n<pre><code>cd \/volume1\/docker\/codex-claude\nsudo docker compose config --quiet\nsudo docker compose up -d --build --force-recreate<\/code><\/pre>\n<p>\u6700\u5f8c\u76f4\u63a5\u67e5\u770b\u65b0\u5bb9\u5668\u8a18\u9304\u7684\u639b\u8f09\u4f86\u6e90\uff0c\u4e0d\u53ea\u78ba\u8a8d\u7db2\u9801\u300c\u770b\u8d77\u4f86\u80fd\u958b\u300d\uff1a<\/p>\n<pre><code>sudo docker inspect codex-web \\\n  --format '{{range .Mounts}}{{println .Source \"\u2192\" .Destination}}{{end}}'\nsudo docker inspect codex-claude-claude-web \\\n  --format '{{range .Mounts}}{{println .Source \"\u2192\" .Destination}}{{end}}'<\/code><\/pre>\n<p>\u8f38\u51fa\u4e2d\u7684\u5c08\u6848\u4f86\u6e90\u61c9\u662f <code>\/volume1\/docker\/codex-claude<\/code>\uff0c\u4e0d\u80fd\u518d\u51fa\u73fe\u820a\u7684 <code>\/volume1\/docker\/codex<\/code>\u3002<\/p>\n<\/section>\n<section id=\"dockerfile\">\n<h2>7. <span class=\"cc-step\">\u6b65\u9a5f 3<\/span>\u4e00\u4efd Dockerfile \u88dd\u5169\u5957 CLI<\/h2>\n<p>\u6574\u500b\u5171\u5b58\u67b6\u69cb\u7684\u6838\u5fc3\uff0c\u5c31\u662f\u9019\u4efd Dockerfile \u7684 npm \u5b89\u88dd\u6bb5\u2014\u2014\u540c\u6642\u88dd OpenAI Codex CLI \u8207 Anthropic Claude Code\uff1a<\/p>\n<pre><code>FROM node:22-bookworm-slim\n\nARG CODEX_UID=1026\nENV DEBIAN_FRONTEND=noninteractive \\\n    LANG=C.UTF-8 LC_ALL=C.UTF-8 \\\n    HOME=\/home\/codexuser TERM=xterm-256color\n\n# 1) \u7cfb\u7d71\u5de5\u5177\uff1a\u5169\u5957 CLI \u90fd\u6703\u7528\u5230\nRUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \\\n      bash bubblewrap ca-certificates curl git less \\\n      openssh-client procps ripgrep tmux \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\n# 2) \u4e00\u6b21\u88dd\u597d\u5169\u5957 AI CLI\uff08\u672c\u6559\u5b78\u7528\u6700\u65b0\u7248\uff1b\u6b63\u5f0f\u74b0\u5883\u5efa\u8b70\u91d8\u7248\u672c\uff09\nRUN npm install -g \\\n      @openai\/codex \\\n      @anthropic-ai\/claude-code \\\n    &amp;&amp; npm cache clean --force\n\n# 3) ttyd\uff1a\u628a\u7d42\u7aef\u8b8a\u6210\u7db2\u9801\nRUN curl -fsSL \\\n      https:\/\/github.com\/tsl0922\/ttyd\/releases\/download\/1.7.7\/ttyd.x86_64 \\\n      -o \/usr\/local\/bin\/ttyd \\\n    &amp;&amp; chmod 0755 \/usr\/local\/bin\/ttyd\n\n# 4) \u5efa\u7acb\u8207 NAS \u76ee\u9304\u540c UID \u7684\u975e root \u4f7f\u7528\u8005\nRUN useradd --uid \"${CODEX_UID}\" --create-home --shell \/bin\/bash codexuser \\\n    &amp;&amp; mkdir -p \/home\/codexuser\/.codex \/workspace \\\n    &amp;&amp; chown -R codexuser:codexuser \/home\/codexuser \/workspace\n\nUSER codexuser\nWORKDIR \/workspace\nCMD [\"bash\"]<\/code><\/pre>\n<p>\u56e0\u70ba\u5169\u500b\u670d\u52d9\u90fd build \u81ea\u9019\u4efd Dockerfile\uff0c<strong>\u5347\u7d1a\u7cfb\u7d71\u5de5\u5177\u6216\u4efb\u4e00 CLI \u53ea\u9700\u8981\u6539\u9019\u4e00\u8655\uff0c\u518d\u91cd\u65b0 build<\/strong>\u3002\u9019\u5c31\u662f\u300c\u4e00\u4efd\u6620\u50cf\u300d\u5e36\u4f86\u7684\u7dad\u8b77\u7d05\u5229\u3002<\/p>\n<div class=\"cc-note\"><strong>\u7248\u672c\u53ef\u91cd\u73fe\u6027\uff1a<\/strong>\u6559\u5b78\u70ba\u4e86\u62ff\u5230\u6700\u65b0\u529f\u80fd\u800c\u7528\u6700\u65b0\u7248\u3002\u6b63\u5f0f\u74b0\u5883\u5efa\u8b70\u628a <code>@openai\/codex<\/code>\u3001<code>@anthropic-ai\/claude-code<\/code> \u91d8\u5728\u6e2c\u8a66\u904e\u7684\u7248\u672c\u865f\uff0c\u78ba\u8a8d\u6c92\u554f\u984c\u5f8c\u518d\u5347\u7248\u91cd build\u3002<\/div>\n<div class=\"cc-note\"><strong>\u4e4b\u5f8c\u6703\u7528\u5230\u7684 <code>project-control<\/code>\uff1a<\/strong>\u82e5\u4f60\u8981\u505a<a href=\"#compose-full\">\u5b8c\u6574\u7248<\/a>\uff0cDockerfile \u5c3e\u7aef\u9084\u6703 <code>COPY project-control<\/code> \u9032\u6620\u50cf\u4e26\u5efa\u7acb\u5e7e\u500b\u5225\u540d\uff08<code>project-restart<\/code> \u7b49\uff09\u3002\u7b2c 9 \u7bc0\u6703\u88dc\u4e0a\u9019\u6bb5\u3002<\/div>\n<\/section>\n<section id=\"compose-min\">\n<h2>8. <span class=\"cc-step\">\u6b65\u9a5f 4<\/span>\u6700\u5c0f\u53ef\u884c\u7248\uff1aCompose \u5169\u500b\u670d\u52d9<\/h2>\n<p>\u5148\u628a\u300c\u80fd\u7528\u300d\u505a\u51fa\u4f86\u3002\u9019\u500b\u7248\u672c\u53ea\u6709 <code>codex-web<\/code> \u548c <code>claude-web<\/code>\uff0c\u6c92\u6709 project-helper\u3002\u5c0d\u5927\u591a\u6578\u4eba\u9019\u6a23\u5c31\u5920\u4e86\u3002<\/p>\n<pre><code>name: codex-claude\n\nservices:\n  codex-web:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      args:\n        CODEX_UID: \"${CODEX_UID:-1026}\"\n    container_name: codex-web\n    restart: unless-stopped\n    init: true\n    stdin_open: true\n    tty: true\n    working_dir: \/workspace\n    environment:\n      LANG: C.UTF-8\n      LC_ALL: C.UTF-8\n      TERM: xterm-256color\n      HOME: \/home\/codexuser\n    ports:\n      - \"${TTYD_PORT:-7681}:7681\"\n    volumes:\n      - .\/home:\/home\/codexuser\/.codex\n      - \/volume1\/docker\/codex-claude:\/workspace\/codex-claude\n    entrypoint:\n      - ttyd\n      - -W\n      - -p\n      - \"7681\"\n      - -t\n      - \"fontSize=18\"\n      - -t\n      - \"rendererType=canvas\"\n      - bash\n      - -lc\n      - 'exec tmux new-session -A -s codex \"codex\"'\n    healthcheck:\n      test: [\"CMD-SHELL\", \"curl -sS -o \/dev\/null http:\/\/127.0.0.1:7681 || exit 1\"]\n      interval: 30s\n      timeout: 5s\n      retries: 3\n      start_period: 20s\n\n  claude-web:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      args:\n        CODEX_UID: \"${CODEX_UID:-1026}\"\n    container_name: codex-claude-claude-web\n    restart: unless-stopped\n    init: true\n    stdin_open: true\n    tty: true\n    working_dir: \/workspace\n    environment:\n      LANG: C.UTF-8\n      LC_ALL: C.UTF-8\n      TERM: xterm-256color\n      HOME: \/home\/codexuser\n    ports:\n      - \"${CLAUDE_TTYD_PORT:-7682}:7682\"\n    volumes:\n      - .\/claude-home:\/home\/codexuser\/.claude\n      - \/volume1\/docker\/codex-claude:\/workspace\/codex-claude\n    entrypoint:\n      - ttyd\n      - -W\n      - -p\n      - \"7682\"\n      - -t\n      - \"fontSize=18\"\n      - -t\n      - \"rendererType=canvas\"\n      - bash\n      - -lc\n      - 'exec tmux new-session -A -s claude \"claude\"'\n    healthcheck:\n      test: [\"CMD-SHELL\", \"curl -sS -o \/dev\/null http:\/\/127.0.0.1:7682 || exit 1\"]\n      interval: 30s\n      timeout: 5s\n      retries: 3\n      start_period: 20s<\/code><\/pre>\n<h3>\u9010\u6bb5\u770b\u61c2\u9019\u4efd\u8a2d\u5b9a<\/h3>\n<dl class=\"cc-gloss\">\n<dt><code>build.args.CODEX_UID<\/code><\/dt>\n<dd>\u628a <code>.env<\/code> \u7684 UID \u50b3\u9032 Dockerfile\uff0c\u8b93\u5bb9\u5668\u4f7f\u7528\u8005\u548c NAS \u8cc7\u6599\u593e\u64c1\u6709\u8005\u4e00\u81f4\u3002<\/dd>\n<dt><code>init: true<\/code> \/ <code>stdin_open<\/code> \/ <code>tty<\/code><\/dt>\n<dd>\u4e92\u52d5\u5f0f CLI \u9700\u8981\u4e00\u500b\u300c\u771f\u7684\u7d42\u7aef\u300d\u3002\u9019\u4e09\u500b\u8b93\u5bb9\u5668\u63d0\u4f9b TTY \u4e26\u6b63\u78ba\u8655\u7406 Ctrl+C\u3002<\/dd>\n<dt><code>ports<\/code><\/dt>\n<dd>\u5169\u500b\u670d\u52d9\u5de6\u908a\u4e3b\u6a5f\u57e0\u4e0d\u540c\uff087681 \/ 7682\uff09\uff0c\u53f3\u908a\u5bb9\u5668\u57e0\u4e5f\u4e0d\u540c\uff0c\u6240\u4ee5\u540c\u6642\u958b\u5169\u500b\u7db2\u9801\u4e0d\u6703\u649e\u3002<\/dd>\n<dt><code>volumes<\/code> \u7b2c\u4e00\u884c<\/dt>\n<dd>\u95dc\u9375\u7684\u9694\u96e2\uff1aCodex \u639b <code>.\/home<\/code> \u2192 <code>.codex<\/code>\uff0cClaude \u639b <code>.\/claude-home<\/code> \u2192 <code>.claude<\/code>\u3002<strong>\u5404\u81ea\u7368\u7acb\uff0c\u9019\u662f\u96d9 CLI \u80fd\u7a69\u5b9a\u5171\u5b58\u7684\u6839\u672c\u539f\u56e0\u3002<\/strong><\/dd>\n<dt><code>volumes<\/code> \u7b2c\u4e8c\u884c<\/dt>\n<dd>\u628a\u5c08\u6848\u672c\u8eab\u639b\u9032 <code>\/workspace\/codex-claude<\/code>\uff0c\u65b9\u4fbf\u4f60\u7528 AI \u76f4\u63a5\u6539\u9019\u5957\u8a2d\u5b9a\u3002\u8981\u8b93\u5169\u5957 CLI \u770b\u5230\u66f4\u591a\u7a0b\u5f0f\u78bc\uff0c\u5c31\u5728<strong>\u5169\u500b\u670d\u52d9\u90fd<\/strong>\u5404\u52a0\u4e00\u884c\u660e\u78ba\u7684\u639b\u8f09\uff0c\u4f8b\u5982 <code>- \/volume1\/docker\/blog:\/workspace\/blog<\/code>\u3002<\/dd>\n<dt><code>entrypoint<\/code> \u6700\u5f8c\u4e00\u884c<\/dt>\n<dd><code>tmux new-session -A -s codex \"codex\"<\/code>\uff1a\u958b\u4e00\u500b\u53eb <code>codex<\/code> \u7684 tmux session \u8dd1 <code>codex<\/code>\uff1b<code>-A<\/code> \u4ee3\u8868\u300c\u5df2\u5b58\u5728\u5c31\u63a5\u56de\u53bb\u300d\u3002\u6240\u4ee5\u700f\u89bd\u5668\u91cd\u6574\u6216\u77ed\u66ab\u65b7\u7dda\u5f8c\uff0cCLI \u4e0d\u6703\u6d88\u5931\u3002<\/dd>\n<dt><code>healthcheck<\/code><\/dt>\n<dd>Docker \u6bcf 30 \u79d2 curl \u4e00\u6b21\u81ea\u5df1\u7684 ttyd \u57e0\uff0c\u5931\u6557\u5c31\u628a\u5bb9\u5668\u6a19\u8a18\u70ba unhealthy\uff0c\u65b9\u4fbf\u4f60\u7528 <code>docker compose ps<\/code> \u4e00\u773c\u770b\u51fa\u54ea\u500b\u670d\u52d9\u639b\u4e86\u3002<\/dd>\n<\/dl>\n<div class=\"cc-warning\"><strong>\u4e0d\u8981\u628a\u6574\u53f0 NAS \u639b\u9032\u5bb9\u5668\u3002<\/strong>\u6bcf\u4e00\u500b\u8981\u5171\u7528\u7684\u8cc7\u6599\u593e\u90fd\u61c9\u8a72\u5728 <code>volumes<\/code> \u660e\u78ba\u5217\u51fa\u3002\u628a <code>\/volume1<\/code> \u6216 <code>\/<\/code> \u76f4\u63a5\u4ea4\u7d66\u5bb9\u5668\uff0c\u7b49\u65bc\u628a\u6574\u53f0 NAS \u7684\u8b80\u5beb\u6b0a\u4ea4\u7d66 AI CLI\u3002<\/div>\n<\/section>\n<section id=\"compose-full\">\n<h2>9. <span class=\"cc-step\">\u6b65\u9a5f 4\uff0b<\/span>\u5b8c\u6574\u7248\uff1a\u52a0\u5165\u53d7\u9650\u7684 project-helper<\/h2>\n<p>\u5982\u679c\u4f60\u60f3\u5728 Codex \/ Claude \u88e1\u76f4\u63a5\u4e0b\u6307\u4ee4\u53bb<strong>\u91cd\u555f\u6216\u91cd\u5efa\u5176\u4ed6 Compose \u5c08\u6848<\/strong>\uff08\u4f8b\u5982\u300c\u5e6b\u6211 rebuild blog\u300d\uff09\uff0c\u90a3 CLI \u5c31\u9700\u8981 Docker \u63a7\u5236\u6b0a\u3002\u76f4\u63a5\u628a <code>\/var\/run\/docker.sock<\/code> \u639b\u9032 AI \u5bb9\u5668\u5f88\u5371\u96aa\u2014\u2014\u90a3\u7b49\u65bc\u7d66\u4e86\u6574\u53f0\u4e3b\u6a5f\u7684 root\u3002<\/p>\n<p>\u89e3\u6cd5\uff1a\u591a\u4e00\u500b<strong>\u53ea\u6703\u505a\u4e94\u4ef6\u4e8b\u3001\u53ea\u8a8d\u767d\u540d\u55ae<\/strong>\u7684\u5b88\u9580\u54e1\u670d\u52d9\u3002\u5b83\u4e0d\u5c0d\u5916\u958b\u57e0\uff0c\u53ea\u6709 Compose \u5167\u90e8\u7db2\u8def\u4e0a\u7684 <code>codex-web<\/code> \/ <code>claude-web<\/code> \u9023\u5f97\u5230\u3002<\/p>\n<h3>9-1 project-helper\/app.py\uff08\u5b88\u9580\u54e1\u672c\u9ad4\uff09<\/h3>\n<p>\u4e00\u652f\u6975\u5c0f\u7684 Flask \u7a0b\u5f0f\uff1a\u9a57\u8b49 Bearer token \u2192 \u6aa2\u67e5\u5c08\u6848\u5728\u767d\u540d\u55ae \u2192 \u53ea\u5141\u8a31 <code>status \/ restart \/ up \/ rebuild \/ logs<\/code> \u2192 \u5728\u8a72\u5c08\u6848\u76ee\u9304\u57f7\u884c\u5c0d\u61c9\u7684 <code>docker compose<\/code> \u6307\u4ee4\u3002<\/p>\n<pre><code>import os, subprocess\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\nTOKEN = os.environ[\"PROJECT_HELPER_TOKEN\"]\n\nPROJECTS = {\n    \"blog\": \"\/projects\/blog\",\n    \"umami\": \"\/projects\/umami\",\n    \"codex-claude\": \"\/projects\/codex-claude\",\n    # \u2026\u53ea\u653e\u4f60\u5141\u8a31\u88ab\u63a7\u5236\u7684\u5c08\u6848\n}\nACTIONS = {\n    \"restart\": [\"docker\", \"compose\", \"restart\"],\n    \"up\":      [\"docker\", \"compose\", \"up\", \"-d\"],\n    \"rebuild\": [\"docker\", \"compose\", \"up\", \"-d\", \"--build\"],\n    \"status\":  [\"docker\", \"compose\", \"ps\"],\n    \"logs\":    [\"docker\", \"compose\", \"logs\", \"--tail\", \"200\"],\n}\n\n@app.post(\"\/action\")\ndef action():\n    if request.headers.get(\"Authorization\", \"\") != f\"Bearer {TOKEN}\":\n        return jsonify(error=\"unauthorized\"), 401\n    data = request.get_json(silent=True) or {}\n    project, name = data.get(\"project\", \"\"), data.get(\"action\", \"\")\n    if project not in PROJECTS:\n        return jsonify(error=\"project not allowed\", allowed=sorted(PROJECTS)), 400\n    if name not in ACTIONS:\n        return jsonify(error=\"action not allowed\", allowed=sorted(ACTIONS)), 400\n    p = subprocess.run(ACTIONS[name], cwd=PROJECTS[project],\n                       text=True, capture_output=True,\n                       timeout=1800 if name in {\"rebuild\", \"up\"} else 120)\n    return jsonify(ok=(p.returncode == 0), project=project, action=name,\n                   stdout=p.stdout[-20000:], stderr=p.stderr[-20000:]), \\\n           (200 if p.returncode == 0 else 500)<\/code><\/pre>\n<h3>9-2 project-helper\/Dockerfile<\/h3>\n<pre><code>FROM docker:27-cli\nRUN apk add --no-cache python3 py3-pip \\\n    &amp;&amp; pip3 install --break-system-packages --no-cache-dir flask gunicorn\nWORKDIR \/app\nCOPY app.py \/app\/app.py\nEXPOSE 8080\nCMD [\"gunicorn\", \"--bind\", \"0.0.0.0:8080\", \"--workers\", \"1\", \"--threads\", \"4\", \"app:app\"]<\/code><\/pre>\n<h3>9-3 project-control\uff08\u653e\u9032\u4e3b Dockerfile \u7684\u53cb\u5584\u6307\u4ee4\uff09<\/h3>\n<p>\u5728<a href=\"#dockerfile\">\u7b2c 7 \u7bc0<\/a>\u7684 Dockerfile <code>USER codexuser<\/code> \u4e4b\u524d\u52a0\u5165\uff1a<\/p>\n<pre><code>COPY project-control \/usr\/local\/bin\/project-control\nRUN chmod 0755 \/usr\/local\/bin\/project-control \\\n    &amp;&amp; for a in restart up rebuild status logs; do \\\n         ln -s \/usr\/local\/bin\/project-control \/usr\/local\/bin\/project-$a; \\\n       done<\/code><\/pre>\n<p><code>project-control<\/code> \u672c\u8eab\u662f\u4e00\u652f\u5c0f shell script\uff0c\u628a <code>project-restart blog<\/code> \u8f49\u6210\u4e00\u500b\u5e36 token \u7684 HTTP \u8acb\u6c42\u9001\u7d66 helper\uff1a<\/p>\n<pre><code>#!\/bin\/sh\nset -eu\nPROG=\"$(basename \"$0\")\"\ncase \"$PROG\" in\n  project-restart) set -- restart \"${1:-}\" ;;\n  project-up)      set -- up \"${1:-}\" ;;\n  project-rebuild) set -- rebuild \"${1:-}\" ;;\n  project-status)  set -- status \"${1:-}\" ;;\n  project-logs)    set -- logs \"${1:-}\" ;;\nesac\nACTION=\"${1:-}\"; PROJECT=\"${2:-}\"\nURL=\"${PROJECT_HELPER_URL:-http:\/\/project-helper:8080}\"\nTOKEN=\"${PROJECT_HELPER_TOKEN:?PROJECT_HELPER_TOKEN is not set}\"\ncurl -fsS -X POST \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application\/json\" \\\n  --data \"{\\\"action\\\":\\\"$ACTION\\\",\\\"project\\\":\\\"$PROJECT\\\"}\" \\\n  \"$URL\/action\"\necho<\/code><\/pre>\n<h3>9-4 \u5728 compose.yaml \u52a0\u5165 helper\uff0c\u4e26\u8b93\u5169\u500b CLI \u670d\u52d9\u8a8d\u5f97\u5b83<\/h3>\n<p>\u5728 <code>codex-web<\/code> \u548c <code>claude-web<\/code> \u7684 <code>environment<\/code> \u5404\u52a0\u5169\u884c\uff0c\u4e26\u52a0\u4e0a <code>depends_on<\/code>\uff1a<\/p>\n<pre><code>    environment:\n      # \u2026\u539f\u672c\u7684 LANG \/ TERM \/ HOME \u2026\n      PROJECT_HELPER_URL: http:\/\/project-helper:8080\n      PROJECT_HELPER_TOKEN: \"${PROJECT_HELPER_TOKEN}\"\n    depends_on:\n      - project-helper<\/code><\/pre>\n<p>\u7136\u5f8c\u65b0\u589e\u7b2c\u4e09\u500b\u670d\u52d9\uff1a<\/p>\n<pre><code>  project-helper:\n    build:\n      context: .\/project-helper\n      dockerfile: Dockerfile\n    container_name: codex-project-helper\n    restart: unless-stopped\n    environment:\n      PROJECT_HELPER_TOKEN: \"${PROJECT_HELPER_TOKEN}\"\n    volumes:\n      # \u53ea\u6709\u5b88\u9580\u54e1\u62ff\u5230 Docker \u63a7\u5236\u6b0a\n      - \/var\/run\/docker.sock:\/var\/run\/docker.sock\n      # \u53ea\u639b\u4f60\u5141\u8a31\u88ab\u63a7\u5236\u7684 Compose \u5c08\u6848\u76ee\u9304\n      - \/volume1\/docker\/blog:\/projects\/blog\n      - \/volume1\/docker\/umami:\/projects\/umami\n      - \/volume1\/docker\/codex-claude:\/projects\/codex-claude\n    # \u4e0d\u5c0d\u5916\u958b port\uff1a\u53ea\u6709\u9019\u500b Compose \u7db2\u8def\u4e0a\u7684\u670d\u52d9\u9023\u5f97\u5230\n    expose:\n      - \"8080\"<\/code><\/pre>\n<div class=\"cc-danger\"><strong>\u7d55\u4e0d\u8981<\/strong>\u628a <code>\/var\/run\/docker.sock<\/code> \u639b\u9032 <code>codex-web<\/code> \u6216 <code>claude-web<\/code>\u3002\u6574\u500b\u8a2d\u8a08\u7684\u610f\u7fa9\u5c31\u662f\uff1aDocker \u63a7\u5236\u6b0a\u53ea\u5b58\u5728\u65bc <code>project-helper<\/code> \u9019\u4e00\u500b\u5bb9\u5668\uff0c\u800c\u4e14\u5b83\u88ab token\u3001\u767d\u540d\u55ae\u5c08\u6848\u3001\u767d\u540d\u55ae\u52d5\u4f5c\u4e09\u91cd\u9650\u5236\u3002<\/div>\n<\/section>\n<section id=\"env\">\n<h2>10. <span class=\"cc-step\">\u6b65\u9a5f 5<\/span>Port \u8a2d\u8a08\u8207 .env<\/h2>\n<p>\u5bb9\u5668\u88e1\u7684 ttyd \u57e0\u56fa\u5b9a\u662f 7681 \/ 7682\uff1b\u4f60\u80fd\u81ea\u7531\u66f4\u52d5\u7684\u662f\u300c\u4e3b\u6a5f\u7aef\u300d\u7684\u57e0\u3002\u5168\u90e8\u96c6\u4e2d\u5beb\u5728 <code>.env<\/code>\uff1a<\/p>\n<pre><code>CODEX_UID=1026\nTTYD_PORT=7681\nCLAUDE_TTYD_PORT=7682\n# \u7528 openssl rand -hex 32 \u7522\u751f\u4e00\u9577\u4e32\u96a8\u6a5f\u503c\u8cbc\u5728\u9019\u88e1\uff08\u5b8c\u6574\u7248\u624d\u9700\u8981\uff09\nPROJECT_HELPER_TOKEN=\u8acb\u63db\u6210\u4f60\u81ea\u5df1\u7684\u96a8\u6a5f\u5b57\u4e32<\/code><\/pre>\n<p>\u5047\u8a2d NAS \u4e0a 7682 \u5df2\u7d93\u88ab\u5225\u7684\u670d\u52d9\u4f54\u7528\uff0c\u4f60<strong>\u53ea\u8981<\/strong>\u628a <code>CLAUDE_TTYD_PORT<\/code> \u6539\u6210\u6c92\u88ab\u7528\u7684\u57e0\uff08\u4f8b\u5982 <code>8765<\/code>\uff09\uff0c\u7136\u5f8c <code>docker compose up -d<\/code>\u3002\u5bb9\u5668\u5167\u7684 ttyd \u4ecd\u7136\u662f 7682\uff0c\u4e0d\u7528\u52d5 <code>compose.yaml<\/code>\u3002<\/p>\n<p>\u7522\u751f helper \u5bc6\u9470\uff1a<\/p>\n<pre><code>openssl rand -hex 32<\/code><\/pre>\n<div class=\"cc-warning\"><code>.env<\/code> \u4e00\u5b9a\u8981\u9032 <code>.gitignore<\/code>\u3002\u88e1\u9762\u7684 <code>PROJECT_HELPER_TOKEN<\/code> \u4e00\u65e6\u5916\u6d29\uff0c\u4efb\u4f55\u80fd\u9023\u5230 helper \u7684\u4eba\u90fd\u80fd\u91cd\u5efa\u4f60\u7684\u5c08\u6848\u3002<\/div>\n<\/section>\n<section id=\"auth\">\n<h2>11. <span class=\"cc-step\">\u6b65\u9a5f 6<\/span>\u767b\u5165\u8cc7\u6599\u5206\u96e2\u8207\u6301\u4e45\u5316<\/h2>\n<p>\u96d9 CLI \u80fd\u7a69\u5b9a\u5171\u5b58\u7684\u7b2c\u4e8c\u500b\u8981\u9ede\uff1a<strong>\u5169\u5957\u8a8d\u8b49\u8cc7\u6599\u593e\u5b8c\u5168\u5206\u958b<\/strong>\u3002<\/p>\n<pre><code>.\/home        \u2192 \/home\/codexuser\/.codex    \uff08Codex\uff1aauth.json\uff09\n.\/claude-home \u2192 \/home\/codexuser\/.claude   \uff08Claude\uff1a.credentials.json\uff09<\/code><\/pre>\n<p>\u5148\u5efa\u597d\u76ee\u9304\uff0c\u4e26\u628a\u64c1\u6709\u8005\u8a2d\u6210\u548c <code>CODEX_UID<\/code> \u4e00\u81f4\uff0c\u5bb9\u5668\u624d\u5beb\u5f97\u9032\u53bb\uff1a<\/p>\n<pre><code>cd \/volume1\/docker\/codex-claude\nmkdir -p home claude-home\nsudo chown -R 1026:1026 home claude-home\ntouch home\/.gitkeep claude-home\/.gitkeep<\/code><\/pre>\n<p>\u5c0d\u61c9\u7684 <code>.gitignore<\/code>\uff08\u5169\u500b\u8a2d\u5b9a\u76ee\u9304\u90fd\u8981\u5ffd\u7565\uff09\uff1a<\/p>\n<pre><code>.env\nhome\/*\n!home\/.gitkeep\nclaude-home\/*\n!claude-home\/.gitkeep<\/code><\/pre>\n<div class=\"cc-note\">\n<p><strong>\u9019\u6b21\u8e29\u5230\u7684\u5751\uff1aClaude \u7684 <code>~\/.claude.json<\/code> \u5728 <code>.claude\/<\/code> <em>\u5916\u9762<\/em>\u3002<\/strong>Claude Code \u6703\u628a\u300c\u5c08\u6848\u4fe1\u4efb\u72c0\u614b\u3001\u521d\u6b21\u8a2d\u5b9a\u3001MCP \u8a2d\u5b9a\u300d\u5beb\u5728 <code>\/home\/codexuser\/.claude.json<\/code>\uff0c\u800c\u6211\u5011\u53ea\u639b\u4e86 <code>.claude\/<\/code> \u76ee\u9304\u3002\u75c7\u72c0\u662f\uff1a<strong>\u6bcf\u6b21 <code>--build<\/code> \u91cd\u5efa\u5f8c\uff0cClaude \u53c8\u8981\u4f60\u91cd\u65b0\u78ba\u8a8d\u4fe1\u4efb\u9019\u500b\u8cc7\u6599\u593e<\/strong>\u3002\u82e5\u60f3\u9023\u9019\u500b\u4e5f\u4fdd\u7559\uff0c\u5148\u5efa\u597d\u6a94\u6848\u518d\u591a\u639b\u4e00\u884c\uff1a<\/p>\n<pre><code># \u4e3b\u6a5f\u7aef\u5148\u5efa\u7acb\u4e00\u500b\u7a7a JSON\uff0c\u907f\u514d Docker \u628a\u5b83\u7576\u8cc7\u6599\u593e\u639b\ntouch \/volume1\/docker\/codex-claude\/claude-home\/dot-claude.json\nsudo chown 1026:1026 \/volume1\/docker\/codex-claude\/claude-home\/dot-claude.json<\/code><\/pre>\n<p>\u7136\u5f8c\u5728 <code>claude-web<\/code> \u7684 <code>volumes<\/code> \u52a0\uff1a<\/p>\n<pre><code>      - .\/claude-home\/dot-claude.json:\/home\/codexuser\/.claude.json<\/code><\/pre>\n<p>Codex \u5c0d\u61c9\u7684\u6a94\u6848\uff08<code>~\/.codex\/<\/code> \u5167\u7684 <code>config.toml<\/code>\u3001<code>auth.json<\/code>\uff09\u90fd\u5728\u639b\u8f09\u76ee\u9304\u88e1\uff0c\u6c92\u6709\u9019\u500b\u554f\u984c\u3002<\/p>\n<\/div>\n<div class=\"cc-danger\"><strong>\u5207\u52ff\u628a\u8a8d\u8b49\u76ee\u9304\u516c\u958b\u3002<\/strong><code>home\/<\/code>\u3001<code>claude-home\/<\/code>\u3001<code>.env<\/code> \u90fd\u8981\u5728 <code>.gitignore<\/code>\uff1b\u4e5f\u4e0d\u8981\u5728\u6587\u7ae0\u3001\u622a\u5716\u6216\u8cbc\u9664\u932f\u8f38\u51fa\u6642\u9732\u51fa token\u3002<\/div>\n<\/section>\n<section id=\"launch\">\n<h2>12. <span class=\"cc-step\">\u6b65\u9a5f 7<\/span>\u5efa\u7f6e\u4e26\u555f\u52d5<\/h2>\n<p>\u5148\u8b93 Compose \u6aa2\u67e5 YAML \u8a9e\u6cd5\uff0c\u518d build \u4e26\u555f\u52d5\uff1a<\/p>\n<pre><code>cd \/volume1\/docker\/codex-claude\nsudo docker compose config --quiet      # \u6c92\u8f38\u51fa\u5c31\u662f\u8a9e\u6cd5\u6b63\u78ba\nsudo docker compose up -d --build --force-recreate\nsudo docker compose ps<\/code><\/pre>\n<p>\u7b2c\u4e00\u6b21 build \u6703\u4e0b\u8f09\u57fa\u790e\u6620\u50cf\u548c npm \u5957\u4ef6\uff0c\u9700\u8981\u5e7e\u5206\u9418\u3002\u5b8c\u6210\u5f8c\u958b\u700f\u89bd\u5668\uff1a<\/p>\n<ul>\n<li><strong>Codex\uff1a<\/strong><code>http:\/\/NAS-IP:7681<\/code><\/li>\n<li><strong>Claude\uff1a<\/strong><code>http:\/\/NAS-IP:7682<\/code>\uff08\u6216\u4f60\u5728 <code>.env<\/code> \u6539\u7684\u57e0\uff09<\/li>\n<\/ul>\n<div class=\"cc-ok\"><strong>\u6210\u529f\u9577\u76f8\uff1a<\/strong><code>docker compose ps<\/code> \u88e1 <code>codex-web<\/code>\u3001<code>claude-web<\/code>\uff08\u6709\u505a\u5b8c\u6574\u7248\u9084\u6709 <code>codex-project-helper<\/code>\uff09\u72c0\u614b\u90fd\u662f <code>Up<\/code>\uff0c\u4e14\u524d\u5169\u8005\u662f <code>(healthy)<\/code>\u3002\u5169\u500b\u7db2\u5740\u90fd\u80fd\u6253\u958b\u770b\u5230\u5404\u81ea\u7684 CLI \u6b61\u8fce\u756b\u9762\u3002<\/div>\n<\/section>\n<section id=\"verify\">\n<h2>13. \u9a57\u8b49\u3001\u9996\u6b21\u767b\u5165\u8207\u65e5\u5e38\u64cd\u4f5c<\/h2>\n<h3>\u78ba\u8a8d\u5169\u5957 CLI \u90fd\u5728<\/h3>\n<pre><code>sudo docker compose exec codex-web  codex  --version\nsudo docker compose exec claude-web claude --version<\/code><\/pre>\n<h3>\u78ba\u8a8d\u5065\u5eb7\u72c0\u614b\u8207\u57e0<\/h3>\n<pre><code>sudo docker compose ps\ncurl -I http:\/\/127.0.0.1:7681\ncurl -I http:\/\/127.0.0.1:7682<\/code><\/pre>\n<h3>\u9996\u6b21\u767b\u5165<\/h3>\n<p>\u5404\u81ea\u6253\u958b\u7db2\u9801\u7d42\u7aef\uff0c\u4f9d CLI \u756b\u9762\u5b8c\u6210\u5b98\u65b9\u767b\u5165\uff0f\u6388\u6b0a\u3002\u767b\u5165\u7d50\u679c\u6703\u5206\u5225\u5beb\u9032 <code>.\/home\/auth.json<\/code> \u8207 <code>.\/claude-home\/.credentials.json<\/code>\uff0c\u4e4b\u5f8c\u91cd\u5efa\u5bb9\u5668\u4e0d\u7528\u91cd\u767b\uff08<code>.claude.json<\/code> \u7684\u4f8b\u5916\u898b<a href=\"#auth\">\u7b2c 11 \u7bc0<\/a>\uff09\u3002<\/p>\n<figure id=\"attachment_1916\" aria-describedby=\"caption-attachment-1916\" style=\"width: 963px\" class=\"wp-caption aligncenter\"><img class=\"size-full wp-image-1916\" src=\"https:\/\/skypray.synology.me\/blog\/wp-content\/uploads\/2026\/08\/Claude-Code-CLI.png\" alt=\"Claude Code CLI \u767b\u5165\u754c\u9762\" width=\"963\" height=\"847\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/skypray.synology.me\/blog\/wp-content\/uploads\/2026\/08\/Claude-Code-CLI.png 963w, https:\/\/skypray.synology.me\/blog\/wp-content\/uploads\/2026\/08\/Claude-Code-CLI-300x264.png 300w, https:\/\/skypray.synology.me\/blog\/wp-content\/uploads\/2026\/08\/Claude-Code-CLI-768x675.png 768w\" sizes=\"auto, (max-width: 963px) 100vw, 963px\" \/><figcaption id=\"caption-attachment-1916\" class=\"wp-caption-text\">Claude Code CLI \u767b\u5165\u754c\u9762<\/figcaption><\/figure>\n<h3>\u770b\u65e5\u8a8c\u3001\u91cd\u555f\u55ae\u4e00\u670d\u52d9<\/h3>\n<pre><code>sudo docker compose logs --tail 100 codex-web\nsudo docker compose logs --tail 100 claude-web\n\n# \u53ea\u91cd\u555f Claude\uff0c\u4e0d\u6253\u64fe\u6b63\u5728\u5de5\u4f5c\u7684 Codex\nsudo docker compose restart claude-web\n# \u53ea\u91cd\u5efa Claude\uff08\u4f8b\u5982\u4f60\u6539\u4e86 Dockerfile \u60f3\u5347\u7d1a\uff09\nsudo docker compose up -d --build claude-web<\/code><\/pre>\n<h3>\u5b8c\u6574\u7248\uff1a\u5728 CLI \u88e1\u63a7\u5236\u5176\u4ed6\u5c08\u6848<\/h3>\n<p>\u5728 Codex \u6216 Claude \u7684\u7d42\u7aef\u88e1\uff1a<\/p>\n<pre><code>project-status  blog\nproject-logs    blog\nproject-restart blog\nproject-rebuild blog<\/code><\/pre>\n<p>\u9019\u4e9b\u6307\u4ee4\u6703\u900f\u904e <code>project-helper<\/code> \u57f7\u884c\uff0c\u4e14\u53ea\u5c0d\u767d\u540d\u55ae\u5c08\u6848\u6709\u6548\u3002<\/p>\n<\/section>\n<section id=\"security\">\n<h2>14. \u5b89\u5168\u6027\uff1a<code>ttyd -W<\/code> \u4e0d\u7b49\u65bc\u767b\u5165\u4fdd\u8b77<\/h2>\n<p><code>-W<\/code> \u53ea\u662f\u300c\u5141\u8a31\u5728\u7db2\u9801\u88e1\u6253\u5b57\u300d\uff0c<strong>\u6c92\u6709\u4efb\u4f55\u5bc6\u78bc\u9a57\u8b49<\/strong>\u3002\u628a 7681\uff0f7682 \u76f4\u63a5\u958b\u5230\u516c\u7db2\uff0c\u7b49\u65bc\u628a\u4e00\u500b\u80fd\u57f7\u884c\u4efb\u610f\u6307\u4ee4\u7684\u7d42\u7aef\u9001\u7d66\u5168\u4e16\u754c\u3002<\/p>\n<ul>\n<li>\u53ea\u5728\u53ef\u4fe1\u4efb\u7684 LAN\u3001VPN \u6216 Tailscale \u7db2\u8def\u88e1\u4f7f\u7528\u3002<\/li>\n<li>\u9700\u8981\u5916\u7db2\u5b58\u53d6\u6642\uff0c\u524d\u9762\u653e\u53cd\u5411\u4ee3\u7406\uff0c\u52a0\u4e0a HTTPS \u8207\u53ef\u9760\u7684\u8eab\u5206\u9a57\u8b49\uff08\u4e0d\u662f ttyd \u5167\u5efa\u7684 basic auth \u5c31\u597d\uff0c\u5efa\u8b70\u7528\u4ee3\u7406\u5c64\u7684 SSO \u6216\u81f3\u5c11\u5f37\u5bc6\u78bc\uff09\u3002<\/li>\n<li>\u9632\u706b\u7246\u53ea\u653e\u884c\u5fc5\u8981\u4f86\u6e90\uff0c\u4e0d\u8981\u505a\u7121\u9650\u5236\u7684 Port Forwarding\u3002<\/li>\n<li>\u5bb9\u5668\u7528\u975e root \u4f7f\u7528\u8005\uff08<code>codexuser<\/code>\uff09\uff0c\u53ea\u639b\u5169\u5957 CLI \u771f\u6b63\u9700\u8981\u7684\u5c08\u6848\u76ee\u9304\u3002<\/li>\n<li>Docker \u63a7\u5236\u6b0a\u53ea\u7d66 <code>project-helper<\/code>\uff0c\u4e26\u7528 token\uff0b\u767d\u540d\u55ae\u5c08\u6848\uff0b\u767d\u540d\u55ae\u52d5\u4f5c\u4e09\u91cd\u628a\u95dc\u3002<\/li>\n<\/ul>\n<div class=\"cc-note\">Dockerfile \u88e1\u7684 <code>bubblewrap<\/code> \u8b93 Codex \u80fd\u5728\u5bb9\u5668\u5167\u518d\u505a\u4e00\u5c64 sandbox\u3002\u82e5\u4f60\u8981\u653e\u5bec Codex \u7684\u6b0a\u9650\uff08\u4f8b\u5982 <code>codex --sandbox danger-full-access<\/code>\uff09\uff0c\u52d9\u5fc5\u5148\u78ba\u8a8d\u4e0a\u9762\u7684\u7db2\u8def\u9694\u96e2\u90fd\u5230\u4f4d\u3002<\/div>\n<\/section>\n<section id=\"troubleshooting\">\n<h2>15. \u6545\u969c\u6392\u9664\u8207\u5b89\u5168\u56de\u5fa9<\/h2>\n<h3>Claude \u7684\u7db2\u5740\uff087682\uff09\u6253\u4e0d\u958b<\/h3>\n<pre><code>sudo docker compose ps\nsudo docker compose logs --tail 200 claude-web\nsudo ss -lntp | grep ':7682'<\/code><\/pre>\n<p>\u82e5\u4e3b\u6a5f\u57e0\u88ab\u5225\u7684\u7a0b\u5f0f\u4f54\u7528\uff0c\u6539 <code>.env<\/code> \u7684 <code>CLAUDE_TTYD_PORT<\/code> \u5f8c <code>docker compose up -d<\/code>\u3002<\/p>\n<h3>\u8a2d\u5b9a\u76ee\u9304\u5beb\u4e0d\u9032\u53bb \/ \u6b0a\u9650\u932f\u8aa4<\/h3>\n<p>\u5e7e\u4e4e\u90fd\u662f NAS \u76ee\u9304\u64c1\u6709\u8005\u548c\u5bb9\u5668 UID \u4e0d\u4e00\u81f4\u3002\u78ba\u8a8d <code>.env<\/code> \u7684 <code>CODEX_UID<\/code>\uff0c\u53ea\u91dd\u5c0d\u5169\u500b\u8a2d\u5b9a\u76ee\u9304\u4fee\u6b0a\u9650\uff0c<strong>\u4e0d\u8981\u5c0d\u6574\u500b\u5c08\u6848 <code>chmod 777<\/code><\/strong>\uff1a<\/p>\n<pre><code>sudo chown -R 1026:1026 \/volume1\/docker\/codex-claude\/home \\\n                        \/volume1\/docker\/codex-claude\/claude-home<\/code><\/pre>\n<h3>\u91cd\u5efa\u5f8c Claude \u53c8\u8981\u6c42\u4fe1\u4efb\u8cc7\u6599\u593e<\/h3>\n<p><code>~\/.claude.json<\/code> \u6c92\u6709\u88ab\u4fdd\u7559\uff0c\u898b<a href=\"#auth\">\u7b2c 11 \u7bc0<\/a>\u7684\u591a\u639b\u4e00\u884c\u505a\u6cd5\u3002<\/p>\n<h3>\u642c\u79fb\u5f8c\u5bb9\u5668\u639b\u5230\u7a7a\u76ee\u9304<\/h3>\n<p>\u6aa2\u67e5 <code>compose.yaml<\/code> \u88e1\u6240\u6709\u820a\u7d55\u5c0d\u8def\u5f91\u662f\u5426\u90fd\u66f4\u65b0\u6210 <code>codex-claude<\/code>\uff0c\u518d\u91cd\u5efa\uff1a<\/p>\n<pre><code>sudo docker compose down\nsudo docker compose up -d --build<\/code><\/pre>\n<h3>project-control \u56de 401 \/ 403<\/h3>\n<p><code>codex-web<\/code>\uff0f<code>claude-web<\/code> \u7684 <code>PROJECT_HELPER_TOKEN<\/code> \u548c <code>project-helper<\/code> \u7684\u4e0d\u4e00\u81f4\uff0c\u6216 <code>.env<\/code> \u6c92\u88ab\u8b80\u5230\u3002\u4e09\u500b\u670d\u52d9\u8981\u7528\u540c\u4e00\u500b\u503c\uff1b\u6539\u5b8c <code>.env<\/code> \u8981 <code>docker compose up -d<\/code> \u8b93\u74b0\u5883\u8b8a\u6578\u91cd\u65b0\u6ce8\u5165\u3002<\/p>\n<h3>\u6574\u7d44\u56de\u5fa9\u6210\u539f\u672c\u7684 Codex \u74b0\u5883<\/h3>\n<p>\u5148\u505c\u65b0\u670d\u52d9\uff0c\u628a\u6709\u554f\u984c\u7684\u65b0\u76ee\u9304\u7559\u8457\u9664\u932f\uff0c\u518d\u5f9e\u5099\u4efd\u9084\u539f\uff1a<\/p>\n<pre><code>cd \/volume1\/docker\/codex-claude\nsudo docker compose down\n\ncd \/volume1\/docker\nsudo mv codex-claude codex-claude-failed\nsudo cp -a codex-backup-before-claude codex\ncd codex\nsudo docker compose up -d<\/code><\/pre>\n<div class=\"cc-warning\">\u56de\u5fa9\u524d\u5148\u78ba\u8a8d\uff1a\u5099\u4efd\u76ee\u9304\u78ba\u5be6\u5b58\u5728\u3001\u76ee\u7684\u5730 <code>codex<\/code> \u5c1a\u672a\u5b58\u5728\u3002<strong>\u4e0d\u8981\u76f4\u63a5\u522a\u6389\u5931\u6557\u7248\u672c<\/strong>\u2014\u2014\u88e1\u9762\u53ef\u80fd\u6709\u642c\u79fb\u5f8c\u624d\u7522\u751f\u3001\u9084\u6c92\u5099\u4efd\u7684\u767b\u5165\u8cc7\u6599\u6216\u8a2d\u5b9a\u8b8a\u66f4\u3002<\/div>\n<\/section>\n<section id=\"done\">\n<h2>16. \u5b8c\u6210\u6aa2\u67e5\u6e05\u55ae\u8207\u67b6\u69cb\u7e3d\u7d50<\/h2>\n<ul class=\"cc-checklist\">\n<li>\u820a <code>codex<\/code> \u76ee\u9304\u5df2\u5099\u4efd\u70ba <code>codex-backup-before-claude<\/code>\uff0c\u4e26\u6539\u540d\u70ba <code>codex-claude<\/code>\u3002<\/li>\n<li>Dockerfile \u540c\u6642\u5b89\u88dd <code>@openai\/codex<\/code> \u8207 <code>@anthropic-ai\/claude-code<\/code>\uff0c\u5169\u670d\u52d9\u5171\u7528\u9019\u4e00\u4efd\u6620\u50cf\u3002<\/li>\n<li><code>codex-web<\/code> \u5728 7681\u3001<code>claude-web<\/code> \u5728 7682\uff0c<code>docker compose ps<\/code> \u90fd\u662f <code>Up (healthy)<\/code>\u3002<\/li>\n<li>Codex \u7528 <code>.\/home<\/code>\u3001Claude \u7528 <code>.\/claude-home<\/code>\uff1b\u91cd\u5efa\u5bb9\u5668\u5f8c\u5169\u8005\u90fd\u4e0d\u7528\u91cd\u65b0\u767b\u5165\u3002<\/li>\n<li>\uff08\u5b8c\u6574\u7248\uff09<code>project-helper<\/code> \u6c92\u6709\u5c0d\u5916\u958b\u57e0\uff0c\u53ea\u6709\u5b83\u639b <code>docker.sock<\/code>\uff0c\u4e14\u9650\u5b9a\u767d\u540d\u55ae\u5c08\u6848\u8207\u4e94\u500b\u52d5\u4f5c\u3002<\/li>\n<li><code>.env<\/code>\u3001<code>home\/<\/code>\u3001<code>claude-home\/<\/code> \u90fd\u5728 <code>.gitignore<\/code> \u88e1\u3002<\/li>\n<li>\u5916\u90e8\u5b58\u53d6\u7531 VPN\u3001\u9632\u706b\u7246\u6216\u5e36\u9a57\u8b49\u7684 HTTPS \u53cd\u5411\u4ee3\u7406\u4fdd\u8b77\uff0c\u6c92\u6709\u88f8\u5954\u5230\u516c\u7db2\u3002<\/li>\n<\/ul>\n<p>\u9019\u5957\u8a2d\u8a08\u771f\u6b63\u7684\u91cd\u9ede\u4e0d\u662f\u300c\u628a\u5169\u500b\u4e92\u52d5\u5f0f CLI \u64e0\u9032\u4e00\u500b\u7d42\u7aef\u300d\uff0c\u800c\u662f\uff1a<strong>\u7528\u540c\u4e00\u4efd\u6620\u50cf\u58d3\u4f4e\u7dad\u8b77\u6210\u672c\uff0c\u7528\u670d\u52d9\u9694\u96e2\u6d88\u9664\u9023\u63a5\u57e0\u3001tmux session \u8207\u8a8d\u8b49\u7684\u885d\u7a81\uff0c\u518d\u7528\u4e00\u500b\u53d7\u9650\u7684\u5b88\u9580\u54e1\u670d\u52d9\u628a\u5371\u96aa\u7684 Docker \u63a7\u5236\u6b0a\u6536\u6582\u5230\u4e00\u8655\u3002<\/strong>\u65e5\u5f8c\u8981\u5347\u7d1a\u4efb\u4e00 CLI\uff0c\u53ea\u8981\u6539 Dockerfile\u3001\u91cd\u65b0 build\uff0c\u518d\u8996\u9700\u8981\u91cd\u5efa\u55ae\u4e00\u670d\u52d9\u5373\u53ef\u2014\u2014\u53e6\u4e00\u5957\u5b8c\u5168\u4e0d\u53d7\u5f71\u97ff\u3002<\/p>\n<\/section>\n<\/div>\n<div class=\"cc-footer\">\n<p>\u672c\u6559\u5b78\u4ee5 Synology NAS \u7684 <code>\/volume1\/docker\/codex-claude<\/code> \u70ba\u4f8b\uff1b\u5be6\u969b\u57f7\u884c\u524d\uff0c\u8acb\u4f9d\u4f60\u81ea\u5df1\u7684 UID\u3001\u639b\u8f09\u8def\u5f91\u3001\u7db2\u8def\u74b0\u5883\u8207\u5099\u4efd\u7b56\u7565\u8abf\u6574\u3002<\/p>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Synology NAS \u900f\u904e Docker \u90e8\u7f72 Codex CLI \u8207 Claude Code\uff0c\u5efa\u7acb\u96d9\u7db2\u9801\u7d42\u7aef\u3001\u6301\u4e45\u767b\u5165\u8207 tmux \u5de5\u4f5c\u968e\u6bb5\uff0c\u4e26\u5b8c\u6210\u6b0a\u9650\u63a7\u7ba1\u3001\u6545\u969c\u6392\u9664\u53ca\u5099\u4efd\u9084\u539f\uff0c\u65b9\u4fbf\u7528\u624b\u6a5f\u7ba1\u7406\u7db2\u7ad9\u8207\u5bb9\u5668\u3002<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[26],"class_list":["post-1910","post","type-post","status-publish","format-standard","hentry","category-computer","tag-webdev"],"_links":{"self":[{"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/posts\/1910","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/comments?post=1910"}],"version-history":[{"count":5,"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/posts\/1910\/revisions"}],"predecessor-version":[{"id":1960,"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/posts\/1910\/revisions\/1960"}],"wp:attachment":[{"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/media?parent=1910"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/categories?post=1910"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/skypray.synology.me\/blog\/wp-json\/wp\/v2\/tags?post=1910"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}