FTP上传代码到Docker部署:完整闭环流程与排错指南 当业务需要把本地开发好的代码部署到服务器上同时又要用 Docker 统一管理运行环境时很多同学会卡在中间那个环节代码怎么安全高效地传到服务器用 Git 拉取当然可以但很多时候服务器只有临时文件需要更新或者团队还没完全走通 CI/CD 流程FTP 仍然是最直接、最常用的传输方式。这篇文章就来完整梳理一遍从 FTP 上传代码到 Docker 部署的闭环流程包含环境准备、服务器 FTP 配置、本地文件上传、Dockerfile 编写、镜像构建和容器运行的全部步骤以及高频报错的排查思路。文章适合这么几类读者一是有 Linux 基础、想系统落地 Docker 部署的开发者二是公司服务器已经装了 Docker但部署流程还是手工操作的后端同学三是刚接触云服务器、想把本地项目跑起来的学生和转行工程师。全文以实际可运行为标准尽量少讲空洞概念多给可以直接复制的命令和配置。1. 背景与核心概念1.1 为什么需要 FTP 上传代码FTPFile Transfer Protocol文件传输协议是 TCP/IP 协议族中的一员主要作用是在客户端和服务器之间传输文件。它诞生得很早但直到今天依然是很多运维场景里的基础工具。我们平时开发项目代码先写在本地电脑上经过调试、测试、确认功能正常后需要把代码文件放进服务器的指定目录里才能让服务器上的服务加载新代码。在 Docker 部署流程中FTP 上传这个动作通常发生在 Docker 构建之前。也就是说代码先传到服务器的一个工作目录然后在服务器上执行 docker build 通过 Dockerfile 把代码打包成镜像最后 docker run 启动容器对外提供服务。整个流程可以概括为本地开发 - FTP 上传到服务器工作目录 - docker build 构建镜像 - docker run 运行容器 - 访问服务有些人会问服务器上直接装 Git再 git pull 拉代码不就可以了吗当然可以。Git 适合代码仓库管理规范、团队协作频繁的场景。但 FTP 的优点是简单直接不需要配置仓库权限、不需要处理 SSH key尤其适合以下场景临时更新某个配置文件或者静态资源服务器上跑的是非 Git 管理的脚本或工具本地开发环境与服务器网络隔离只能开放 21 端口需要把编译好的产物dist、jar、war直接传上去而不是源码。1.2 Docker 在整个流程里的作用Docker 是一种操作系统级虚拟化技术它把应用以及应用依赖的运行环境一起打包成镜像。镜像相当于一个只读模板容器则是这个模板运行起来的实例。为什么要用 Docker因为传统部署方式里代码上传到服务器后还要手动装 JDK、Node.js、Nginx、MySQL 客户端等各种依赖每次都容易因为版本不一致导致“在我电脑上是好的在服务器上就挂了”的问题。Docker 把依赖固化到镜像里任何一台安装了 Docker 的服务器都能用同一种方式运行同一套服务可移植性很强。在实际部署流程中Docker 承担的是“最后一个环节的标准化”。FTP 负责把代码搬到服务器Docker 负责把代码变成可运行的服务。两者分工明确组合起来就是一套足够轻量又完整的部署方案。1.3 本文涉及的整体流程规划为了让读者对整个操作过程有全局概念这里先列出一份流程清单后面的章节会逐一展开。阶段操作内容工具/技术环境准备配置本地客户端和云服务器基础环境Windows / Mac / Linux云服务器Docker 安装在服务器上安装 Docker 运行环境Docker EngineFTP 服务搭建在服务器上安装并配置 FTP 服务vsftpdLinux本地文件上传连接 FTP将本地代码目录上传到服务器FileZilla 或 ftp 命令编写 Dockerfile定义镜像构建规则把代码和运行环境打包Dockerfile构建并运行容器构建镜像、启动容器、验证服务可访问docker build / docker run维护与排错日志查看、容器状态检查、常见问题修复docker logs / docker ps 等2. 环境准备与版本说明2.1 本地环境准备本教程不限定本地操作系统。无论你用的是 Windows 10/11、macOS 还是 LinuxFTP 客户端和 Docker 构建命令都可以正常工作。唯一的区别在于 Windows 上如果要用 Docker Desktop需要开启系统虚拟化支持macOS 上则直接安装 Docker Desktop 即可。本地环境需要准备以下软件FTP 客户端推荐 FileZilla跨平台且免费开源也可以直接用命令行 ftp 工具Windows 自带Linux 和 macOS 也默认包含。一个准备部署的项目目录可以是任意语言编写的 Web 项目本文示例以 Node.js 和 Python 两个常用镜像为例方便读者对应自己的项目调整。代码编辑器VS Code 或其他任意编辑器用于编写 Dockerfile。2.2 服务器环境准备服务器推荐使用 Linux 发行版比如 Ubuntu 20.04/22.04 或 CentOS 7/8这样可以获得最广泛的 Docker 和 FTP 软件包支持。如果手上的服务器是 Windows Server那么 FTP 和 Docker 的安装方式会有区别本文会额外给出一个简单的参考说明。服务器的运行配置建议最低 1 核 2G 内存Docker 构建过程中对内存有一定要求太小容易触发 OOM。磁盘至少留出 10G 可用空间因为镜像、容器日志和代码本身都会占用空间。开放安全组端口SSH 22、FTP 21、FTP 数据端口 20以及后续 Web 服务要使用的端口比如 8080 或 80。需要注意端口开放需要在两个层面完成云服务商控制台的安全组规则和服务器系统防火墙规则。很多场景下FTP 连不上是因为只配了系统防火墙但云控制台安全组没有放行或者反过来。2.3 版本与软件版本说明本文涉及的软件版本读者需要根据自己实际的服务器操作系统和本地电脑情况调整。以下列出的是当前常见环境中比较稳定的选择软件示例版本说明Ubuntu20.04 / 22.04本文命令基于 apt 包管理器CentOS7 / 8命令略有差异会用 yum 说明Docker Engine20.10 及以上推荐使用 Docker 官方源安装vsftpd3.0.xLinux 下最常见的 FTP 服务端FileZilla3.6x.xWindows/macOS 免费客户端版本这个东西必须提醒一句不要迷信最新版稳定优先。Docker 的 API 一直在演化但 docker build、docker run 这类基础命令非常稳定不会因为版本小升级而出现不兼容。vsftpd 的配置语法也多年没有大变化。2.4 项目结构示例为了后面演示 Dockerfile 的写法这里准备一个最简单的目录结构。假设我们的项目放在本地 D 盘的my-web-app目录下内容如下my-web-app/ ├── src/ │ └── index.js ├── package.json └── README.md如果是一个 Python 项目则可能是my-python-app/ ├── app.py ├── requirements.txt └── README.mdDockerfile 需要放在项目根目录下这样 docker build 才能通过上下文把整个项目目录打包给 Docker 引擎使用。后面实战章节会详细写这两种项目的 Dockerfile。3. 服务器 Docker 安装与基础配置3.1 为什么先在服务器上安装 DockerDocker 不是 CentOS 或 Ubuntu 系统自带的组件需要单独安装。安装 Docker 之后服务器才有能力执行 docker build、docker run 等命令。这一步是整个部署流程的地基。很多第一次部署的同学会直接在本地用 Docker Desktop 构建好镜像然后想办法把镜像文件传到服务器。这种方式是可行的镜像可以导出为 tar 包再导入但流程更繁琐而且镜像文件往往很大不适合跨网络传输。更常见的做法是在服务器上直接构建因为 Dockerfile 是文本文件代码上传完毕后一切都在服务器本地完成。所以FTP 上传代码到服务器之后下一步就是在服务器上执行 Docker 相关命令。为了保证这个流程顺畅先要把 Docker 装好。3.2 在 Ubuntu 上安装 Docker如果服务器使用 Ubuntu推荐用官方安装脚本sudo apt update sudo apt install -y apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository deb [archamd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable sudo apt update sudo apt install -y docker-ce安装完成后先确认 Docker 运行状态sudo systemctl status docker sudo docker version如果systemctl status docker显示 active (running)说明 Docker 服务已经在运行。为了避免每一条 docker 命令都加 sudo可以把当前用户加入 docker 用户组sudo usermod -aG docker $USER执行完后需要重新登录服务器用户组权限才会生效。这里解释一下为什么要加用户组Docker 守护进程默认以 root 权限运行普通用户执行 docker 命令会提示权限不足。把用户加入 docker 组相当于授予了该用户操作 Docker 的权限。这只适合信任的运维用户生产环境中需要谨慎管理权限。3.3 在 CentOS 上安装 Docker如果服务器是 CentOS 7 或 8安装命令如下sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce sudo systemctl start docker sudo systemctl enable dockerCentOS 8 需要注意如果系统自带 Podman 或容器相关软件可能与 docker-ce 冲突。一般服务器是干净的安装问题不大。systemctl enable docker的作用是设置开机自启这样服务器重启后 Docker 自动运行。3.4 配置 Docker 镜像加速器在国内服务器上安装好 Docker 后建议配置镜像加速器否则从 Docker Hub 拉取镜像时速度会非常慢甚至超时。Docker 的镜像源配置文件位于/etc/docker/daemon.json默认不存在需要手动创建sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://docker.mirrors.ustc.edu.cn] } EOF配置完成后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker镜像加速器的选择需要根据当前网络环境调整不同时间、不同地区的可用性不一样。配置完成后可以执行一条docker pull nginx:alpine测试拉取速度如果能顺利拉完就说明加速生效。4. 服务器 FTP 服务搭建与配置4.1 FTP 服务选型vsftpdLinux 下最常用的 FTP 服务端软件是 vsftpdVery Secure FTP Daemon。它的优点是稳定、轻量、安全性较高Ubuntu 和 CentOS 官方软件源里都包含。安装 vsftpd 的命令Ubuntusudo apt install -y vsftpdCentOSsudo yum install -y vsftpd安装完成后先查看配置文件位置。vsftpd 的主配置文件是/etc/vsftpd.conf。Ubuntu 默认配置可以直接用但需要做几处关键调整下面会详细说明。4.2 配置 FTP 用户与目录出于安全考虑不建议使用 root 账号直接登录 FTP。推荐创建一个专门的系统用户并把它的家目录设置为项目代码存放的目录。创建用户并指定家目录sudo useradd -m -d /home/deploy deploy sudo passwd deployuseradd参数说明-m自动创建家目录-d /home/deploy指定家目录路径deploy用户名这里可以按自己的习惯取比如 webuser、ftpuser。创建完用户后在用户家目录下创建一个www目录专门用来接收本地代码sudo mkdir -p /home/deploy/www sudo chown -R deploy:deploy /home/deploy/www把目录所有者改成 deploy 用户这样 FTP 上传文件时才有写权限。4.3 修改 vsftpd 配置编辑/etc/vsftpd.confsudo vim /etc/vsftpd.conf需要确认或修改以下几个关键配置项# 允许本地用户登录 local_enableYES # 允许文件写入 write_enableYES # 本地用户的 umask 值保持默认即可 local_umask022 # 启用被动模式 pasv_enableYES pasv_min_port30000 pasv_max_port31000 # 限定用户只能访问自己的家目录 chroot_local_userYES # 允许写入被 chroot 限定的目录 allow_writeable_chrootYES这里解释一下几个容易出问题的配置chroot_local_userYES会让用户登录后只能访问自己的家目录这个安全限制很好但如果家目录本身不允许写入FTP 上传时会报 550 Permission denied。所以同时设置allow_writeable_chrootYES允许用户在家目录里有写权限。被动模式端口范围pasv_min_port和pasv_max_port非常重要。FTP 有两种连接模式主动模式Active和被动模式Passive。默认情况下客户端软件通常使用被动模式服务器会开启一个随机端口用于数据传输。如果服务器防火墙没有放行这个端口段就会卡在列目录或上传环节。建议固定一个端口段比如 30000 到 31000然后在防火墙和安全组中一并放行。修改完配置后重启 vsftpdsudo systemctl restart vsftpd sudo systemctl enable vsftpd4.4 防火墙放行 FTP 端口如果是 CentOS 且启用了 firewalld需要执行sudo firewall-cmd --permanent --add-port21/tcp sudo firewall-cmd --permanent --add-port20/tcp sudo firewall-cmd --permanent --add-port30000-31000/tcp sudo firewall-cmd --reload如果是 Ubuntu 且启用了 ufw则执行sudo ufw allow 21/tcp sudo ufw allow 20/tcp sudo ufw allow 30000:31000/tcp sudo ufw reload还需要提醒的是云服务器一般还有一个安全组控制台。不同云厂商的安全组入口不同但逻辑一样必须确认 TCP 21、20 和 30000-31000 端口在安全组入方向规则中放行。很多 FTP 连接问题最后查下来都是安全组没配好。4.5 Windows Server 上的 FTP 搭建说明如果你的服务器是 Windows Server可以通过 IIS 的角色功能添加 FTP 服务。操作路径一般是服务器管理器 - 添加角色和功能 - 勾选 Web 服务器IIS- 勾选 FTP 服务。配置流程不再单独展开Windows 图形化界面比较直观。但需要注意云服务器安全组同样要放行 21 端口和被动模式端口段否则无法连接。5. 本地通过 FTP 上传代码5.1 使用 FileZilla 客户端上传FileZilla 是跨平台的 FTP 客户端界面简单支持拖拽上传和下载。打开 FileZilla在顶部填写主机服务器公网 IP比如123.123.123.123用户名前面创建的deploy密码对应用户的密码端口21点击快速连接后右侧显示服务器目录左侧显示本地目录。进入本地项目的根目录选择所有文件拖拽到右侧服务器的/home/deploy/www目录中。上传完成后可以右键点击远程目录里的文件选择“查看/编辑”确认内容是否完整。需要注意FileZilla 默认会显示隐藏文件如果项目里有.dockerignore或.env这类以点开头的文件务必确保它们被上传了。5.2 使用命令行 ftp 上传有些情况下服务器上只有命令行环境或者你更习惯用终端操作。本地命令行也能完成 FTP 上传。首先连接服务器ftp 123.123.123.123输入用户名和密码后进入 FTP 交互模式。常用命令有命令作用ls查看远程目录cd /home/deploy/www切换远程目录lcd D:\my-web-app切换本地目录put index.js上传单个文件mput *.js上传多个匹配文件passive切换被动模式bye退出 FTP一段典型的上传过程如下ftp passive Passive mode on. ftp cd /home/deploy/www ftp lcd D:\my-web-app ftp mput *命令行 ftp 功能相对基础不支持断点续传如果文件很大且网络不稳定可能上传到一半失败。对于大文件或整个目录的传输建议还是用 FileZilla它有断点续传和并发上传能力。5.3 上传后检查文件完整性代码上传到服务器后先不要急着构建 Docker 镜像。建议在服务器上先确认文件结构和内容ls -la /home/deploy/www再看下关键文件是否存在find /home/deploy/www -type f | wc -l这个命令会统计文件数量可以跟本地对比避免漏传文件。如果需要确认某个文件内容是否完整可以用cat或head查看head -20 /home/deploy/www/package.json确认无误后就可以进入 Docker 部署环节。需要说明FTP 是明文传输协议在公网环境下文件内容有被截获的风险。如果是公司内部测试环境这个方案完全够用如果涉及密码、密钥等敏感信息生产环境建议切换到 SFTP 或者 scp 命令后面最佳实践章节会再展开。6. Docker 部署实战构建镜像并启动容器6.1 编写 Dockerfile代码已经在服务器的/home/deploy/www目录下接下来要在这个目录里编写 Dockerfile。先看一个 Node.js 项目的 Dockerfile 示例。假设项目用 Express 框架入口文件是src/index.js。文件路径/home/deploy/www/Dockerfile# 基础镜像Node.js 20 的 Alpine 版本体积更小 FROM node:20-alpine # 设置工作目录 WORKDIR /app # 先复制 package.json 和 package-lock.json充分利用 Docker 缓存 COPY package*.json ./ # 安装项目依赖 RUN npm install --registryhttps://registry.npmmirror.com # 复制项目源码 COPY . . # 暴露应用端口 EXPOSE 3000 # 容器启动命令 CMD [node, src/index.js]逐行解释关键点FROM node:20-alpine指定基础镜像。alpine 是精简版 Linux体积小拉取速度快生产环境很常用。WORKDIR /app是在容器内部创建一个工作目录后面的操作都基于这个目录执行。COPY package*.json ./先把依赖清单复制进去。这一步单独提出来是为了利用 Docker 的层缓存机制只要 package.json 没变后面重新构建时就不会重复执行 npm install。RUN npm install在构建阶段安装依赖。这里加了 npm 镜像源可以提升安装速度。EXPOSE 3000声明容器对外提供服务的端口它只是声明真正发布端口还需要在 docker run 时用-p参数映射。再来看一个 Python Flask 项目的 Dockerfile# 基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 复制依赖清单并安装 COPY requirements.txt ./ RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 复制源码 COPY . . # 暴露端口 EXPOSE 5000 # 启动命令 CMD [python, app.py]编写 Dockerfile 的核心思路都一样先复制依赖清单安装依赖再复制源码最后声明端口和启动命令。不同的语言只是基础镜像和包管理器不同。6.2 编写 .dockerignore如果项目里有node_modules、.git、__pycache__这类不需要进入镜像的目录建议在项目根目录创建.dockerignore文件node_modules .git .gitignore *.log .env __pycache__ dist.dockerignore的作用是减小构建上下文体积加快构建速度同时避免把本地依赖目录或敏感文件打包进镜像。因为 docker build 会把上下文目录整个打包发送给 Docker 引擎如果不排除node_modules一个动辄几百 MB 的目录会被白白发送过去。6.3 构建 Docker 镜像一切准备就绪在服务器上进入项目目录并执行构建命令cd /home/deploy/www docker build -t my-web-app:v1.0 .命令说明-t my-web-app:v1.0给镜像打标签my-web-app是镜像名v1.0是版本号末尾的.表示构建上下文是当前目录。构建过程中Docker 会逐层执行 Dockerfile 里的指令终端会输出每一步的日志。第一次构建耗时较长因为要拉取基础镜像并安装依赖。看到类似下面的输出就说明构建成功Successfully built 6f9a4d2f1b3a Successfully tagged my-web-app:v1.0可以用docker images确认镜像列表docker images输出中应该能看到my-web-app和标签v1.0。6.4 运行 Docker 容器镜像构建完成后用docker run启动容器docker run -d --name my-web-app -p 3000:3000 my-web-app:v1.0参数说明-d后台运行容器--name my-web-app给容器命名方便后续管理-p 3000:3000把服务器的 3000 端口映射到容器内的 3000 端口。注意如果服务器防火墙和安全组没有放行 3000 端口外部仍然无法访问最后的my-web-app:v1.0是镜像名。运行完成后查看容器状态docker ps状态是Up就说明容器正常在运行。查看日志docker logs -f my-web-app如果端口没有冲突、依赖安装正确日志里应该会出现服务启动成功的提示比如 Node.js 项目的Server running at http://0.0.0.0:3000。最后用浏览器访问http://服务器公网IP:3000看到页面内容整个部署流程就走通了。6.5 停止与重新部署后续代码更新后只需要重新上传代码然后执行以下几个命令# 停止旧容器 docker stop my-web-app # 删除旧容器 docker rm my-web-app # 删除旧镜像可选 docker rmi my-web-app:v1.0 # 重新构建镜像 docker build -t my-web-app:v1.0 . # 重新启动容器 docker run -d --name my-web-app -p 3000:3000 my-web-app:v1.0这一套操作就是最基础的“重新构建 重新部署”流程。如果项目代码更新频繁可以结合 Docker Compose 或 CI/CD 工具进一步优化。下面简单说一下用 Docker Compose 的做法。6.6 使用 Docker Compose 管理部署当容器配置比较复杂比如需要环境变量、数据卷挂载、多个服务联动使用 docker run 会变得很长。这时候可以在项目根目录创建docker-compose.ymlversion: 3.8 services: web: build: . image: my-web-app:v1.0 container_name: my-web-app ports: - 3000:3000 environment: - NODE_ENVproduction restart: always然后通过 Docker Compose 管理和启动docker compose up -d查看服务状态docker compose ps停止服务docker compose downrestart: always的意思是容器异常退出后会自动重启这对线上服务的稳定性很重要。使用 Compose 之后重新部署只需要docker compose up -d --build--build参数会在启动前重新构建镜像省去了手动 stop、rm、build、run 这一连串操作。7. 常见问题与排查思路在实际部署过程中FTP 连接失败、Docker 构建报错、容器启动失败这几个问题出现频率最高。下面把常见场景整理成表格并给出排查思路。问题现象常见原因解决思路FileZilla 连接超时服务器防火墙或安全组未放行 21 端口检查安全组和 firewall/ufw 规则服务器返回 530 Login incorrect用户名或密码错误用户未创建成功确认 deploy 用户存在密码正确上传文件时卡住或提示 500被动模式端口段未放行放行 pasv_min_port 到 pasv_max_port 端口段上传文件提示 550 Permission denied目标目录无写权限chown 目录所有者检查目录权限docker build 报 network timeout 错误镜像源或依赖源网络不稳定配置 Docker 镜像加速器npm/pip 换镜像源docker ps 看不到容器docker run失败后容器已退出用docker ps -a查看所有容器再用docker logs查日志容器启动后立即退出启动命令有问题或端口被占用查看日志检查 CMD 命令和端口占用访问服务超时安全组未放行业务端口在云控制台放行对应端口比如 3000下面挑几个重点问题单独展开。7.1 Docker Desktop 启动报虚拟化错误有同学习惯在本地用 Docker Desktop 做验证结果启动时报Virtualization support was detected but is disabled或者Docker Desktop failed to start because virtualization support wasnt detected这个问题的根因是 Windows 系统没有开启虚拟化功能。需要到 BIOS 中开启 Intel VT-x 或 AMD-V 虚拟化技术然后在 Windows 功能里启用“适用于 Linux 的 Windows 子系统”和“虚拟机平台”。开启后重启电脑Docker Desktop 一般就能正常启动。如果确认 BIOS 已经开启虚拟化但 Docker Desktop 仍然报错可以在管理员 PowerShell 中执行bcdedit /set hypervisorlaunchtype auto然后重启电脑。这一步是让 Windows Hypervisor 启动Docker Desktop 依赖它运行 Linux 虚拟机。7.2 FileZilla 能连接但列不出目录这个问题非常常见。登录成功但远程目录列表一直在刷新或者超时。原因几乎都是被动模式的数据端口没有放行。vsftpd 配置中已经设置了pasv_min_port30000和pasv_max_port31000那么服务器防火墙和安全组必须放行 30000 到 31000 的 TCP 端口。另外FileZilla 默认使用被动模式如果服务器没有在配置中启用被动模式或者被动端口与防火墙冲突就会出现这种现象。排查时可以这样做先用命令检查 vsftpd 配置是否生效sudo grep -E pasv|listen /etc/vsftpd.conf再确认防火墙状态Ubuntu 下sudo ufw statusCentOS 下sudo firewall-cmd --list-ports最后到云服务商控制台确认安全组入方向是否有 30000-31000 的规则。7.3 docker build 卡在下载依赖如果构建过程中 npm install 或 pip install 卡住通常不是命令的问题而是网络问题。解决方案是给包管理器配置国内镜像源。npm 在 Dockerfile 中指定镜像源RUN npm install --registryhttps://registry.npmmirror.compip 在 Dockerfile 中指定镜像源RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple也可以把 Docker 的基础镜像拉取改为使用镜像加速器这在前面的章节已经介绍过。构建成功后网络问题一般不会影响容器运行。7.4 容器启动后立即退出docker ps看不到容器但docker ps -a能看到一个 Exited 状态的容器。原因是容器内的主进程执行完就退出或者启动命令报错。排查步骤docker ps -a docker logs 容器名日志中如果能看到报错堆栈按报错原因针对性修改。比如 Node.js 项目找不到入口文件检查 Dockerfile 中的CMD [node, src/index.js]路径是否正确Python 项目找不到模块检查requirements.txt是否完整。另外有一种情况开发环境依赖安装成功但容器启动时仍然报模块找不到常见原因是 .dockerignore 把依赖目录排除后Dockerfile 又忘了COPY依赖清单。确认 Dockerfile 中先 COPY 依赖清单再安装依赖的顺序是否正确。8. 最佳实践与工程建议8.1 生产环境尽量使用 SFTP/SCP 替代 FTPFTP 协议有一个致命短板包括用户名、密码、文件内容在内的所有数据都是明文传输。只要数据经过公网就可能被中间人截获。如果在生产环境中传输敏感代码或配置建议改用 SFTPSSH File Transfer Protocol或 scp 命令。SFTP 与 FTP 的命令和操作逻辑相似但底层走的是 SSH 协议数据加密传输。FileZilla 同样支持 SFTP只需要在主机栏填写sftp://服务器IP端口改为 22。如果已经配置好 SSH 密钥也可以在 FileZilla 里加载私钥文件实现免密登录。8.2 不要在 FTP 目录下存放敏感配置很多项目根目录有.env文件里面包含数据库连接串、密码、API Key 等敏感信息。通过 FTP 上传代码时如果 .env 文件被意外传到了服务器再被其他人拿到后果很严重。建议在本地就把敏感配置与代码分离或者使用.dockerignore把.env排除在镜像之外。生产环境的配置可以通过 Docker 的环境变量参数注入docker run -d --name my-web-app -p 3000:3000 \ -e DB_PASSWORDyour_password \ my-web-app:v1.0或者使用 Docker Compose 的 environment 配置项。这样做的好处是敏感信息不会被打包进镜像不会因为镜像分发导致泄露。8.3 镜像版本管理不要只使用latest标签。latest 表示最新但部署时无法确定当前服务器上跑的是哪一次构建的镜像回滚时也难以定位。建议每次构建都打上明确的版本号docker build -t my-web-app:v1.0 . docker build -t my-web-app:v1.1 .如果需要回滚旧版本只要用旧镜像重新运行容器即可docker run -d --name my-web-app -p 3000:3000 my-web-app:v1.0如果镜像存放在私有仓库中可以同时设置多个标签比如v1.0和latest都指向同一个镜像方便统一管理。8.4 数据卷挂载与日志持久化对于需要持久化的数据比如上传的图片、数据库文件、日志不要存储在容器内部。容器的文件系统是临时的容器删除后数据就没了。推荐使用 Docker 数据卷将宿主机目录挂载到容器内部docker run -d --name my-web-app \ -p 3000:3000 \ -v /home/deploy/data:/app/data \ my-web-app:v1.0这样容器内部/app/data目录的数据会直接写到服务器/home/deploy/data目录即使容器删除重建数据也不会丢失。日志同理可以通过--log-opt参数限制日志大小docker run -d --name my-web-app \ --log-opt max-size10m \ --log-opt max-file3 \ my-web-app:v1.0防止容器长时间运行导致日志文件膨胀占满服务器磁盘。8.5 使用 Docker Compose 或 CI/CD 优化部署流程手工执行docker stop、docker rm、docker build、docker run这一系列命令偶尔操作一两次没问题但频繁部署时容易出错。现阶段推荐的做法团队规模小、部署频率低用 Docker Compose 管理容器生命周期团队协作频繁、部署频率高把 FTP 上传和 Docker 构建接入 CI/CD 流水线比如 Jenkins、GitLab CI、GitHub Actions。代码推送到仓库后自动触发构建在服务器上自动拉取最新代码并重建容器不再需要人工上传。无论采用哪种方式都建议保持 Dockerfile 的幂等性同一份代码、同一个 Dockerfile在任何时间构建出的镜像行为一致。这是可维护性的基础。8.6 定期更新基础镜像基础镜像里的软件包可能存在安全漏洞建议定期执行docker pull node:20-alpine docker pull python:3.11-slim然后重新构建服务镜像。镜像更新前先在测试环境验证再把新镜像部署到生产。没有把握的情况下不要贸然升级基础镜像的大版本比如从 Node 18 跳到 Node 20因为某些依赖可能不兼容。9. 总结与下一步方向到这里一条完整的部署链路已经跑通了本地代码通过 FTP 上传到服务器服务器上的 vsftpd 服务接收文件随后在服务器上通过 Dockerfile 构建镜像最后用 docker run 或 Docker Compose 启动容器对外提供服务。整篇文章的关键技术点可以归纳为三个方面。第一是 FTP 服务的搭建和配置重点在于用户权限、被动模式端口、防火墙和安全组的配合能够解决绝大多数连接类问题。第二是 Dockerfile 的编写逻辑依赖清单先行、源码后置、合理使用 .dockerignore 是控制构建时间和镜像体积的关键。第三是容器的生命周期管理从构建、启动、日志查看到重新部署每一条命令都有明确的职责。下一步建议按自己的技术基础选择学习路线。如果对 Docker 还比较陌生可以先系统学习镜像和容器的核心概念理解分层存储机制如果 Docker 基础已经没问题可以尝试把 FTP 手动上传替换成 Git 仓库拉取再配合 Docker Compose 做多容器编排如果已经能够熟练管理单个服务的部署可以考虑把整体流程迁移到 CI/CD 工具中实现代码提交后自动构建、自动部署。每一步都在解决上一个环节中出现的重复劳动问题。最后给一个部署前检查小清单服务器 SSH 能连通、FTP 用户和目录权限配好、防火墙和安全组端口都放行、代码目录结构完整、Docker 服务正常运行、Dockerfile 里的启动命令和实际入口文件一致。这些都没有问题的话一次成功的部署基本就是顺手的事。