尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

FTP上传代码+Docker部署:从本地到服务器的完整实践指南

FTP上传代码+Docker部署:从本地到服务器的完整实践指南 很多开发者在本地把项目跑起来非常轻松但一到云服务器部署就容易卡壳系统环境不同、依赖冲突、进程管理混乱、端口被占用……这些问题的根源往往是“本地开发环境”和“服务器运行环境”不一致。本文整理了一套适合个人项目和中小团队的部署闭环本地开发完成后通过 FTP/SFTP 把代码上传到服务器再借助 Docker 构建镜像并运行容器最终通过浏览器访问服务。文章会先讲清楚 FTP 与 Docker 的基本概念再给出完整的目录结构、Dockerfile、docker-compose 配置和命令示例最后整理高频报错的排查思路。新手可以照着一步步操作有经验的开发者也能把它当作部署速查手册。1. 背景为什么需要 FTP 加 Docker 这套部署方式实际开发中我们通常先在本地编写代码、调试功能等基本功能稳定后再部署到服务器。这里的核心问题有两个一是“代码怎么从本地上传到服务器”二是“服务在服务器上如何稳定运行”。很多同学会直接用git pull在服务器上拉取代码但这要求代码仓库已经存在而且服务器上还要提前安装好所有运行时环境。对于个人项目、内网项目或临时交付场景FTP/SFTP 仍然是操作成本最低的上传方式。把代码上传到服务器后如果直接裸跑进程会遇到版本不一致、进程退出、无法自启动等问题而把相同的代码交给 Docker 处理它会把应用和依赖打包成镜像用同一套镜像在任意 Linux 服务器上运行环境差异就被抹平了。所以常见的部署链路是本地编写代码并测试通过。使用 FTP/SFTP 把项目文件上传到服务器指定目录。SSH 登录服务器进入项目目录。使用 Dockerfile 构建项目镜像。使用docker run或docker compose up -d启动容器。验证端口和日志确认服务正常对外提供服务。需要提前说明的是公网环境下更推荐使用 SFTP基于 SSH 的文件传输协议而不是传统 FTP。传统 FTP 使用明文传输账号和文件内容在公网传输存在较大安全隐患SFTP 走 22 端口数据加密操作方式与 FTP 几乎一致。本文后面给出的命令示例优先使用 SFTP/SCP如果服务器确实需要暴露 FTP 服务也应限制防火墙并设置强密码。2. 环境准备与版本说明在开始部署之前先梳理需要准备的环境。本文示例以常见部署场景为例具体版本请根据服务器实际情况调整。2.1 本地开发环境本地机器可以是 Windows、macOS 或 Linux需要安装SSH 客户端。Windows 用户可以使用 PowerShell 自带的 SSH也可以使用 Xshell、MobaXtermmacOS 和 Linux 用户直接使用终端。SFTP/FTP 客户端。这里推荐 FileZilla 或 WinSCP它们支持图形化拖拽上传也支持编辑远端文件。命令行爱好者可以直接使用sftp或scp。Docker Desktop。如果本地需要模拟服务器环境可以在本地装 Docker如果只在服务器上使用 Docker本地不装也行。2.2 服务器环境服务器建议选择 Linux 系统本文以 Ubuntu 22.04 和 Debian 系命令为例。需要确认以下条件服务器可以正常通过 SSH 登录。服务器有公网 IP 或内网可达 IP。已经安装 Docker Engine 和 Docker Compose 插件。没有安装的话可以执行下面的命令快速安装# 适用于 Ubuntu / Debian 的 Docker 安装方式 curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # 启动 Docker 服务 sudo systemctl start docker sudo systemctl enable docker # 查看 Docker 版本 docker --version docker compose version安装完成后可以使用docker info检查 Docker 是否正常运行。2.3 部署目录约定为了方便管理和备份建议在服务器上统一使用/opt/projects/或/home/用户目录/apps/作为部署目录。示例中我们统一使用/opt/projects/my-app。版本相关的推荐如下Ubuntu 22.04 LTS Docker Engine 24 Docker Compose v2 Node.js 项目示例Node 18不是说不能用其他版本而是本文中的命令和配置以这个环境为基础。你的项目如果使用 Python、Java、Go只需要把 Dockerfile 换成对应运行时的写法整体部署流程完全一致。3. 核心概念拆解部署链路中会经常遇到几个术语FTP、SFTP、镜像、容器、Dockerfile、docker-compose。下面把容易混淆的概念拆开说明。3.1 FTP 与 SFTP 的区别FTP 是传统的文件传输协议默认端口 21支持主动模式和被动模式。缺点是控制通道和传输通道都不加密账号密码和文件内容可能被网络中的第三方截获。SFTP 虽然不是 FTP 的加密版但它的工作方式完全不同SFTP 基于 SSH 协议默认端口 22使用加密通道传输文件支持断点续传、远程文件管理等操作安全性远高于 FTP。在实际服务器部署中除非服务器上已经部署了 vsftpd 等 FTP 服务否则更简单的方式是直接使用sftp命令# 使用 SFTP 登录服务器 sftp root192.168.1.100登录成功后命令提示符会变成sftp可以使用lpwd查看本地目录、pwd查看远端目录、put上传文件、get下载文件、put -r上传目录。3.2 Docker 镜像与容器Docker 可以把应用及其依赖打包成一个独立单元。镜像Image是只读模板容器Container是镜像运行后的实例。你可以从 Docker Hub 拉取官方镜像也可以使用 Dockerfile 定制自己的镜像。从这个流程中可以看出Docker 解决的核心问题是“可复制性”和“隔离性”。同样的镜像在开发机器、测试机器、生产机器上运行结果是一致的不会出现“在我电脑上是好的到你服务器就报错”的情况。3.3 Dockerfile 的作用Dockerfile 是一个文本文件包含一条条构建指令。常用的指令有FROM指定基础镜像。WORKDIR设置容器内的工作目录。COPY把本地文件复制到镜像内。RUN构建镜像时执行的命令比如安装依赖。EXPOSE声明容器对外暴露的端口。CMD或ENTRYPOINT容器启动时执行的命令。例如FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [npm, start]这个 Dockerfile 的流程是先基于 Node 18 的精简镜像设置工作目录/app复制依赖清单并安装依赖再把整个项目代码复制进去最后启动 npm start。写成镜像后服务器上只需要有 Docker不需要再额外安装 Node.js 和 npm。3.4 从本地代码到容器运行的整体流程为了让思路更清晰可以把这个流程拆成 7 个阶段在本地完成代码编写跑一遍npm install和本地测试。检查项目目录确认是否包含 Dockerfile 和.dockerignore。使用 FTP/SFTP 把代码上传到服务器/opt/projects/my-app。SSH 登录服务器进入该目录。执行docker build -t my-app:1.0 .构建镜像。执行docker run或docker compose up -d启动容器。访问http://服务器IP:映射端口验证服务。这套流程是手工部署的基础。后续如果引入 CI/CD可以把 2 到 6 步自动化但底层思路不变。4. 完整实战FTP 上传本地代码并用 Docker 部署接下来用一个 Node.js Express 项目作为示例完整演示从本地开发、FTP 上传到 Docker 部署的全过程。这个项目结构非常简单但它能覆盖整个部署链路中的所有关键技术点。4.1 准备一个可部署的示例项目在本地新建一个目录my-app目录结构如下my-app/ ├── package.json ├── app.js ├── public/ │ └── index.html ├── Dockerfile └── .dockerignore先来看package.json{ name: my-app, version: 1.0.0, description: A simple Node.js app for Docker deployment, main: app.js, scripts: { start: node app.js }, dependencies: { express: ^4.19.2 } }再写一个最简单的app.js创建 HTTP 服务并监听 3000 端口// 文件路径my-app/app.js const express require(express); const app express(); const PORT process.env.PORT || 3000; // 静态资源目录 app.use(express.static(public)); // 健康检查接口 app.get(/health, (req, res) { res.json({ status: ok, time: new Date().toLocaleString() }); }); app.listen(PORT, () { console.log(App is running at http://localhost:${PORT}); });在public/index.html中放一个简单的页面!-- 文件路径my-app/public/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 titleDocker Deploy Demo/title /head body h1Hello Docker/h1 p如果你能看到这个页面说明 Docker 部署已经成功。/p /body /html4.2 编写 Dockerfile 与 .dockerignore在项目根目录创建Dockerfile# 文件路径my-app/Dockerfile # 使用 Node 18 的精简镜像作为基础镜像 FROM node:18-alpine # 设置容器内工作目录 WORKDIR /app # 先复制 package.json 和 package-lock.json充分利用 Docker 层缓存 COPY package*.json ./ # 安装生产依赖 RUN npm install --omitdev # 复制整个项目文件到容器内 COPY . . # 声明容器运行时的端口 EXPOSE 3000 # 启动命令 CMD [npm, start]这里有个容易忽略的细节先复制依赖清单文件并安装依赖再复制项目代码。这样做的原因是 Docker 构建时每一层都有缓存如果项目代码经常修改而依赖不经常修改修改代码后重新构建镜像时RUN npm install这一层会命中缓存构建速度会快很多。同时创建.dockerignore避免把不需要的文件打包进镜像# 文件路径my-app/.dockerignore node_modules npm-debug.log .git .gitignore .env *.md.dockerignore的作用和 Git 的.gitignore类似但它针对的是 Docker 构建上下文。加上它以后COPY . .不会把node_modules和本地日志等无关文件复制进镜像既减少了镜像体积也避免因为本地环境依赖和容器不一致导致的奇怪问题。4.3 本地验证项目在本地进入项目根目录安装依赖并启动项目cd my-app npm install npm start浏览器访问http://localhost:3000如果能看到页面再访问http://localhost:3000/health会看到返回 JSON{ status: ok, time: 2025/1/1 10:00:00 }本地验证这一步非常关键。如果项目在本地都跑不起来直接上传到服务器问题排查会更困难。先保证本地通过再进入部署环节。4.4 使用 FTP/SFTP 将代码上传到服务器接下来把代码上传到服务器。这里提供两种操作方式图形化方式和命令行方式。如果你是 Windows 用户可以使用 FileZilla 或 WinSCP。以 FileZilla 为例打开 FileZilla在“主机”输入服务器 IP在“用户名”和“密码”输入 SSH 登录信息端口填 22。连接成功后左侧是本地文件右侧是服务器文件。在右侧定位到/opt/projects目录如果目录不存在先在服务器上创建。把本地my-app目录拖拽到右侧等待上传完成。如果你是命令行用户可以使用scp或sftp上传目录# 在服务器上创建部署目录 ssh root192.168.1.100 mkdir -p /opt/projects/my-app # 使用 scp 上传整个目录 scp -r ./my-app/* root192.168.1.100:/opt/projects/my-app/也可以使用sftp交互式上传sftp root192.168.1.100 sftp cd /opt/projects/my-app sftp put -r public sftp put package.json sftp put app.js sftp put Dockerfile sftp put .dockerignore sftp exit无论使用哪种方式上传完成后建议在服务器上检查一下文件是否完整ssh root192.168.1.100 cd /opt/projects/my-app ls -la正常的输出应该能看到package.json、app.js、Dockerfile、public等文件和目录。注意不要手动上传node_modules目录这个目录应该在构建镜像时由npm install生成手动上传不仅体积大还容易出现平台相关的问题。4.5 SSH 登录服务器并构建 Docker 镜像上传代码到服务器后通过 SSH 登录服务器并进入项目目录ssh root192.168.1.100 cd /opt/projects/my-app然后执行 Docker 构建命令docker build -t my-app:1.0 .这里的-t参数用来给镜像命名打标签格式是镜像名:版本号。命令最后的点号表示使用当前目录作为构建上下文。构建过程中Docker 会按照 Dockerfile 的步骤逐层执行你会在终端看到下载基础镜像、复制文件、安装依赖等日志。构建完成后可以使用docker images查看镜像docker images输出中应该能看到my-app镜像大小约在 180MB 左右。这个体积是 Node 运行时的基础镜像和 Express 依赖共同构成的如果使用精简的工具链可以进一步压缩但作为示例项目这个体积已经可以接受。4.6 运行 Docker 容器镜像构建成功后使用docker run启动容器docker run -d \ --name my-app \ -p 8080:3000 \ --restartalways \ my-app:1.0拆开解释一下这条命令-d以守护进程方式在后台运行容器。--name my-app给容器命名为 my-app。-p 8080:3000把服务器上的 8080 端口映射到容器内的 3000 端口。--restartalways容器退出后自动重启适合生产环境长期运行。my-app:1.0使用刚才构建的镜像。执行后通过docker ps查看容器状态docker ps如果看到STATUS为Up说明容器正在运行。然后通过docker logs查看实时日志docker logs my-app正常输出应该包含App is running at http://localhost:3000最后在浏览器中访问http://服务器IP:8080如果能看到 “Hello Docker” 的页面说明整个 FTP 上传和 Docker 部署流程已经成功走通。4.7 使用 docker-compose 简化启动流程如果项目后续会加入数据库、Nginx、Redis 等更多服务建议使用 docker-compose 统一管理。在项目根目录创建docker-compose.yml# 文件路径my-app/docker-compose.yml services: app: build: . image: my-app:1.0 ports: - 8080:3000 environment: - NODE_ENVproduction restart: always然后使用以下命令启动docker compose up -d docker compose ps docker compose logs -f使用 docker-compose 的好处是所有容器定义都写在文件里团队成员拉取项目代码后在服务器上执行docker compose up -d就能启动整套服务不需要手动敲一长串docker run命令。4.8 代码更新后的重新部署部署并没有在第一次启动成功后结束。开发过程中你可能会修改代码修改后重新部署的流程如下本地修改代码测试通过。使用 SFTP 重新上传修改后的文件。SSH 登录服务器进入项目目录。重新构建镜像。停止旧容器启动新容器。命令如下cd /opt/projects/my-app # 重新构建镜像 docker build -t my-app:1.1 . # 停止并删除旧容器 docker stop my-app docker rm my-app # 启动新容器 docker run -d \ --name my-app \ -p 8080:3000 \ --restartalways \ my-app:1.1如果使用 docker-compose则更简单docker compose up -d --build--build参数会先根据镜像构建配置重新构建镜像再启动新容器。如果镜像配置没有变化也可以只使用docker compose restart但这不会重新拉取代码所以实际更新代码时通常需要先重新上传代码再构建。5. 常见问题与排查思路即使流程完整实际操作中仍然会遇到不少问题。下面是几个高频场景和排查思路。问题现象常见原因解决思路FTP/SFTP 连接服务器超时服务器防火墙未放行端口或云安全组未放行 22 端口检查云安全组规则确认 22 端口对外开放如果使用 FTP 21 端口还要检查 vsftpd 配置和防火墙上传文件后目录为空FTP 上传到错误目录或当前用户无写入权限使用pwd确认远端目录使用mkdir -p创建目录检查/opt/projects目录权限docker build 找不到 package.json构建上下文没选对或在 Dockerfile 中 COPY 路径错了确认在项目根目录执行docker build检查 Dockerfile 中COPY package*.json ./的路径容器启动后立即退出代码报错、端口被占用、依赖未安装成功使用docker logs my-app查看日志使用docker ps -a查看退出状态浏览器访问服务器 IP 打不开页面防火墙未放行映射端口或容器内服务监听地址不是 0.0.0.0检查云服务器的安全组是否放行 8080 端口检查代码是否监听0.0.0.0而不是127.0.0.1镜像体积过大没有编写 .dockerignore或安装了开发依赖优化 .dockerignore使用npm install --omitdev尽量选择体积小的基础镜像npm install 构建很慢服务器访问 npm 源不稳定在 Dockerfile 中配置 npm 镜像RUN npm config set registry https://registry.npmmirror.com其中“容器启动后立即退出”是最常见的问题。排查顺序建议是使用docker ps -a查看容器是否仍然存在退出状态码是多少。使用docker logs --tail 200 my-app查看最近 200 行日志。确认日志中是否有语法错误、依赖缺失、端口被占用等关键信息。如果日志不明确可以手动执行docker run --rm -it my-app:1.0 sh进入容器内部排查环境。这里需要特别提醒云服务器的安全组是很多新手最容易忽略的环节。即使 Docker 容器已经监听 8080 端口如果云控制台的安全组没有放行 8080 端口外网依然无法访问。安全组的放行和服务器内置防火墙如 iptables/firewalld是两套体系需要同时确认。6. 最佳实践与工程建议部署一个可用的服务不难难的是长期稳定运行。这里根据实际工程经验整理几条建议帮助你把部署流程做得更规范。6.1 文件传输安全公网环境下优先使用 SFTP 或 SCP尽量避免使用传统的明文 FTP。如果业务确实需要 FTP 服务建议使用 vsftpd并开启ssl_enableYES。限制用户可以访问的目录范围不让 FTP 账号直接操作系统根目录。为 FTP 账号单独创建一个用户并使用独立强密码。只在防火墙层级放行必要的端口避免大范围暴露。服务器的 SSH 登录也建议改为密钥认证并停用密码登录。密钥认证比密码更安全也方便脚本自动化操作。6.2 生产环境变更前先备份涉及生产环境操作时建议遵循以下原则修改服务器配置前备份原文件。更新 Docker 镜像前保留旧镜像的 tag方便快速回滚。如果存在数据库在发布前做一次数据库备份。先在小流量或测试环境验证再切到正式环境。备份可以使用最简单的命令例如docker save my-app:1.0 -o my-app-1.0.tar这个命令把镜像导出为 tar 文件如果新版本出问题可以通过docker load快速恢复旧版本。6.3 容器数据与配置分离容器本身是无状态的删除容器后数据会丢失。如果你的应用需要保存文件、上传图片或数据库数据建议使用 Docker volume 或挂载宿主机的目录。使用docker run时通过-v参数挂载宿主机目录docker run -d \ --name my-app \ -p 8080:3000 \ -v /opt/projects/my-app/data:/app/data \ --restartalways \ my-app:1.0这样容器内/app/data目录对应的数据会持久化在宿主机/opt/projects/my-app/data中即使删除容器数据也不会丢失。6.4 谨慎使用 .env 文件很多项目会把数据库密码、API Key、密钥等敏感配置放在.env文件中。如果上传时不小心把.env传到了服务器甚至被打包进镜像会带来严重的安全风险。建议的做法是在.dockerignore和.gitignore中排除.env。生产环境的敏感配置不要写在代码仓库里而是通过docker run -e参数或 docker-compose 的environment字段注入。如果配置较多可以单独维护一个服务器本地的.env文件但需要严格限制文件权限并注意不要把它包含进镜像。举例来说docker-compose 中可以通过env_file引入外部配置但.dockerignore中要排除这个文件services: app: build: . env_file: - .env restart: always这样.env在宿主机中存在即可不会被打包进镜像也不会泄露到项目仓库中。6.5 监控与日志容器跑起来之后不能只看一次docker ps就认为万事大吉。生产环境中资源耗尽、容器频繁重启、日志增长过快都可能导致服务异常。基础监控可以从这几条命令开始# 查看容器资源占用 docker stats # 查看容器启动以来的历史信息 docker inspect my-app # 查看最近日志 docker logs --tail 200 my-app更完整的监控方案可以把容器日志收集到统一的日志平台但这属于进阶话题。在一个小型项目中至少要养成“每次发布后查看日志、定期查看资源占用”的习惯。6.6 服务器目录规划不要把项目随意放在/tmp、/root或家目录的任意位置。建议建立统一的目录约定/opt/projects/ ├── my-app/ # 应用代码 ├── my-app-backup/ # 数据备份 └── my-app-logs/ # 日志文件目录规划的意义在于当服务器出现磁盘空间不足或需要迁移时你能够快速判断哪些目录是业务数据、哪些目录是临时文件。6.7 基于 CI/CD 的自动化方向本文介绍的手工部署流程适合初期项目和临时环境使用。当项目规模变大后手工操作会暴露出效率低、容易遗漏的问题。下一步建议接触 CI/CD 工具把“上传代码 → 构建镜像 → 运行容器”的过程自动化代码推送到 Git 仓库后由 CI 自动执行测试和构建。构建完成后自动把镜像推送到镜像仓库。服务器监听新镜像自动拉取并发布。常见的工具组合包括 GitLab CI、Jenkins、GitHub Actions、阿里云流水线等。理解了手工部署的每一步之后再学习 CI/CD 会顺利很多。7. 总结与建议到这里一套完整的“FTP 上传代码 Docker 部署”流程就梳理完了。通过本文的示例你已经掌握了从本地项目编写、SFTP 上传代码、编写 Dockerfile、构建镜像、运行容器到更新部署的完整链路也了解了端口映射、日志查看、安全配置等关键概念。如果你第一次成功把页面跑在服务器上可以尝试做几个小练习来巩固把 Dockerfile 换成 Python Flask 或 Java Spring Boot 版本。在 docker-compose 中加入 Nginx 反向代理。给项目添加一个 MySQL 或 Redis 容器梳理多容器之间的网络通信。把所有手工部署命令整理成 shell 脚本减少重复操作。在真实项目中优先关注安全风险和数据持久化。部署前检查安全组端口、使用 SFTP 替代明文 FTP、避免把敏感配置写入镜像都能大幅降低出问题的概率。如果本文对你有帮助可以收藏备用。实践过程中遇到别的报错欢迎在评论区复现你的命令和日志一起把坑踩平。
返回列表