从域名定位到 Docker:找到 Linux 上实际运行的 Node 服务
假设现在只有一台服务器的 SSH 权限和一个域名 www.example.com。不知道 Nginx 配置文件在哪里,不知道 Node 监听哪个端口,也不知道服务是否运行在 Docker 中。定位顺序应固定为:域名对应的 Nginx server -> Nginx 上游 -> 监听端口 -> 主机进程或 Docker 容器 -> 工作目录和挂载目录。
不要先搜索 / 下的所有配置文件,也不要先猜端口或容器名。历史配置、备份文件和停止的容器都会干扰判断;每一步都应由上一步的实际输出决定。
WARNING
本文所有“示意输出”仅说明命令的常见输出格式和下一步判断方法,不是任何目标服务器的真实日志。实际 PID、时间、路径、镜像名和端口必须以当前服务器输出为准。
先确定请求链路
www.example.com
-> Nginx 生效的 server 块
-> proxy_pass 或 upstream
-> 本机端口、Unix Socket 或远程地址
-> Node 进程,或 Docker 发布端口
-> 容器状态、启动命令、工作目录和挂载目录本文以 Nginx 最终指向本机 127.0.0.1:3000 为主线。若配置指向 Unix Socket 或另一台主机,后文也给出分支处理方式。
第一步:用域名找到生效的 Nginx 配置
nginx -T 会检查并输出主配置及所有 include 展开的配置,因此它反映的是 Nginx 实际加载的内容。
sudo nginx -T 2>&1 \
| grep -nE -C 8 'server_name[[:space:]].*www\.example\.com'示意输出:
184:# configuration file /etc/nginx/conf.d/www.example.com.conf:
185:server {
186: listen 443 ssl http2;
187: server_name www.example.com;
188:
189: location / {
190: proxy_pass http://127.0.0.1:3000;
191: }
192:}输出中的 # configuration file 给出实际加载的文件;proxy_pass 给出下一个定位对象。这里的上游是本机 3000 端口。
如果 proxy_pass 使用上游名称,例如 proxy_pass http://web_backend,继续定位该上游组:
sudo nginx -T 2>&1 \
| grep -nE -C 5 'upstream[[:space:]]+web_backend'示意输出:
42:upstream web_backend {
43: server 127.0.0.1:3000;
44:}此时同样得到 3000,再进入端口定位步骤。若这里出现多个 server,应根据权重、备份节点和健康检查配置继续判断,不能任选一个端口。
如果配置是 Unix Socket:
proxy_pass http://unix:/run/web/app.sock;这不是 TCP 端口,直接跳到 Unix Socket 分支。
第二步:通过端口判断服务运行在主机还是 Docker
将上一步得到的端口写入变量:
port=3000
sudo ss -ltnp "sport = :$port"示意输出:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:* users:(("docker-proxy",pid=2814,fd=4))重点查看输出中的进程名和 PID:
| 监听者 | 说明 | 下一步 |
|---|---|---|
node | Node 直接运行在主机 | 进入 主机 Node 分支 |
docker-proxy | Docker 将宿主机端口发布给容器 | 进入 Docker 分支 |
| 没有输出 | 服务未监听该端口,或 Nginx 并未转发到本机 TCP 端口 | 回到 Nginx 配置检查上游类型 |
docker-proxy 只是端口转发进程,不是应用本身。不能从它的 PID 推断 Node 项目目录,必须继续定位容器。
主机 Node 分支
若上一步的 ss 输出显示 node,假设其 PID 为 1234,先确认它实际以什么命令运行:
pid=1234
sudo ps -o pid,ppid,user,lstart,cmd -p "$pid"示意输出:
PID PPID USER STARTED CMD
1234 1 deploy Mon Jul 24 09:18:42 2026 /usr/bin/node dist/server.js查看进程当前工作目录:
sudo readlink -f "/proc/$pid/cwd"示意输出:
/srv/web-api/current查看 Node 入口文件和启动参数:
sudo cat "/proc/$pid/cmdline" | tr '\0' '\n'示意输出:
/usr/bin/node
dist/server.js三个命令分别提供启动命令、进程当前工作目录和 Node 入口文件。/proc/<PID>/cwd 是运行时目录;入口脚本可能位于其下的 dist/ 或 build/ 目录。
再检查进程是否由 systemd 托管:
sudo cat "/proc/$pid/cgroup"示意输出:
0::/system.slice/web-api.service若输出包含 web-api.service,读取服务定义中的工作目录与启动命令:
sudo systemctl show web-api.service \
-p MainPID \
-p WorkingDirectory \
-p ExecStart \
-p FragmentPath示意输出:
ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node dist/server.js ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
FragmentPath=/etc/systemd/system/web-api.service
MainPID=1234
WorkingDirectory=/srv/web-api/current查看主 Unit 与 drop-in 覆盖文件:
sudo systemctl cat web-api.service示意输出:
# /etc/systemd/system/web-api.service
[Service]
User=deploy
WorkingDirectory=/srv/web-api/current
ExecStart=/usr/bin/node dist/server.js
Restart=alwaysWorkingDirectory、ExecStart 与 /proc/<PID>/cwd 应一起判断。它们不一致时,前者是部署声明,后者是进程当前实际目录。
Docker 分支
先按发布端口查找容器,而不是按猜测的容器名搜索:
port=3000
docker ps --filter "publish=$port" \
--format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Ports}}'示意输出:
CONTAINER ID NAMES IMAGE PORTS
a1b2c3d4e5f6 web-api registry/web-api:1.4 127.0.0.1:3000->3000/tcp得到容器名称后,后续命令使用变量:
container=web-api示意输出:
# 变量已设置;后续命令将操作 web-api 容器。查看容器是否正在运行
docker inspect "$container" \
--format '状态={{.State.Status}} PID={{.State.Pid}} StartedAt={{.State.StartedAt}} RestartCount={{.RestartCount}}'示意输出:
状态=running PID=8214 StartedAt=2026-07-24T01:20:11.582Z RestartCount=0再查看 Docker 展示的状态和端口映射:
docker ps --filter "name=^/${container}$" \
--format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'示意输出:
NAMES STATUS PORTS
web-api Up 2 hours 127.0.0.1:3000->3000/tcp状态为 running 或 Up 才表示容器正在运行。RestartCount 持续增加时,服务可能反复退出;先查看最近日志,而不是立刻重启容器:
docker logs --tail 100 "$container"示意输出:
2026-07-24T09:20:15.019Z info: server listening on 0.0.0.0:3000
2026-07-24T09:23:41.654Z info: GET /health 200 3ms需要持续观察时再使用下面的命令,按 Ctrl+C 退出日志跟随,不会停止容器:
docker logs --tail 100 --follow "$container"示意输出:
2026-07-24T09:25:08.201Z info: GET /api/users 200 12ms
2026-07-24T09:25:11.887Z info: GET /health 200 2ms查看容器启动的应用
先读取镜像配置声明的工作目录:
docker inspect "$container" --format 'WorkingDir={{.Config.WorkingDir}}'示意输出:
WorkingDir=/app读取入口程序和命令参数:
docker inspect "$container" --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'示意输出:
Entrypoint=["node"] Cmd=["dist/server.js"]再从宿主机查看容器进程。即使镜像没有安装 ps,这个命令仍可用于观察容器内 PID:
docker top "$container" -eo pid,ppid,user,etime,args示意输出:
PID PPID USER ELAPSED COMMAND
8214 8190 node 02:04:31 node dist/server.js如果启动命令中有 node dist/server.js,它只说明容器内的入口文件。要确定宿主机上的代码或配置位置,需要继续看挂载。
找到宿主机挂载目录
docker inspect "$container" \
--format '{{range .Mounts}}{{.Type}}{{"\t"}}{{.Source}}{{"\t->\t"}}{{.Destination}}{{println}}{{end}}'示意输出:
bind /srv/web-api/current -> /app
volume /var/lib/docker/volumes/web-api-data/_data -> /app/data输出应按下面方式解释:
Type | Source 的含义 | 是否通常是宿主机项目目录 |
|---|---|---|
bind | 宿主机实际路径 | 可能是代码、配置或日志目录,需结合 Destination 判断 |
volume | Docker 管理的卷路径或卷名 | 通常是数据目录,不应直接当作代码发布目录 |
tmpfs | 内存文件系统 | 不是持久化部署目录 |
本例中 bind /srv/web-api/current -> /app,且 WorkingDir=/app,因此 /srv/web-api/current 是最有力的宿主机部署目录证据。
进入容器确认运行时目录
优先使用 sh,因为许多精简镜像没有 bash:
docker exec -it "$container" sh示意输出:
/app #进入后依次执行:
pwd
readlink -f /proc/1/cwd
tr '\0' '\n' </proc/1/cmdline示意输出:
/app
/app
node
dist/server.js也可以不进入交互终端,直接读取 PID 1 的目录与启动参数:
docker exec "$container" sh -c 'readlink -f /proc/1/cwd; tr "\0" "\n" </proc/1/cmdline'示意输出:
/app
node
dist/server.js如果镜像是 distroless,可能没有 sh。此时 docker exec 的示意错误形式如下:
OCI runtime exec failed: exec: "sh": executable file not found in $PATH使用 docker inspect、docker top 和挂载信息完成定位,不要为了进入容器而修改镜像或安装工具。
Unix Socket 分支
当 Nginx 指向 /run/web/app.sock 时,检查 Socket 的监听者:
sudo ss -lxnp | grep '/run/web/app.sock'示意输出:
u_str LISTEN 0 511 /run/web/app.sock 13846 * 0 users:(("node",pid=1234,fd=21))若监听者是 Node,按 主机 Node 分支 使用 PID 检查工作目录。若 Socket 由容器通过 bind mount 提供,ss 的结果和 Docker 容器信息需要结合 docker inspect 的 Mounts 判断。
收束:每一步要确认的事实
域名匹配哪个 server_name?
-> Nginx 转发到哪个上游?
-> 上游是本机端口、Unix Socket,还是远程地址?
-> 本机端口由 node 还是 docker-proxy 监听?
-> 容器实际是否 running,入口命令是什么?
-> bind mount 是否把宿主机目录挂到应用工作目录?只有这条证据链完整时,才能把某个目录称为“正在运行的服务部署目录”。单独看到 package.json、旧容器、停止容器或 Nginx 历史配置,都不足以证明它正在接收 www.example.com 的请求。
