无头服务器上 Codex CLI 对接本地 DeepSeek:完整配置与排错记录
2026/9/16 2:37:52 网站建设 项目流程

我之前一直以为 Codex 这类终端编程工具必须连云端服务,直到这次要在无图形界面服务器上用 Codex 终端连接本地部署的 DeepSeek,才发现头号限制根本不是网络,而是你对整个调用链路的理解。整台服务器只有 SSH 可用,没有桌面、没有浏览器、没有可视化监控,所有操作都得在终端里完成。这篇文章就是记录我在这台无头 Linux 机器上,把 Codex CLI 和 Ollama 拉起的 DeepSeek 蒸馏模型对接起来的完整过程,包括架构设计、安装配置、踩坑排错和常驻运行,适合想在纯终端环境里跑本地 AI 编程 agent 的人参考。

1. 先想清楚架构:Codex、Ollama、DeepSeek 各干各的活

1.1 Codex 不是模型,它只是一个会调用模型的终端 agent

很多第一次接触 Codex 的人会有一个误区:装了 Codex 就等于拥有一个 AI 编程助手,打开就能用。实际上 Codex 本身不包含大模型权重,它只是一个运行在终端里的 agent 框架,负责理解用户意图、决定调用哪些工具、读写文件、执行命令,真正回答问题的是背后的模型推理服务。OpenAI 官方版本默认连的是 ChatGPT 的服务,但从 0.x 版本开始,Codex 引入了自定义 model provider 的机制,允许把请求转发到任何兼容 OpenAI 协议的服务上。这正好是接入本地 DeepSeek 的入口。

在本机没有图形界面这个前提下,我建议把 Codex 和模型服务放在同一台服务器上。这样通信链路就是 Codex 进程通过 HTTP 调用本机 Ollama,Ollama 再调度显卡或 CPU 跑 DeepSeek 蒸馏权重。整个过程不经过公网,数据不出内网,也不用额外管理密钥,API key 在本地只是一个占位符。更重要的是,Ollama 默认只监听 127.0.0.1,这样的部署姿势最安全,不会把推理端口暴露到局域网。

1.2 为什么要用 Ollama 当中间那层协议桥

如果你之前直接用过 DeepSeek 的模型权重,可能会想:既然我有模型文件,为什么不直接拉起推理进程让 Codex 去调?问题在于 Codex 走的是 OpenAI 的 Chat Completions 协议,而裸的 GGUF 模型文件没法直接提供这种 HTTP 接口。你需要一个推理服务框架,把模型加载、采样、KV cache 管理、并发请求这些事都处理好。

Ollama 的优势在于它内置了一个 OpenAI 兼容层,监听 11434 端口的/v1路径,对外暴露/v1/models/v1/chat/completions这些端点。也就是说,你不需要让 Codex 去理解 Ollama 的原生 API,只需要把 base_url 指到http://127.0.0.1:11434/v1,Codex 就会认为自己在和一个标准的 OpenAI 兼容服务对话。我在实际项目中比较过 vLLM 和 Ollama,vLLM 吞吐确实更高,但在无头服务器上只想快速跑通 Codex 接入流程时,Ollama 的安装和配置成本明显更低,对显存的管理也相对省心。

1.3 三条链路必须对齐:模型名、base_url、wire_api

整个对接过程最核心的是三个信息要对齐:base_url 的路径、模型名、Codex 的 wire_api 类型。base_url 必须以/v1结尾,否则请求会打到/chat/completions而不是/v1/chat/completions,直接 404。模型名必须和 Ollama 里拉取的 tag 完全一致,比如deepseek-r1:8b,多一个空格都不行。wire_api 建议显式设置为chat,因为 Codex 还支持更新的 Responses API,而 Ollama 目前并没有实现那个协议,如果默认走了 responses 就会报错。

2. 在无头服务器上把 DeepSeek 跑起来:模型选型与 Ollama 启动

2.1 DeepSeek 本地模型的选型思路,别被名字骗了

