从零到一:基于Docker的服务器部署实战指南

发布时间:2026/8/5 3:49:10
从零到一:基于Docker的服务器部署实战指南 1. 项目概述从零到一一个开发者的服务器部署实战如果你是一名开发者无论是刚完成一个个人项目还是准备将团队的第一个产品推向线上从本地代码到公网可访问的服务中间那道看似简单的“部署”鸿沟常常是新手遇到的第一道坎。服务器怎么选系统怎么装环境怎么配代码怎么跑起来这些问题往往比写代码本身更让人头疼。今天我就以一个过来人的身份和你完整走一遍这个流程从一台裸机服务器开始到最终用 Docker 将你的项目稳稳地跑在线上。这个过程我们称之为“从零到一”。这不仅仅是把代码扔到服务器上那么简单。它关乎如何选择一个稳定、成本可控的服务器如何安全地配置基础环境以及如何利用 Docker 这种现代技术将你的应用及其所有依赖打包成一个标准、可移植的“集装箱”实现一次构建、处处运行。无论你的项目是 Python Flask、Node.js、Java Spring Boot 还是 Go这套思路都是相通的。我会把每一步的“为什么”和“怎么做”都讲清楚并附上我踩过的坑和总结的技巧目标是让你看完就能动手亲手把自己的项目送上云端。2. 核心思路与工具选型为什么是 Docker在动手之前我们先理清思路。传统的部署方式我们称之为“宠物模式”你需要小心翼翼地登录服务器像照顾宠物一样手动安装 Python/Node.js 运行环境、数据库、缓存修改各种配置文件一个依赖版本不对就可能让整个服务瘫痪。更可怕的是这台服务器的状态是独一无二的迁移或复制极其困难。而现代部署我们追求的是“牲畜模式”服务器实例是可以随时创建、销毁和替换的应用本身才是核心。Docker 正是实现这一理念的利器。它通过容器化技术将应用代码、运行环境、系统工具、系统库等全部打包成一个镜像。这个镜像在任何安装了 Docker 的机器上都能以完全一致的方式运行起来。为什么选择 Docker 作为本次部署的核心环境一致性杜绝了“在我机器上是好的”这类问题。开发、测试、生产环境使用同一个镜像保证了绝对一致。快速部署与回滚启动一个容器只需秒级。如果新版本有问题立即停止容器运行旧版本镜像即可完成回滚整个过程干净利落。资源高效与传统虚拟机相比容器共享主机操作系统内核无需为每个应用加载完整的操作系统因此更轻量、启动更快、资源占用更少。简化配置所有环境变量、配置文件都可以固化在镜像或通过启动命令注入管理起来比散落在服务器各处的配置文件要清晰得多。基于这个思路我们的部署路径就非常清晰了准备服务器 - 在服务器上安装 Docker - 将我们的项目制作成 Docker 镜像 - 运行容器并暴露服务。接下来我们一步步拆解。3. 第一步服务器准备与基础环境配置3.1 服务器选购与系统初始化服务器的选择丰俭由人。对于个人项目或初创公司早期云服务器是首选免去了自建机房的麻烦。国内常见的如阿里云、腾讯云国外有 DigitalOcean、Vultr 等。选择时主要看几点地理位置影响访问延迟、CPU 内存配置、带宽、以及最重要的——价格。注意对于纯粹的学习和测试你完全可以在自己的电脑上通过虚拟机如 VirtualBox来模拟服务器环境所有操作逻辑完全相同。这里我以购买一台全新的 CentOS 7 或 Ubuntu 20.04 LTS 云服务器为例。为什么推荐这两个CentOS 以其稳定著称虽然 CentOS 8 之后策略有变但 7 仍在广泛使用Ubuntu 则拥有更活跃的社区和更新的软件包。Ubuntu 对新手更友好一些。购买后你会获得一个公网 IP 地址、一个 root 用户密码或 SSH 密钥。第一件事不是立马部署项目而是做好安全加固。安全加固实操步骤使用 SSH 密钥登录禁用密码登录。这是防止暴力破解的第一道防线。# 本地生成密钥对如果还没有 # ssh-keygen -t rsa -b 4096 -C your_emailexample.com # 将公钥上传到服务器 ssh-copy-id root你的服务器IP # 测试密钥登录成功 ssh root你的服务器IP登录成功后编辑 SSH 配置文件vim /etc/ssh/sshd_config找到并修改以下参数PermitRootLogin yes # 可以改为 no完全禁止root远程登录更安全但需要先创建普通用户并赋予sudo权限 PasswordAuthentication no # 改为 no禁用密码认证 PubkeyAuthentication yes # 确保是 yes保存后重启 SSH 服务systemctl restart sshd。务必在重启前开一个新的终端窗口测试密钥登录是否依然有效否则可能把自己关在门外配置防火墙。只开放必要的端口通常是 SSH(22) HTTP(80) HTTPS(443)以及你的应用端口如 3000, 8080 等。# 对于 CentOS 7 (使用firewalld) systemctl start firewalld systemctl enable firewalld firewall-cmd --permanent --add-servicessh firewall-cmd --permanent --add-servicehttp firewall-cmd --permanent --add-servicehttps firewall-cmd --permanent --add-port你的应用端口/tcp firewall-cmd --reload firewall-cmd --list-all # 查看规则 # 对于 Ubuntu (使用ufw) ufw allow ssh ufw allow http ufw allow https ufw allow 你的应用端口/tcp ufw enable # 启用防火墙 ufw status verbose # 查看状态更新系统。这是良好习惯确保系统软件包是最新的安全版本。# CentOS yum update -y # Ubuntu apt update apt upgrade -y做完这些你的服务器就有了一个相对安全、干净的基础环境。接下来我们安装本次的主角Docker。3.2 Docker 引擎的安装与配置Docker 的安装其实非常简单官方提供了非常便捷的脚本。但我不推荐直接使用curl | sh这种管道安装方式因为不够透明。我们使用包管理器来安装更可控。以 Ubuntu 20.04 为例安装 Docker CE社区版卸载旧版本如果是全新系统可跳过sudo apt-get remove docker docker-engine docker.io containerd runc安装必要的工具让 apt 可以通过 HTTPS 使用仓库sudo apt-get update sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common添加 Docker 的官方 GPG 密钥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安装 Docker Enginesudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io验证安装是否成功sudo docker run hello-world如果看到 “Hello from Docker!” 的输出说明安装成功。一个关键的后续配置将当前用户加入 docker 组。默认情况下运行docker命令需要sudo权限。为了避免每次输入sudo可以将你的用户加入docker组。sudo usermod -aG docker $USER执行此命令后你需要完全退出当前 SSH 会话然后重新登录这个组权限变更才会生效。实操心得这里有个小坑。有些教程会建议修改 Docker 的守护进程配置比如更换镜像加速器。我建议在/etc/docker/daemon.json文件中配置如果文件不存在就创建。例如使用阿里云镜像加速{ registry-mirrors: [https://你的专属加速器地址.mirror.aliyuncs.com] }修改后需要重启 Dockersudo systemctl restart docker。这能极大提升拉取镜像的速度特别是在国内网络环境下。4. 第二步将你的项目 Docker 化这是核心环节。你需要为你的项目创建一个Dockerfile。这个文件是一个文本文件包含了一系列指令告诉 Docker 如何构建你的应用镜像。4.1 编写 Dockerfile一个 Node.js 项目的例子假设我们有一个简单的 Node.js Express 应用。项目结构如下my-app/ ├── app.js ├── package.json └── DockerfileDockerfile内容详解# 第一阶段构建阶段 # 使用官方 Node.js 运行时作为父镜像并指定一个具体的、较小的版本如 alpine 版本避免使用 latest 标签。 FROM node:16-alpine AS builder # 设置工作目录后续命令都会在这个目录下执行 WORKDIR /app # 将 package.json 和 package-lock.json 复制到工作目录 # 先复制依赖定义文件利用 Docker 的缓存层如果依赖没变则跳过 npm install加速构建 COPY package*.json ./ # 安装项目依赖 # 使用 npm ci 而不是 npm install因为它严格根据 lock 文件安装能确保环境一致性 RUN npm ci --onlyproduction # 第二阶段运行阶段 # 再次使用一个干净的、更小的基础镜像只包含运行所需减小最终镜像体积 FROM node:16-alpine WORKDIR /app # 从构建阶段仅复制生产所需的 node_modules 和源码 COPY --frombuilder /app/node_modules ./node_modules COPY . . # 声明容器运行时对外暴露的端口需要与你的应用监听端口一致 EXPOSE 3000 # 定义容器启动时执行的命令 CMD [node, app.js]逐行解析与注意事项FROM ... AS builder这是多阶段构建。第一阶段builder负责安装依赖、编译代码等“构建”工作。第二阶段从一个干净的基础镜像开始只从第一阶段拷贝构建好的产物如node_modules。这能显著减少最终镜像的体积因为最终镜像不包含构建工具如 gcc, python 等。WORKDIR相当于在容器内cd到这个目录。后续的COPY、RUN、CMD等命令都在此目录下执行。COPY package*.json ./先于COPY . .这是利用 Docker 层缓存的关键技巧。package.json的变化频率远低于源代码。这样当源代码改变但依赖未变时Docker 可以直接复用之前npm install创建的缓存层极大加快构建速度。RUN npm ci --onlyproductionnpm ci速度更快、更严格。--onlyproduction只安装dependencies不安装devDependencies进一步精简。EXPOSE 3000这只是一个文档说明告诉使用者这个容器打算使用 3000 端口。它并不会自动将端口映射到宿主机真正的端口映射在运行容器时通过-p参数指定。CMD容器启动时执行的命令。一个 Dockerfile 中只能有一个CMD指令。推荐使用exec 格式CMD [“executable”, “param1”, “param2”]而不是 shell 格式CMD node app.js这能让你的应用正确接收 Unix 信号如 SIGTERM实现优雅关闭。4.2 构建与测试镜像在项目根目录Dockerfile所在目录执行构建命令docker build -t my-app:latest .-t给镜像打标签格式为name:tag。latest是默认标签但在生产环境中建议使用有意义的标签如版本号或 Git 提交哈希。.构建上下文路径指当前目录。Docker 客户端会将这个目录下的所有文件打包发送给 Docker 守护进程因此要避免包含不必要的文件如node_modules,.git, 日志文件等可以通过.dockerignore文件来忽略类似.gitignore。构建成功后可以在本地运行测试# 将容器的 3000 端口映射到宿主机的 3000 端口 docker run -p 3000:3000 -d my-app:latest # -d 代表后台运行detached mode用curl localhost:3000或浏览器访问http://你的服务器IP:3000测试应用是否正常响应。5. 第三步在服务器上部署与运行现在我们有了一个可以运行的 Docker 镜像。如何把它放到服务器上并跑起来呢有多种方式5.1 简单部署直接运行容器对于最简单的场景你可以在服务器上直接运行容器。首先需要将镜像传到服务器。有两种方法使用 Docker Registry推荐将镜像推送到 Docker Hub、阿里云容器镜像服务等公共或私有仓库然后在服务器上拉取。# 本地打标签关联到你的仓库 docker tag my-app:latest yourusername/my-app:latest # 登录并推送 docker login docker push yourusername/my-app:latest # 在服务器上拉取并运行 ssh root服务器IP docker pull yourusername/my-app:latest docker run -d -p 80:3000 --name my-running-app yourusername/my-app:latest这里-p 80:3000将容器内的 3000 端口映射到了宿主机的 80 端口HTTP默认端口这样用户就可以直接通过 IP 访问了。直接传输镜像文件使用docker save和docker load适合内网或镜像不大的情况。# 本地保存镜像为 tar 文件 docker save -o my-app.tar my-app:latest # 通过 scp 传到服务器 scp my-app.tar root服务器IP:/root/ # 在服务器上加载镜像 ssh root服务器IP docker load -i /root/my-app.tar docker run -d -p 80:3000 --name my-app my-app:latest5.2 进阶管理使用 Docker Compose当你的应用不止一个容器时比如应用 数据库 缓存手动管理每个容器的启动参数就变得繁琐。Docker Compose 通过一个docker-compose.yml文件来定义和运行多容器应用。假设你的应用需要连接一个 PostgreSQL 数据库docker-compose.yml可能长这样version: 3.8 services: app: image: yourusername/my-app:latest # 或使用 build: . 直接从 Dockerfile 构建 container_name: my-app restart: always # 总是重启保证服务高可用 ports: - 80:3000 environment: - DATABASE_URLpostgresql://user:passworddb:5432/mydb depends_on: - db networks: - app-network db: image: postgres:13-alpine container_name: my-db restart: always environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data # 数据持久化 networks: - app-network volumes: postgres_data: # 声明一个命名卷用于持久化数据库数据 networks: app-network: # 创建一个自定义网络方便服务间通过服务名通信在服务器上安装 Docker Compose 后只需一行命令即可启动整个应用栈# 安装 Docker Compose (以 Linux 为例) sudo curl -L https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose # 在 docker-compose.yml 所在目录运行 docker-compose up -d-d同样代表后台运行。使用docker-compose logs -f app可以查看应用日志docker-compose down可以停止并移除所有容器。5.3 生产级部署考虑容器编排对于更复杂、要求高可用的生产环境你可能需要考虑 Kubernetes (K8s) 或 Docker Swarm 这类容器编排工具。它们能管理成百上千的容器负责自动部署、扩缩容、负载均衡、服务发现、滚动更新等。但这已经超出了“从零到一”的范畴属于“从一到N”的进阶话题了。对于大多数中小项目Docker Compose 已经足够强大和稳定。6. 持续集成与持续部署CI/CD思路手动登录服务器执行命令终究不是长久之计。一个理想的流程是当你将代码推送到 Git 仓库的主分支时自动触发构建、测试、并部署到服务器。这就是 CI/CD。你可以利用 GitHub Actions、GitLab CI、Jenkins 等工具来实现。其核心步骤通常包括监听代码推送配置 Webhook 或轮询。运行测试在干净的 CI 环境中运行你的单元测试、集成测试。构建 Docker 镜像使用docker build。推送镜像到仓库docker push到你的私有或公有 Registry。部署到服务器通过 SSH 连接到你的服务器执行docker pull和docker-compose up -d等命令或使用更优雅的 Agent 方式。一个简单的 GitHub Actions 工作流配置文件.github/workflows/deploy.yml骨架如下name: Deploy to Server on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Login to Docker Hub uses: docker/login-actionv2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image run: | docker build -t yourusername/my-app:${{ github.sha }} . docker push yourusername/my-app:${{ github.sha }} - name: Deploy to Server via SSH uses: appleboy/ssh-actionv0.1.4 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | docker pull yourusername/my-app:${{ github.sha }} docker tag yourusername/my-app:${{ github.sha }} yourusername/my-app:latest docker-compose -f /path/to/your/docker-compose.yml up -d你需要将DOCKER_USERNAME、DOCKER_PASSWORD、SERVER_HOST等敏感信息配置在 GitHub 仓库的 Secrets 中。7. 常见问题与排查技巧实录即使流程清晰实操中依然会遇到各种问题。这里记录几个我高频遇到的坑和解决方法。问题一docker: Error response from daemon: driver failed programming external connectivity on endpoint...这通常是因为端口冲突。你尝试映射的宿主机端口如 80已经被其他进程占用。使用netstat -tulpn | grep :80或lsof -i :80查看占用进程停止它或为你的容器换一个端口。问题二容器内应用无法连接宿主机上的服务如数据库在容器内localhost或127.0.0.1指向的是容器自己而不是宿主机。要连接宿主机的服务需要使用宿主机的真实 IP或者 Docker 在 Linux 上提供的一个特殊 DNS 名称host.docker.internal在 Docker Desktop for Mac/Windows 中自动支持Linux 版 Docker 需要额外配置。更标准的做法是将依赖的服务如数据库也容器化并通过 Docker Compose 在同一个自定义网络内通信如上文示例所示。问题三容器运行后立即退出这通常是因为容器的主进程即CMD或ENTRYPOINT指定的命令执行完毕退出了。对于 Web 服务器要确保它是以“前台”模式运行而不是作为守护进程启动后自己退出。例如对于 Node.js 应用就是直接node app.js对于 Nginx默认就是前台模式。使用docker logs container_id查看容器日志是定位此类问题的第一选择。问题四构建镜像时下载依赖超慢或失败这就是前面提到的配置国内镜像加速器的重要性。另外对于npm可以在构建时通过RUN npm config set registry https://registry.npmmirror.com临时切换源。对于pip可以使用-i参数指定镜像源。问题五如何查看容器资源使用情况使用docker stats命令可以实时查看所有运行中容器的 CPU、内存、网络 IO 使用情况。这对于监控和排查性能问题非常有用。问题六数据如何持久化容器本身是无状态的停止后其内部产生的所有数据都会丢失。对于数据库、上传的文件等需要持久化的数据必须使用Docker 卷Volume或绑定挂载Bind Mount。在docker run时使用-v参数或在docker-compose.yml中定义volumes。强烈建议使用命名卷如上面 Compose 示例中的postgres_data由 Docker 管理其生命周期和存储位置比直接绑定宿主机目录更安全、便携。整个流程走下来你会发现一旦将 Docker 这套流程跑通部署就从一件令人畏惧的琐事变成了一个可重复、可自动化、甚至有点愉悦的标准化操作。它带来的环境一致性和部署效率的提升在团队协作和项目维护中价值巨大。从今天起尝试用 Docker 来封装你的下一个项目吧这绝对是现代开发者工具箱里必不可少的一项技能。