
这次我们来看一套非常实在的部署流程本地代码通过 FTP 传到服务器再用 Docker 在服务器上跑起来。很多小团队、独立开发者现在还不愿意上完整的 CI/CD项目也不大用 FTP 手动上传、SSH 登录、Docker 构建是最快的上线方式。这套流程不需要额外搭代码仓库、不需要写流水线只要能装 Docker、能传文件服务就能跑。这篇文章会把整条链路拆开FTP 工具怎么连接、上传哪些文件、Dockerfile 怎么写、镜像怎么构建、容器怎么运行、端口怎么映射、日志怎么看以及常见的端口冲突、权限问题和安全配置。整个过程以手动部署为主适合个人项目和中小型业务的快速发布。如果你已经有 K8s 或 GitLab Runner那这篇文章的价值不大。先说清楚几个重点Docker 负责服务器端的应用隔离和运行FTP 解决本地到服务器的文件传输最后通过浏览器或 curl 访问服务验证容器是否正常。整体门槛不高一台 Linux 服务器、本机装一个 FTP 客户端、服务器装好 Docker 就能操作。下面按实际部署顺序展开。1. 核心能力速览能力项说明部署目标将本地代码上传到服务器并通过 Docker 运行核心工具FTP/SFTP 客户端 Docker Engine Linux 服务器主要流程本地打包代码 - FTP 上传 - SSH 登录 - 编写 Dockerfile - 构建镜像 - 运行容器操作系统服务器建议 Ubuntu/Debian/CentOS本地任意系统均可Docker 要求Docker Engine 20.10Docker Compose 可选FTP 要求支持 FTP/SFTP 的客户端推荐 FileZilla、WinSCP、FinalShell是否支持 APIDocker 守护进程可开启 TCP API默认关闭生产环境不建议直接暴露是否支持批量任务支持通过 FTP 批量目录上传 docker compose 批量启动多个服务硬件门槛最低 1 核 1G 内存实际取决于应用类型和并发量适合场景小型 Web 服务、个人项目、临时交付、无 CI/CD 环境的快速发布这套方案本质上是“手动发布流程”的标准化FTP 只负责传输Docker 负责环境一致性。好处是服务器不需要安装 Node、Python 或 Java 的运行时所有依赖都打进镜像里坏处是每次更新都需要手动上传文件并重新构建容器比 Git 触发自动部署多两步操作。2. 适用场景与使用边界Docker FTP 的方式适合以下几类场景。第一个人博客、小型 API、管理后台这类轻量应用。没有专职运维也不想引入复杂的 CI/CD 工具链。把代码传到服务器构建镜像一条 docker run 命令就能启动回滚也简单重新跑一个旧版本镜像即可。第二客户短期项目或临时演示环境。对方只需要一个可访问的 HTTP 服务不需要完整的部署平台。FTP 上传代码后用 Docker 隔离运行对宿主机影响小删除也方便。第三已有 Docker 环境但缺少 Git 服务器的小团队。团队成员通过 FTP 把代码包传到固定目录由负责人或脚本统一触发构建避免多人直接在生产服务器上改源码。但同时也要看清边界。这套流程不适合需要频繁发布、多环境管理和细粒度权限控制的场景。FTP 上传是明文传输普通 FTP在公网环境下容易被嗅探建议使用 SFTP 或 FTPS。FTP 没有版本管理能力多个开发者同时上传容易互相覆盖也不适合多人协作的大项目。Docker 本身不解决代码正确性问题。镜像构建成功不代表业务正常容器启动后还要做健康检查、看日志、验证接口。另外不要为了省事把 Docker 守护进程的 TCP 端口直接暴露到公网这等于把一个无鉴权的管理接口交给互联网风险非常高。3. 环境准备与前置条件在开始部署前先确认本机和服务器的基础环境。3.1 服务器端要求服务器需要满足以下条件Linux 操作系统推荐 Ubuntu 20.04、Debian 12、CentOS 7/8 或兼容系统。已安装 Docker Engine可以通过docker --version查看。可用的 SSH 登录权限最好使用 root 或具有 sudo 权限的用户。对外开放 FTP/SFTP 所需的端口21 或 22以及应用对外服务的端口如 8080。磁盘空间至少 5G 以上用于存放源码、构建缓存和镜像。如果服务器还没装 Docker简单方式如下以 Ubuntu 为例实际版本以官方文档为准# 更新软件源 sudo apt update # 安装 Docker 依赖 sudo apt install -y ca-certificates curl # 导入 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 添加软件源 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装 Docker sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # 验证 sudo docker --versionCentOS/RHEL 系列通常使用 yum命令结构类似但软件源不同需要参考对应系统的 Docker 官方安装文档。如果你是 CentOS建议先确认内核版本和 SELinux 状态再安装 docker-ce。3.2 本地端要求本地不需要安装 Docker只要有 FTP 客户端和 SSH 终端即可。推荐的 FTP/SFTP 客户端FileZilla跨平台界面直观支持 FTP、SFTP、FTPS。WinSCPWindows 下常用支持批处理和目录同步。FinalShell集 SSH、FTP 于一体适合 Java/Python 后端调试。SSH 终端可以使用系统自带的终端也可以使用 Xshell、MobaXterm、Termius 等工具。Windows 用户直接使用 PowerShell 或 Windows Terminal 也可以。3.3 检查网络与端口部署前先确认服务器端口是否可达。例如要访问 8080 端口可以在本地执行telnet 服务器IP 8080如果端口不通检查云服务商的安全组规则和服务器防火墙# 查看防火墙状态 sudo ufw status # 或者 sudo firewall-cmd --list-allFTP 连接失败通常也是端口问题FTP 主动模式需要服务器开放 20/21 端口被动模式需要开放服务器的被动端口范围这往往是新手最容易踩的坑。4. FTP 上传本地代码到服务器代码从本地到服务器重点不是简单拖动文件而是知道传什么、传到哪里、怎么传。4.1 创建远程目录先通过 SSH 登录服务器创建项目目录# 登录服务器 ssh root服务器IP # 创建目录例如 /opt/app/my-web mkdir -p /opt/app/my-web把项目放在/opt/app下是常见做法权限清晰、不会和系统文件混在一起。如果使用非 root 用户需要确保该用户对目录有写权限sudo chown -R $USER:$USER /opt/app/my-web4.2 选择上传方式和连接打开 FileZilla 或 WinSCP连接参数如下参数值协议SFTP推荐或 FTP主机服务器公网 IP端口SFTP 默认 22FTP 默认 21用户名root 或其他有权限的用户密码对应登录密码或密钥使用 SFTP 的优势是数据走 SSH 加密通道即使密码被截获数据流也是密文。如果服务器只开放了 FTP 端口也可以使用 FTPSFTP over SSL。4.3 上传前排除无用文件直接把整个项目目录拖上去是最容易出问题的做法。node_modules、__pycache__、.git、*.log、dist等目录体积大且不是源码上传后既浪费时间也可能让 Docker 构建上下文变得巨大。上传前先在工作目录写一个.gitignore或使用客户端过滤规则。Windows 下可以用 WinSCP 的“跳过列表”FileZilla 可以在站点管理器中设置“过滤器”。正确的上传内容是源码文件、配置文件、依赖描述文件、Dockerfile、环境变量示例等。4.4 上传产物验证上传完成后在 SSH 中确认目录结构cd /opt/app/my-web ls -la # 查看文件数量 find . -type f | wc -l如果上传的是压缩包可以只传一个 tar.gz然后在服务器上解压# 本地打包 tar -czf my-web.tar.gz --excludenode_modules --exclude.git . # 服务器解压 tar -xzf my-web.tar.gz这种方式在文件数量很多时比逐个上传更快也是批量部署的常用手段。5. 编写 Dockerfile 并构建镜像代码到服务器后接下来要写 Dockerfile让 Docker 知道如何构建可运行的应用镜像。5.1 Python 项目示例假设是一个 Python FastAPI 或 Flask 项目Dockerfile 可以这样写# 基础镜像固定版本便于复现 FROM python:3.11-slim WORKDIR /app # 先复制依赖文件利用 Docker 层缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再复制业务代码 COPY . . EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]这个 Dockerfile 的顺序很有讲究requirements.txt单独复制是为了让依赖安装层保持缓存。如果只改了业务代码重新构建时不会重新跑pip install能显著缩短构建时间。5.2 Node.js 项目示例如果是前端或 Node 服务Dockerfile 类似FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . EXPOSE 3000 CMD [node, server.js]如果项目需要先构建前端产物可以拆成多阶段构建减小最终镜像体积。5.3 添加 .dockerignoreDocker 构建时会发送整个上下文因此一定要在项目根目录放.dockerignore避免把无关文件复制进镜像node_modules .git *.md *.log .DS_Store __pycache__ dist .env注意.dockerignore只作用于构建上下文不是服务器上的运行目录。如果应用启动时还需要.env文件建议通过--env-file或挂载方式注入而不是打进镜像。5.4 构建镜像在项目目录执行cd /opt/app/my-web # 构建镜像使用标签 app:v1 sudo docker build -t my-web:v1 .构建期间如果网络不好可以给 Docker 配置镜像加速。编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io ] }然后重启 Dockersudo systemctl restart docker构建完成后用docker images查看镜像sudo docker images如果看到my-web:v1说明构建成功。如果看不到需要倒回去看 Dockerfile 的每一步日志常见原因是依赖下载失败或复制路径不对。6. 运行 Docker 容器并暴露服务镜像构建成功只是第一步真正要让外部访问必须把容器端口映射到宿主机并保证业务进程前台运行。6.1 直接运行容器sudo docker run -d \ --name my-web-container \ -p 8080:8000 \ --restart unless-stopped \ my-web:v1参数说明-d后台运行。--name给容器命名便于后续操作。-p 8080:8000宿主机 8080 端口映射到容器内 8000 端口。--restart unless-stopped容器意外退出时自动重启适合服务常驻。如果你的 Dockerfile 里 EXPOSE 是 3000就把映射改成-p 8080:3000。具体端口由你的项目决定不要照抄。6.2 查看容器状态sudo docker ps看到Up状态说明容器在运行。再看日志sudo docker logs -f my-web-container日志是排查启动问题的最核心手段。如果容器启动后立刻退出先看日志sudo docker logs --tail 100 my-web-container常见的退出原因有应用监听端口写错、缺少环境变量、磁盘权限不足、启动命令找不到。6.3 访问验证本地浏览器访问http://服务器IP:8080或者在 SSH 上用 curl 验证curl -I http://127.0.0.1:8080如果返回200 OK说明 Docker 容器已经正常工作。如果超时先确认服务是否监听在0.0.0.0。很多框架默认监听127.0.0.1在容器内只接受本机回环外部访问不到需要把监听地址改成0.0.0.0。6.4 使用 Docker Compose 管理多服务如果项目包含 Nginx、Redis、MySQL 等多个服务建议写docker-compose.yml管理version: 3 services: web: build: . ports: - 8080:8000 environment: - MODEproduction restart: unless-stopped redis: image: redis:7-alpine restart: unless-stopped然后启动sudo docker compose up -d --buildDocker Compose 会让多个容器用同一个网络互相访问免去手动配置--link。更重要的是它可以处理“更新代码后重建并重启”的批量操作适合手动部署升级。7. 接口 API 与批量任务虽然 FTP 上传是手动操作但部署流程中的“接口”和“批量”仍然值得考虑。这里讲两个层面Docker 服务本身的 API以及 FTP/构建的批量处理思路。7.1 Docker 守护进程 APIDocker 守护进程默认只监听本地 Unix Socket不对外开放 HTTP API。如果确实需要远程管理可以在 Docker daemon 配置中增加 TCP 监听比如{ hosts: [unix:///var/run/docker.sock, tcp://0.0.0.0:2375] }但危险在于2375 端口无加密、无鉴权一旦暴露到公网别人就可以直接操作你的 Docker创建任意容器。生产环境千万不要这样暴露。如果只是本地测试可以临时打开用完立即关闭更稳妥的做法是通过 SSH 隧道访问远程 Docker APIssh -L 2375:127.0.0.1:2375 user服务器IP此时本地可以访问http://127.0.0.1:2375/versioncurl http://127.0.0.1:2375/version如果能看到 Docker 版本信息说明 API 通了。接下来可以调用 Docker API 列出容器curl http://127.0.0.1:2375/containers/json这只是演示 API 用法不会在你没有配置的情况下生效。实际项目中用 Docker SDK for Python、docker CLI 或 docker-compose 都比直接裸调用 API 更安全。7.2 FTP 批量上传FTP 工具本身就支持批量。FileZilla 可以把多个站点保存成一个队列WinSCP 支持命令行同步目录。脚本化场景可以使用lftp# 使用 lftp 同步本地目录到远程目录 lftp -u username,password -e mirror -R ./local_dir /remote_dir; quit sftp://服务器IP这个命令会将本地目录整个镜像到远程目录删除本地不存在的文件需要用--delete参数。用于定时发布时注意--delete可能误删远程文件生产环境建议先备份。7.3 批量构建与启动服务器上如果有多个应用目录可以写一个简单循环批量构建for dir in /opt/app/*/; do cd $dir || continue if [ -f docker-compose.yml ]; then sudo docker compose up -d --build fi done这种脚本适合统一管理多个独立项目但需要考虑失败重试和日志采集。一次循环里如果有某个服务构建失败应输出错误并记录而不是整个脚本直接崩掉。可以把执行结果重定向到日志文件for dir in /opt/app/*/; do cd $dir || continue echo Building $dir /var/log/deploy.log sudo docker compose up -d --build /var/log/deploy.log 21 done注意docker compose在不同系统上可能是docker-compose带连字符使用前先确认版本。8. 资源占用与性能观察部署完成后要观察服务器的资源使用情况判断是否有必要优化。8.1 查看容器资源占用使用docker stats可以动态查看各容器的 CPU、内存、网络和磁盘 I/Osudo docker stats --all这个命令类似 Linux 的 top但显示的是每个容器的资源配额。长时间运行后如果容器内存持续增长说明可能存在内存泄漏需要考虑重启策略或应用级优化。查看磁盘占用sudo df -h sudo du -sh /opt/app/*Docker 的构建缓存和悬空镜像也会占磁盘空间。定期清理无用的镜像和构建缓存# 查看悬空镜像 sudo docker images -f danglingtrue # 清理悬空镜像 sudo docker image prune # 清理所有未使用的镜像 sudo docker image prune -a如果频繁更新代码并重新构建镜像旧镜像会越来越多建议定期执行docker system prune但要注意这个命令会移除停止的容器和未使用的网络不要在生产高峰期随意执行。8.2 限制容器资源为了防止某个应用把服务器资源吃满可以在docker run时限制内存和 CPUsudo docker run -d \ --name my-web-container \ -p 8080:8000 \ --memory 512m \ --cpus 1.0 \ my-web:v1--memory 512m限制容器最大使用 512MB 内存--cpus 1.0限制最多使用 1 个 CPU 核心。对于负载敏感的服务可以设置 swap 限制--memory-swap 1g。但这些数值必须根据实际应用和服务器配置调整不能随意照搬。8.3 构建性能优化镜像构建速度会影响发布频率。几个实用技巧优先使用COPY requirements.txt而不是COPY . .让依赖层复用缓存。合并RUN apt-get update apt-get install -y ...减少镜像层数。使用--no-cache或无需要时不要强制重新拉取基础镜像。构建时指定--progressplain可以看到完整日志便于定位卡住的位置。sudo docker build --progressplain -t my-web:v2 .9. 常见问题与排查方法9.1 排错表格问题现象可能原因排查方式解决方案FTP 连接失败端口未开放、被动模式范围限制检查安全组和防火墙开放 20/21 端口或被动端口范围文件上传后中文乱码传输模式为 ASCII切换为二进制方式FileZilla 默认自动可手动切换二进制模式docker: Permission denied当前用户不在 docker 组sudo groupadd docker sudo usermod -aG docker $USER重新登录或重启会话端口冲突8080 已被占用sudo lsof -i:8080或sudo netstat -tunlp | grep 8080更换映射端口或停止占用进程容器启动后立即退出启动命令异常、端口监听错误sudo docker logs查看日志并修正启动命令外部无法访问服务防火墙/安全组未放行curl 127.0.0.1:8080测试开放对应端口、确认监听地址为 0.0.0.0镜像构建很慢网络不稳定、基础镜像过大ping 或 curl 测试外网配置镜像加速或使用 Alpine 基础镜像容器日志过多未设置日志轮转docker logs --tail查看大小配置loggingdriver 或定期清理9.2 Docker 日志轮转日志无限增长会撑满磁盘。可以在/etc/docker/daemon.json中配置全局日志限制{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }修改后重启 Dockersudo systemctl restart docker这个配置对新建容器生效已存在的容器需要重建才生效。9.3 FTP 权限问题上传到服务器后文件权限可能会影响 Docker 构建。如果在构建时出现permission denied可以检查源码目录权限sudo chown -R $USER:$USER /opt/app/my-web sudo chmod -R urwX /opt/app/my-web如果项目内有需要写入的目录比如logs、uploads要在 Dockerfile 中显式创建并授权RUN mkdir -p /app/logs chmod 777 /app/logs生产环境建议避免777只给运行用户最小权限。10. 最佳实践与使用建议FTP Docker 的整套流程并不复杂但稳定运行需要养成几个习惯。10.1 传输层用 SFTP / FTPS不要用明文 FTP 传输包含数据库配置、密钥、API Token 的文件。云平台一般都开通了 SSH 端口直接用 SFTP 或 FTPS 替代普通 FTP成本低、安全收益高。10.2 上传前先构建再跑生产在上传到服务器之前先在本地用 Docker 构建一次能规避大量“服务器上编不过”的问题。本地验证通过后再传代码。如果项目依赖太大的可以只传源码和 Dockerfile服务端构建时再拉取依赖。10.3 用版本标签管理镜像每次构建不要只覆盖latest而是用语义化版本或时间戳sudo docker build -t my-web:v1.0.0 . sudo docker run -d -p 8080:8000 my-web:v1.0.0这样出了问题可以快速切回上一个版本。10.4 健康检查要加在 Dockerfile 中声明健康检查HEALTHCHECK --interval30s --timeout3s \ CMD curl -f http://127.0.0.1:8000/health || exit 1如果基础镜像中没有 curl需要先安装。健康检查能帮助你在容器状态异常时快速发现配合--restart unless-stopped可以自动重启不稳定进程。10.5 环境变量与密钥分离不要把数据库密码、Token 写进镜像。通过-e或--env-file注入sudo docker run -d \ --env-file .env.prod \ -p 8080:8000 \ my-web:v1.0.0同时保证.env文件不要通过 FTP 上传到公开目录更不要提交进镜像。10.6 发布前备份发布前可以先备份当前运行镜像sudo docker commit my-web-container my-web-backup:$(date %Y%m%d%H%M)也可以直接docker save导出镜像文件。10.7 遵守授权与合规要求代码可能涉及第三方开源组件、商业软件或客户数据。使用 FTP 上传时要注意目标服务器是否具备合法部署授权如果项目涉及用户隐私数据需要在服务器上做好访问控制和日志审计。Docker 镜像如果发布到公开仓库要留意基础镜像许可证和业务代码保密性不要直接 push 包含密钥的镜像。11. 总结FTP 上传 Docker 部署这个组合最实用的价值是把“手动发布”这件事变得可预期代码文件固定传到某个目录Dockerfile 描述运行环境一条命令启动服务。相比直接在裸机上安装环境Docker 让服务器更干净也让应用之间的依赖冲突更少。如果你想马上开始建议先做三步第一用 FTP 客户端连接服务器并确认能上传文件第二在服务器上写一个最小 Dockerfile构建并运行任意一个测试服务第三用docker logs和docker ps验证容器状态。核心链路跑通后再把自己的真实业务镜像化补充环境变量、端口映射和数据持久化。最容易踩的坑是端口监听地址和防火墙设置。Docker 容器内应用必须监听0.0.0.0宿主机端口和安全组必须放行三者缺一不可。把这条规则记清楚FTP Docker 部署就不会有大问题。后续可以把流程继续升级换成 Git 仓库 Webhook 自动构建或者接入 Gitea、Jenkins但底层仍然是一样的 Docker 镜像和容器管理。先跑通手动发布再逐步自动化这样每一步的风险都可控。