首先要明确一件事:本地部署 DeepSeek,不等于把官网那个 671B 的完整版 V3 或 R1 塞进你的显卡。那东西需要多卡集群,普通服务器根本喂不饱。实际大家在 Ollama 上拉取的 deepseek-r1 系列,是官方发布的蒸馏版本,用 Qwen 或 Llama 作为基座,参数量从 1.5B 到 70B 不等。虽然规模小了,但代码和推理能力在开源模型里依然能打。

我这次在无图形界面服务器上先列了一张表来选型,背后的逻辑很简单:按显存定模型。如果服务器是纯 CPU 机器,建议用 7B 或 8B 的 Q4 量化版,跑起来虽然速度一般,但至少能完成链路验证。如果有 16GB 显存,可以考虑 14B;有 24GB 显存,直接上 32B,Codex 的代码任务完成度会有肉眼可见的提升。用 1.5B 不是不行,但你能明显感觉到模型经常答非所问,尤其当 Codex 需要同时理解多文件上下文的时候,小模型很容易跟不上。

模型 tag量化后体积估算最低内存/显存估算适合场景
deepseek-r1:1.5b约 1.1GB4GB 内存链路测试、功能验证
deepseek-r1:7b约 4.7GB8GB 内存/显存CPU 或入门级 GPU,日常问答
deepseek-r1:14b约 9GB16GB 内存/显存有一定代码能力的轻量 agent
deepseek-r1:32b约 20GB24GB 显存配合 Codex 做正经编程任务

2.2 安装 Ollama 并拉动 DeepSeek 蒸馏模型

无头服务器上安装 Ollama 非常简单,官方提供了一个一键脚本。不过安装脚本需要从外网拉取资源,如果你的服务器访问外网受限,就得提前在能联网的机器上下载好对应架构的安装包再传上去。我这次是 x86_64 的 Ubuntu 服务器,直接用官方脚本装完后,用ollama pull deepseek-r1:8b拉取模型。拉取过程在 SSH 会话里会显示进度条,如果网络不稳定,可以多试几次,Ollama 支持断点续传,这设计很实用。

拉完模型后,先别急着配 Codex,先用 curl 测一下 OpenAI 兼容接口是否通了。很多配置问题都可以在这一步就暴露出来,比如 Ollama 服务没起来、端口被占用、模型 tag 写错。我当时的验证命令大概是这样的:

curl http://127.0.0.1:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1:8b", "messages": [ {"role": "user", "content": "你好,请用一句话介绍你自己"} ] }'

如果能正常返回一段包含choices字段的 JSON,就说明模型的推理服务和 OpenAI 兼容层都正常。这里有一个非常容易踩的坑:Ollama 服务默认加载模型的上下文长度可能很小,某些版本下只有 2048 或 4096,而 Codex 这类工具动辄要读整个代码仓库,上下文一超限就会报错。所以我强烈建议在启动 Ollama 前就把上下文长度调大。

2.3 用 Modelfile 固定上下文长度,而不是每次手动指定

调整 Ollama 上下文长度有两种常用方式。第一种是启动服务前设置环境变量OLLAMA_CONTEXT_LENGTH=32768,然后重启 Ollama;第二种是写一个 Modelfile,把num_ctx参数固化进模型配置里。我更推荐第二种,因为可复现、不依赖环境变量,换机器也不会丢配置。Modelfile 内容大概长这样:

FROM deepseek-r1:8b PARAMETER num_ctx 32768

然后执行ollama create deepseek-r1-ctx32 -f Modelfile,之后把请求里的模型名改成deepseek-r1-ctx32就行。注意,调大 num_ctx 不是没有代价的,上下文越长,KV cache 占用的显存或内存就越多。8B 模型在 32K 上下文下,即使量化后也可能额外吃掉几 GB 内存,所以不要盲目上 128K,先按实际任务需求给一个合理值。Codex 大多数任务 32K 上下文是够用的,个别大型仓库需要更大时再往上加。

2.4 通过 systemd 托管 Ollama,SSH 断开服务不中断

无图形界面服务器上跑服务,最忌讳的就是直接在 SSH 进程里启动。一旦你的 SSH 连接断开,启动 Ollama 的那个 shell 结束,Ollama 进程大概率也会跟着退出。我这次直接用 systemd 来托管 Ollama,这样开机自启、异常重启、日志查看都有了标准化的处理路径。

如果你在 Ubuntu 上用脚本安装 Ollama,系统其实已经生成了一个 systemd unit 文件。你可以通过systemctl edit ollama给它加一个 drop-in 配置,把需要的环境变量写进去:

[Service] Environment="OLLAMA_CONTEXT_LENGTH=32768" Environment="OLLAMA_KEEP_ALIVE=24h"

OLLAMA_KEEP_ALIVE=24h是我个人非常推荐的一个参数,它让模型加载进内存后至少保留 24 小时,避免每次请求都重新加载模型造成几十秒的冷启动等待。改完配置后执行systemctl daemon-reload && systemctl restart ollama,再用journalctl -u ollama -f看日志,确认服务正常起来了。所有操作都在终端里完成,非常适合无头服务器。

3. Codex CLI 的安装与直连配置:base_url、模型名与 API Key

3.1 在纯终端环境安装 Codex CLI

Codex CLI 的安装方式很多,官方推荐脚本和 npm 包都行。我这次选择 npm 方式,因为可以在无图形界面上干净地管理版本,升级也方便。前提是服务器上得有 Node.js 20 或更高版本。Ubuntu 自带 apt 里的 Node 版本一般偏老,我建议先装 nvm,再通过 nvm 装一个 LTS 版本的 Node,这样以后不会被系统包管理器的版本锁定拖累。

Node 装好之后,执行npm install -g @openai/codex,安装完成后用codex --version验证。如果服务器无法访问 npm 源,可以把本地机器上下载好的包打包传过去再离线安装,这个思路和前面 Ollama 的离线安装是一致的。注意 Codex 在运行时会创建~/.codex目录,如果你是用 root 用户操作的,要留意 root 的家目录和普通用户家目录的区别,避免明明装了却找不到配置文件的尴尬。

3.2 编辑 config.toml,把模型指向本地 Ollama

Codex 的配置文件位于~/.codex/config.toml。要让 Codex 走本地 DeepSeek,需要定义一个自定义 model provider,并让模型名指向 Ollama 里实际存在的 tag。我的配置是这样的:

model = "deepseek-r1-ctx32" model_provider = "local" [model_providers.local] name = "Local DeepSeek" base_url = "http://127.0.0.1:11434/v1" wire_api = "chat" env_key = "LOCAL_API_KEY"

解释一下每个字段。model是实际发给 Ollama 的模型名,必须和ollama list里面的 tag 一致。base_url是 Ollama 的 OpenAI 兼容端点,注意末尾一定要带/v1wire_api = "chat"告诉 Codex 使用 Chat Completions 协议,而不是默认的 Responses API。env_key是环境变量名,Codex 会从这个环境变量里读取 API key,即使 Ollama 并不校验 key 的值,也要保证这个环境变量存在并且非空,否则 Codex 可能尝试走官方登录流程,一直卡在认证环节。

设置完之后,在 shell 里导出环境变量:

export LOCAL_API_KEY="sk-noop"

这里 key 的内容随便填一个字符串就行,因为本地 Ollama 不会去校验它,但 Codex 会要求这个变量存在。我试过留空和完全不设置,两种都会报认证错误,填了之后就好了。

3.3 先交互式验证链路,再尝试非交互执行

配置完成后,直接在终端里输入codex就能进入交互模式。第一次启动时,如果 Codex 识别到自定义 provider,就不会强制要求登录 OpenAI 账号。我习惯先问一个简单问题,比如"当前目录有哪些文件",确认 Codex 能拿到模型响应,再让它干真正的活。此时模型可能动作慢,尤其 CPU 推理时,每个 token 都要等一会儿,这是正常的,不代表出故障。

交互模式验证通过后,非交互执行模式才是无头服务器上的重头戏。codex exec "你的任务描述"可以在一条命令里完成指定任务,适合写脚本调用,也适合通过 crontab 做定时任务。我第一次跑codex exec时还遇到一个细节:在没有 TTY 的环境里,某些 Codex 版本会拒绝启动交互式会话,但exec模式一般没问题;如果遇到终端相关的报错,用tmux开一个虚拟终端再执行,能绕开很多古怪的 TTY 问题。

4. 实测排错:从模型名对不上到上下文溢出

4.1 404 model not found,很多时候是模型名带了多余前缀

配置好之后我第一次启动 Codex,请求直接返回了 404 模型不存在。排查过程不复杂:先看 Ollama 日志,确认请求确实进来了,再看报错里的模型名。问题出在 Codex 的模型名称解析上。有些版本的 Codex 在配置了自定义 provider 后,会要求model字段写成provider名/模型名的形式,比如local/deepseek-r1-ctx32,然后把完整的字符串发给后端。但 Ollama 只认deepseek-r1-ctx32,不带 provider 前缀,所以请求被拒。

解决办法有两个方向:一是把model字段改成纯模型名,和我上面的配置一样,很多版本是支持的;二是如果版本强制要求前缀,那就把 Ollama 里的模型 tag 也改成一个带前缀的名字,人为对齐。我的建议是优先尝试纯模型名,不行再改 tag,尽量减少配置里的魔法值。判断标准很简单:请求能到 Ollama,且 Ollama 日志里报的模型名和ollama list对不上,就是这个问题。

4.2 codex ran out of room in the model's context:上下文不够用

跑了几个真实任务之后,我在 Codex 日志里看到了类似codex ran out of room in the model's context的报错。这个报错意味着模型上下文窗口被请求内容塞满了,Codex 需要做自动压缩或者续写,但本地模型没有那么大的上下文余量。这个现象在本地部署里太常见了,因为 Codex 会在对话中累积系统提示、工具调用结果、文件内容、命令输出,任何一个大文件cat下去都是几千 token。

解决这个问题的关键是两层。第一层是服务端扩容,就是我前面说的通过 Modelfile 调大num_ctx,把 Ollama 的上下文长度从默认的 2048 或 4096 提升到 32768,甚至更高。第二层是客户端习惯,尽量避免让 Codex 一次性读取超大文件,改用grep搜索、只看文件片段、分批加载代码等方式,把有效信息喂给模型。我实测下来,配合 32K 上下文的 14B 模型,处理中小型仓库的压力不大;如果是大型 monorepo,8B 模型即使有上下文也容易遗漏重点,这种场景更适合用 32B 模型。

4.3 SSH 一断,Codex 和 Ollama 全没了:tmux 是必需品

无头服务器还有一个经典的坑:直接在 SSH 会话里跑 Codex 交互模式,网络稍微抖动或本地笔记本合盖,SSH 断开,Codex 进程跟着就没了,会话里已经进行的任务全部作废。第一次遇到这个问题时我没多想,后来连续断了几次才意识到必须引入终端复用工具。tmux 就是专门干这个的,它可以在 SSH 断开后继续保留会话,重连后还能恢复现场。

我的操作流程很简单:

tmux new -s codex

进入 tmux 后,再执行codex启动交互模式。需要临时离开时,按Ctrl-b d脱离会话,SSH 断开也不怕。下次登录服务器后执行:

tmux attach -t codex

就能恢复到之前那个 Codex 会话。查看所有 tmux 会话用tmux ls。这个技巧在无头服务器上几乎是生存技能,不只是 Codex,任何长时间运行的命令都可以丢进 tmux 里。

4.4 认准端口和进程,别被多个 Ollama 实例搞晕

还有一次我遇到的诡异情况是:Codex 报连接拒绝,但 Ollama 看起来明明在运行。后来发现服务器上同时有两个 Ollama 进程,一个是 systemd 拉起的,另一个是我之前手动在某个 SSH 会话里启动的,两个进程抢着监听 11434 端口,导致请求被老的实例接收,而老的实例没有加载新的上下文配置。查这个问题用的命令其实很简单:

ss -tlnp | grep 11434 ps aux | grep ollama

看到多个实例后,我把手动启动的那个进程 kill 掉,只保留 systemd 管理的那一个,再把 systemd 配置改到位,重启后问题彻底消失。这个经验让我养成了一个习惯:无头服务器上跑任何服务,都用 systemd 或容器统一管理,绝不手动起进程,否则下次 SSH 重连你都记不清哪个进程是哪个配置。

5. 让它稳定常驻:资源监控、自动化调用与本地工作流扩展

5.1 观察推理资源和内存水位,防止静默 OOM

在无头环境下没有图形监控面板,判断服务是否健康只能靠命令行。我在跑 Codex 接 DeepSeek 时,会在另一个 tmux 窗口开着资源监控,重点看三样东西:显存占用、内存水位、11434 端口状态。nvidia-smi可以看显存;free -h看内存;journalctl -u ollama -f看 Ollama 日志里有没有 OOM 或加载失败的信息。

单独说一个经验:如果是纯 CPU 推理,千万不要把上下文长度调得很大,因为 KV cache 一样吃内存,内存不够系统会开始 swap,整个服务器都会变卡。我一开始给 14B 模型设了 64K 上下文,结果打开几个对话后内存直接见底,最后只能回退到 32K。判断合适的上下文长度,除了看模型参数量,还要结合你实际喂给 Codex 的代码规模,够用就行,不是越大越好。

5.2 用 codex exec 做无人值守任务,crontab 也能调

稳定跑通交互模式之后,我很快发现真正适合无头服务器的是codex exec非交互模式。它让 AI agent 可以出现在定时任务里,比如每天早上自动巡检代码仓库、生成变更摘要、检查配置语法、甚至自动修复一些常规问题。下面的例子展示了基本用法:

codex exec "查看当前目录的 git 状态,并总结最近 5 次提交的改动重点" \ --skip-git-repo-check

配合 crontab 就能变成定时任务。不过要记住,Codex 在 exec 模式下照样会执行命令和修改文件,所以在无人值守场景里一定要先明确沙箱策略和任务边界,最好让它在指定的工作目录里跑,避免越权操作。我第一次用 exec 时没限定目录,它居然自己去/tmp下面写了一堆临时文件,虽然不是坏事,但确实提醒我要管好运行环境。

5.3 一个 Ollama 可以喂多个 agent,不必为每个工具单开模型

接入成功后,你会发现本地这个 DeepSeek 推理服务不只是给 Codex 用的,Ollama 的 OpenAI 兼容端口是一个通用入口。我后来又用同一台服务器跑了其他前端工具,比如 Dify 这类工作流编排平台,也是在配置里把模型 API 地址指向http://127.0.0.1:11434/v1就能共用同一套模型。这样做的好处是模型只加载一份,显存和内存开销不会线性增加;坏处是多个客户端同时请求时,Ollama 的并发调度会让每个请求都变慢,尤其 CPU 推理时感受最明显。

我现在的做法是:Codex 这种交互式编程 agent 用 32B 模型保证质量,定时巡检和轻量任务再用一个 7B 模型,两个模型交替加载。虽然不能同时驻留内存,但任务错峰后体验仍在可接受范围内。

5.4 最后分享一个让我省心很多的小参数

所有链路跑通之后,我最后调的一个参数是OLLAMA_KEEP_ALIVE。默认情况下 Ollama 加载了一个模型,如果一段时间没有请求,可能会自动把它从内存里卸载,下次请求又得重新加载几十秒。对 Codex 这种多次交互的场景来说,这种等待非常影响体验。我把OLLAMA_KEEP_ALIVE设成了 24 小时,模型加载一次就能撑住一整天的连续使用,实际体验改善非常明显。如果你也在无头服务器上跑 Codex 接本地 DeepSeek,我建议从一开始就配上这个参数,少走不少弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询