尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Docker容器化部署实战:从镜像构建到Compose编排与HTTPS接入

Docker容器化部署实战:从镜像构建到Compose编排与HTTPS接入 这篇记录我自己在腾讯云轻量服务器上从零部署容器化应用的完整过程从最基础的 Docker 安装到镜像构建、多容器编排、域名 HTTPS 接入再到后来遇到的坑和自动化部署的经验一次性讲透。文章里的每一步都是实际操作过的可以直接照着做。先交代一下背景这台轻量服务器是 2核4G 的配置系统选的 Ubuntu 22.04 LTS。这个配置对 Docker 来说是比较舒服的区间跑两三个常规 Web 服务加数据库容器内存不会吃紧。如果你只有 2G 内存就得在镜像精简和容器数量上多花心思后面会提到具体怎么取舍。我用这套流程完成了个人项目的上线也帮朋友的小团队搭过类似环境整个过程反复验证过多次。文章顺序就是我当时操作的顺序从服务器准备到 Docker 安装再到构建镜像、compose 编排、证书配置最后是运维经验和自动化部署思路。每部分都会解释为什么要这么做而不只是给命令。1. 部署前的服务器初始化几个容易被忽略的关键动作很多 Docker 教程直接从安装命令讲起但我建议先花十分钟把服务器底子打好。这一步做扎实了后面能省掉大量莫名其妙的权限和时间问题。1.1 为什么轻量服务器适合容器化部署先解释一下选型逻辑。轻量服务器和传统云主机相比管理面简化了很多固定套餐、固定带宽、自带防火墙控制台不用自己折腾底层虚拟化的问题。对个人项目、小团队 demo、博客站点这些负载来说2核4G 的轻量服务器基本是甜点配置价格可控性能足够。系统镜像我选了 Ubuntu 22.04 LTS核心原因只有一个Docker 官方对 Ubuntu 的支持最完善文档里的安装命令几乎零改动就能用遇到问题时网上能搜到的踩坑案例也最多。Linux 发行版很多但作为生产环境遇到问题能快速找到解决方案比什么都重要。1.2 拿到服务器后的三个基础动作第一步更新系统软件包索引。刚开出来的服务器镜像自带的软件源快照可能比较旧直接装软件容易碰到依赖版本过老的问题。sudo apt update sudo apt upgrade -y第二步创建一个部署专用用户别全程用 root。这个习惯我是吃了亏才养成的早年间全程 root 操作一次误删系统文件后恢复起来非常被动。容器化部署涉及大量文件挂载和权限映射如果所有文件都归 root 所有后面容器里的进程以普通用户身份运行时经常撞上权限问题。sudo adduser deploy sudo usermod -aG sudo deploy第三步提前在腾讯云控制台的防火墙规则里放行需要的端口。轻量服务器默认只放行 22、80、443 以及 ICMP如果你后面要暴露 8080、3000 这类端口记得提前加规则。这个顺序很多人搞反了等部署完发现访问不通才去翻防火墙来回折腾半个小时。我的习惯是先放行再部署。2. Docker 安装全流程官方源、权限根治与镜像加速Docker 的安装方式在网上一搜一大把有让你用系统源直接apt install docker.io的有推荐一键脚本的还有教你怎么从源码编译的。我只推荐一种用 Docker 官方源安装。理由有两个官方源版本更新及时很多新特性和安全修复都体现在新版本里官方源提供的是docker-ce和docker-ce-cli分开的包安装灵活卸载干净。2.1 基于官方源的完整安装步骤在 Ubuntu 22.04 上我用的安装命令如下实测最顺畅# 安装依赖 sudo apt update sudo apt install -y ca-certificates curl # 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod ar /etc/apt/keyrings/docker.asc # 添加官方软件仓库 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) 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 docker-buildx-plugin docker-compose-plugin注意最后一行我特意把docker-buildx-plugin和docker-compose-plugin一起装了。buildx 用来构建多平台镜像compose 插件用来编排多容器应用这两块后面都会用到。装完以后验证一下docker --version docker compose version docker buildx version三个命令都有输出说明安装完整。2.2 权限报错的根治方案新装好的 Docker 默认只有 root 和 docker 用户组的成员能操作 socket 文件。你切换到自己创建的 deploy 用户执行docker ps大概率会看到这个报错permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock网上很多教程让直接改 socket 文件的权限比如chmod 666 /var/run/docker.sock这是典型的治标不治本。docker.sock是 Docker 守护进程的通信接口把它权限放大意味着任何本地用户都能直接操控 Docker而 Docker 的权限模型里能访问 socket 基本等于能拿 root。正确做法是把用户加进 docker 组sudo usermod -aG docker deploy执行完后需要重新登录一次或者执行newgrp docker让组权限在当前会话生效。然后跑一个 hello-world 验证docker run hello-world看到 Hello from Docker! 的输出权限这一步才算真正解决。千万不要用 chmod 的方式去修权限问题那是在给服务器埋雷。2.3 镜像加速配置国内服务器上拉取 Docker Hub 镜像的速度问题是不少人入门的第一个坎。直接docker pull一个较大的镜像很可能卡着不动甚至超时。我的做法是配置镜像加速器编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io ] }然后重启 Docker 服务sudo systemctl restart docker这里要提醒的是公共加速器地址没有永远稳定的建议一次配置多个备选遇到失效的及时更换。另外减少拉取体积本身就是策略的一部分后面会讲到的基础镜像选型就是在源头控制镜像大小。3. 从代码到镜像构建你的第一个容器化应用这一节不拿 hello-world 凑数用一个简单的 Node.js Web 服务走完整流程。Docker 的核心概念虽然抽象但落到一个具体项目上就非常好理解了。3.1 示例项目的文件结构项目结构如下my-app/ ├── app.js ├── package.json └── Dockerfileapp.js是一个极简 HTTP 服务const http require(http); const server http.createServer((req, res) { res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify({ message: Hello from Docker, timestamp: new Date().toISOString() })); }); server.listen(3000, () { console.log(Server running on port 3000); });package.json{ name: my-app, version: 1.0.0, main: app.js, scripts: { start: node app.js } }3.2 Dockerfile 的每一行都在做什么Dockerfile 是整个流程的核心逐行看一下设计意图FROM node:20-alpine WORKDIR /app COPY package.json ./ RUN npm install COPY app.js ./ EXPOSE 3000 CMD [node, app.js]FROM node:20-alpinealpine 是体积最小的 Linux 发行版之一基于 musl libc。同样的 Node.js 运行时alpine 镜像只有完整版的三分之一左右。这里就体现出前面说的架构差异完整的 node:20 镜像动辄 1GBalpine 版大概 180MB拉取快、占用小安全暴露面也小很多。WORKDIR /app设定工作目录后续的 COPY、RUN、CMD 都以此为基础。如果不设置文件会堆在根目录既乱又有权限风险。COPY package.json ./ RUN npm install先把依赖清单拷进去再安装依赖。这里用到了 Docker 的层缓存机制package.json的变更频率远低于源码单独成层后只要依赖清单不变后续构建都会直接命中缓存不用重复执行 npm install。这个设计在项目变大后效果非常明显。CMD [node, app.js]容器启动时执行的命令。注意写成 JSON 数组形式Docker 会直接把它作为可执行文件的参数列表不会经过 shell 解析避免信号传递问题。3.3 构建、运行与首轮验证在项目目录下执行docker build -t my-app:1.0.0 .-t参数指定镜像名和标签命名规范建议用应用名:版本号的格式。多版本并存时没有标签管理的镜像仓库会乱成一锅粥。构建完成后确认产物docker images | grep my-app启动容器docker run -d --name my-app-container -p 8080:3000 my-app:1.0.0参数说明-d后台运行--name给容器起名字省得记随机 ID-p 8080:3000宿主机 8080 端口映射到容器内 3000 端口容器是一个独立的网络空间外部访问必须经过端口映射这座桥。验证访问curl http://localhost:8080能看到 JSON 响应第一个容器化应用就跑通了。此时如果你在防火墙里放行了 8080 端口也可以直接用http://服务器公网IP:8080访问。3.4 容器生命周期的日常管理命令跑通之后有几条命令是日常运维的高频操作# 查看运行中的容器 docker ps # 查看所有容器包括已停止的 docker ps -a # 查看容器日志后端排查的第一手段 docker logs -f my-app-container # 进入容器内部调试 docker exec -it my-app-container sh # 停止 / 启动 / 重启容器 docker stop my-app-container docker start my-app-container docker restart my-app-container # 删除容器 docker rm -f my-app-container # 删除悬空镜像 docker image prune -f日志排查在线上问题定位中特别重要。容器启动后应用的 stdout 输出会进入 Docker 的日志驱动docker logs -f是查看应用有没有正常启动、有没有报错的第一现场。很多新手按教程跑完容器发现访问不通第一反应是改代码实际应该先看日志——八成是端口映射配错或者应用内部启动失败。4. 镜像瘦身与构建优化从 1GB 到 180MB 的实操对比镜像体积在本地开发时感受不明显一旦上云就变得非常实在。服务器带宽有限每拉一次大镜像都要等很久。如果后面接自动化部署每次发布都要重新拉镜像时间成本会成倍放大。4.1 基础镜像选型的对比以 Node.js 应用为例我实际对比过三档做法方案基础镜像典型体积优劣势完整版node:201GB工具全但体积大、拉取慢安全暴露面也大精简版node:20-slim约 300MB基于 Debian兼容性好体积适中极简版node:20-alpine约 180MB体积最小但部分原生依赖需要额外编译对于纯 Node.js 应用来说alpine 基本是最优解。但如果你的应用依赖了需要编译原生模块的 npm 包alpine 基于 musl libc 的特性会让部分预编译二进制包无法直接使用需要走编译流程这时候建议退一步用 slim省心优先。4.2 依赖缓存层面的 Dockerfile 层设计Dockerfile 的每一行指令都会产生一个只读层Docker 在重新构建时会检查每一层是否可以复用之前构建过的缓存。这个机制用好了构建速度能快一个数量级。一个典型的反模式是COPY . . RUN npm install这种写法把整个项目目录拷进去后再装依赖只要源码里任何一个文件发生了改动COPY . .这行的校验值就会变化后续所有层全部失效npm install 每次都得重新跑。大型项目里 npm install 动辄几分钟到十几分钟每次构建都白等。推荐的分层策略是COPY package.json ./ RUN npm install COPY . .依赖清单先拷安装依赖的层先构建。因为package.json只有在依赖变更时才会变平时代码改动的构建都能命中依赖层缓存秒级完成。4.3 用 .dockerignore 控制构建上下文还有一个很少被人提起但真实项目中非常重要的文件.dockerignore。构建镜像时Docker 会把项目目录作为上下文打包传给守护进程。如果你直接用项目根目录作为上下文构建node_modules这种动辄几百 MB 的目录也会被打包进去既拖慢构建速度又可能让本地依赖意外混进镜像。在项目根目录创建.dockerignorenode_modules .git *.log .DS_Store这样 Docker 在发送构建上下文时自动排除这些文件构建产物更干净速度更快。这一步和代码仓库里的.gitignore是同一个思路很多入门教程会漏掉但线上项目里它是标配。5. 多容器编排用 Docker Compose 管理业务与数据层第一个应用跑通后很快会碰到新的问题真实业务通常不是单一服务组成的至少需要一个应用服务加一个数据库可能还有缓存。你当然可以一个个手动docker run但容器之间的网络通信、启动顺序、数据卷挂载、环境变量统一管理靠命令行硬拼会非常痛苦。5.1 单容器模式撑不起真实业务我自己的一个典型场景是 Web 应用 MySQL Redis 的组合。手动跑三个容器的话你得记住每个容器的启动参数还要手动建一个网络让它们互通。卸载重建时更是灾难一旦参数写错全凭记忆排查。Docker Compose 解决的就是这个问题把所有容器的配置写在一个docker-compose.yml文件里一条命令启动整个应用栈。5.2 一个生产可用的 compose 配置解读目录结构如下my-stack/ ├── docker-compose.yml └── .envdocker-compose.yml的核心内容services: web: build: . ports: - 8080:3000 environment: - DB_HOSTmysql - DB_PASSWORD${DB_PASSWORD} - REDIS_HOSTredis depends_on: - mysql - redis restart: unless-stopped mysql: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASEmyapp - MYSQL_USERmyapp - MYSQL_PASSWORD${MYSQL_PASSWORD} volumes: - mysql_data:/var/lib/mysql restart: unless-stopped redis: image: redis:7-alpine command: redis-server --requirepass ${REDIS_PASSWORD} restart: unless-stopped volumes: mysql_data:几个关键设计点depends_on控制启动顺序但它只保证依赖服务的容器先启动并不保证服务内部已经完全就绪。严格的生产场景会用 healthcheck 配合condition: service_healthy但中小项目里 depends_on 加应用层重试一般够用。environment中通过${变量名}引用.env文件里的值避免把数据库密码这类敏感信息硬编码在 compose 文件里。mysql_data声明了一个命名卷MySQL 的数据会持久化到 Docker 管理的卷里。这样即使容器删了重建数据还在。restart: unless-stopped保证容器退出时自动重启除非手动停止。部署到服务器上的服务这条策略大大减少了宕机时间。.env文件示例DB_PASSWORDyour_db_password MYSQL_ROOT_PASSWORDyour_root_password MYSQL_PASSWORDyour_mysql_password REDIS_PASSWORDyour_redis_password启动整套服务cd my-stack docker compose up -d查看服务状态和日志docker compose ps docker compose logs -f停止服务docker compose down注意down默认不会删除命名卷数据还在。确认要清理数据才加-v参数但生产环境慎用。5.3 生产环境下 MySQL 容器部署的三个关键细节MySQL 8.0 在容器里跑起来不难但跑得稳需要处理几个细节。第一是字符集问题。MySQL 8.0 默认字符集是 utf8mb4比 5.7 时代进步了不少但通过环境变量启动时建议显式验证一下docker exec -it my-stack-mysql-1 mysql -u root -p执行SHOW VARIABLES LIKE character_set_database;确认是utf8mb4避免中文存储出现乱码隐患。第二是时区问题。容器默认时区是 UTC很多应用层代码在展示时间时会出现 8 小时的偏差。可以在 compose 的 mysql 服务里加启动参数command: - --default-time-zone08:00这个细节在小项目上线后特别容易被用户反馈时间不对才发现提前处理掉能看到很多不必要的工单。第三是数据备份。命名卷虽然解决了持久化但服务器硬件故障时卷也可能丢失。建议定期用mysqldump导出数据到宿主机配合 crontab 每天凌晨备份docker exec my-stack-mysql-1 sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD myapp /backup/myapp_$(date \%Y\%m\%d).sql再配合find /backup -name *.sql -mtime 7 -delete清理七天前的备份文件。备份这件事平时没用出事的时候就是救命稻草。6. 域名接入与 HTTPS 证书部署的最后一公里服务跑通、数据稳定后还差一个关键环节如何用标准的 80/443 端口对外提供服务并让域名指向正确的容器。6.1 反向代理与容器端口的关系直接给容器映射多个端口到宿主机不是不行但存在两个问题一是每个服务都要在宿主机上占一个端口访问时要记端口号不优雅二是 HTTPS 证书的配置要在每个服务里重复做维护成本高。常规做法是引入 Nginx 作为反向代理负责所有对外的流量入口。请求到达宿主机 80/443 端口后Nginx 根据域名将流量转发到对应的容器映射端口。这样一来域名和证书的统一管理被收敛到 Nginx 一层业务容器保持简洁。6.2 Nginx 反向代理配置与 Certbot 免费证书在腾讯云控制台把域名 A 记录解析到服务器公网 IP等待生效后在服务器上安装 Nginx 并配置反向代理。站点配置示例server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }几个proxy_set_header不是凑字数它们承担关键的协议转换工作。X-Forwarded-Proto告诉后端请求的原始协议是 HTTP 还是 HTTPS否则应用层做重定向或生成绝对链接时可能把 HTTPS 强写成 HTTP导致浏览器报不安全警告。验证并重载 Nginxsudo nginx -t sudo systemctl reload nginx然后安装 Certbot 签发免费证书sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d your-domain.comCertbot 会自动完成证书签发、Nginx 配置改写把 80 端口的 server 块改造成 443 HTTP/2以及证书续期。证书有效期 90 天Certbot 默认带有自动续期服务不用自己操心。完成这一步一套完整的上线链路才算走得通域名解析 → Nginx 转发 → 容器应用 → 数据库持久化 → HTTPS 证书。7. 部署后的运维经验与高频故障复盘上线只是开始运维才是长期的事。这一节把我在实际运行中踩过的高频故障和总结出的处理方案完整写出来。7.1 内存与磁盘的常态监控部署完立刻把监控工具装好不要等出事了再补救。先看最基础的资源占用docker stats这条命令实时显示每个容器的 CPU、内存、网络 I/O是排查哪个容器吃资源的第一工具。持续运行可以加--no-stream参数输出单次快照。磁盘方面容器运行时产生的日志、镜像每层的数据、容器可写层的内容都会占空间。跑一段时间后容易出现磁盘暴涨最常见的元凶是日志文件失控。给 Docker 的日志加上轮转限制在/etc/docker/daemon.json里配置{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }每个容器最多保留 3 份 10MB 的日志不会无限膨胀。已经出现磁盘告警时先用docker system df查看空间占用分布再用docker system prune清理悬空镜像、停止的容器和未使用的网络。7.2 服务重启策略与宕机恢复链路容器化部署的崩溃恢复思路和传统部署不同。传统部署中进程崩了要 SSH 上去重启容器化之后Docker 本身承担了进程守护的工作restart: unless-stopped能处理大部分崩溃场景。但这不等于完全不用管。遇到服务器本身重启的情况Docker 服务需要设置开机自启sudo systemctl enable docker如果 compose 项目设置了restart: unless-stoppedDocker 服务启动后会自动拉起对应容器服务器意外重启后服务也能自动恢复。需要特别注意的是容器崩溃但宿主机健康的情况下docker ps -a里会出现不断重启的容器STATUS 显示 Restarting。这时候服务器本身可能一切正常但服务已经挂了。我的经验是给容器配置应用层的健康检查让 Docker 能感知进程活着但服务不可用的状态healthcheck: test: [CMD, curl, -f, http://localhost:3000/health] interval: 30s timeout: 5s retries: 3配合depends_on的condition: service_healthy可以在资源有限的小服务器上减少启动期错误。这一步在处理依赖多个服务的应用时非常关键。7.3 我实际踩过的高频故障与解决链路把几个最典型的故障场景完整写出来每个场景附带排查链路方便直接复现。故障一端口被占用导致容器无法启动启动容器时报错Bind for 0.0.0.0:8080 failed: port is already allocated。排查思路是先看端口占用情况sudo ss -tlnp | grep 8080我实际查到的占用方往往是另一个容器映射了相同端口或者宿主机上之前手动跑的服务没关。解决方法是改映射端口或者停掉旧进程后重启目标容器。这提醒了在 compose 中管理端口可以避免很多手滑的冲突因为同一个 compose 项目的端口分配在文件里一目了然。故障二Exited 状态的容器占满磁盘现象是docker ps -a里躺着几十个 Exited 状态的容器每个都占着磁盘空间。解决方案docker container prune -f一次性清掉停止状态的容器。镜像同理docker image prune -a可以删除所有未被容器引用的镜像但注意这会连旧版本镜像一起清掉回滚能力会受影响需要谨慎权衡。故障三应用配置了 localhost 但在容器里访问不到这是多容器部署后最容易踩的坑。容器的网络空间是独立的localhost指向的是容器自身而不是宿主机。如果应用里配置的是localhost:3306去连数据库容器会尝试连自己而不是连 MySQL 容器。正确写法是在 compose 网络内使用服务名比如mysql:3306。Docker Compose 默认创建的桥接网络中服务名就是容器之间互相访问的 DNS 名称。这个知识点在单容器实验时完全不会暴露多容器编排后是第一个必踩的坑。7.4 镜像更新与服务发布的流程容器化部署最舒服的体验在于发布流程代码更新 → 构建新镜像 → 重建容器整个过程不碰服务器上的其他东西。以 compose 项目为例我常用的发布流程是cd my-stack git pull docker compose build web docker compose up -d webup -d会检测到配置或镜像有变化自动创建并启动新容器然后移除旧容器。发布完成后检查日志确认为新版本docker compose logs --tail20 web我在流程里额外加了一步发布前打一个镜像标签备份当前版本。docker tag my-stack-web:latest my-stack-web:backup因为实际发布中遇到过新镜像启动失败的情况这时回滚只需要重新指定镜像标签再重建容器即可。服务器跑业务后任何没有回滚预案的发布操作都是给半夜的自己挖坑。8. 镜像分发与私有仓库多机协作的基石当你的服务从一台服务器跑全部变成多台服务器协作时镜像的分发就成了绕不开的问题。把镜像推到公共 Docker Hub 当然可以但公共仓库对私有项目不友好。8.1 自建 Registry 的部署方式自建 Registry 是 Docker 重度用户迟早要面对的事。好在registry镜像本身就是一个轻量应用docker run -d --name registry \ -p 5000:5000 \ --restartunless-stopped \ -v /opt/registry:/var/lib/registry \ registry:2这里用命名卷挂载了镜像存储目录确保镜像数据不会随容器删除而丢失。默认的 registry 没有认证和 TLS内网使用问题不大公网使用必须加认证和 HTTPS否则等于把端口裸奔出来。8.2 推送、拉取与 HTTP 访问配置在需要推送镜像的机器上给镜像打上仓库地址的 tagdocker tag my-app:1.0.0 your-server-ip:5000/my-app:1.0.0 docker push your-server-ip:5000/my-app:1.0.0拉取方同样指定仓库地址docker pull your-server-ip:5000/my-app:1.0.0这里有个坑要注意Docker 默认只允许通过 HTTPS 访问 registry直接使用 IP 加端口会报错http: server gave HTTP response to HTTPS client。解决方式是在需要访问该仓库的机器上的/etc/docker/daemon.json中加入 insecure-registries{ insecure-registries: [your-server-ip:5000] }然后重启 Docker 服务。这个配置项解决了 HTTP 裸奔仓库的访问问题但意味着该仓库的通信不加密只建议在受信内网环境中使用。如果你有多台服务器需要同步部署同一套服务私有仓库加 compose 的组合会极度顺畅。9. 自动化部署方向从手动构建到流程化发布手动发布虽然能用但本质还是人肉运维。当项目更新频繁、服务器数量增多时手动构建镜像、推送、拉取、重建容器这套操作占用的时间会变得相当可观。9.1 一套简单可用的 Git 触发部署设计以 GitLab CI/CD 为例构建与部署流程可以分成几个关键阶段。整体设计可以描述为代码合并到主分支GitLab 检测到变更。Runner 在独立环境执行 docker build使用 Dockerfile 构建镜像。构建成功后推送镜像到私有 Registry。目标服务器执行 docker compose pull 与 docker compose up -d完成容器升级。这套链路的核心优势在于构建过程不依赖开发者本地环境任何人在任何机器上提交代码产生的镜像都是可复现的。开发者本地环境的不同不会再造成在我机器上是好的这种问题。9.2 用 Webhook 触发部署的轻量替代方案如果不想引入完整的 CI Runner可以用更轻量的方案在服务器上部署 webhook 监听服务在镜像仓库或代码平台配置推送事件通知由 webhook 触发部署脚本。这类工具的原理统一监听一个 HTTP 端点收到预设的请求后执行指定的部署命令。它非常适合个人项目和小团队不额外占用资源也不需要专门的 CI 基础设施。服务器上只需要准备一个 deploy.sh 脚本内容核心就是三步切换到项目目录、拉取新镜像、重建容器。配合docker compose up -d的幂等特性重复执行不会产生副作用。我用这套方案的个人项目代码 push 后基本 10 秒内完成自动部署体验上接近提交即上线。9.3 容易忽略的回滚预案自动化部署跑通之后很多人会忽略一个问题新版本出问题时如何快速回滚。容器化的天然优势使回滚相对简单前提是事先保留好旧镜像。我目前的习惯是每次构建都打上 Git 提交号或时间戳的 tag例如my-app:20250115-1430。同时维护一个my-app:latest指向最新版本。发现新版本异常时将latest手动指回上一个稳定 tag然后重建容器。这套策略在低成本基础设施上特别适用不引入蓝绿部署、金丝雀发布的复杂度却能把故障恢复时间控制在几分钟内。10. 我的经验沉淀与下一步进阶方向10.1 入门阶段最常用的一套命令序列回顾整个从零到部署的流程很多学习者在初期会被 Docker 海量的命令和概念淹没。这里把最常用的一套命令沉淀出来可以作为日常操作手册# 构建镜像 docker build -t my-app:version . # 查看本地镜像与容器 docker images docker ps -a # 启动 / 查看 / 停止容器 docker run -d --name my-app-container -p 8080:3000 my-app:version docker logs -f my-app-container docker stop my-app-container # 多容器编排 docker compose up -d docker compose ps docker compose logs -f # 清理无用资源 docker system prune -f docker image prune -a10.2 初学者最容易混淆的三组概念第一组是镜像与容器的区别。镜像是一个只读的模板容器是模板的运行实例。打个比方镜像像是安装包容器像是安装运行后的程序。对镜像做的修改不会影响容器对容器做的写操作也不会影响镜像。如果要保存容器里的修改需要以当前容器为基础构建新镜像。第二组是端口映射与容器 IP 的区别。-p 8080:3000表示将宿主机的 8080 端口转发到容器的 3000 端口。容器内部的 IP 默认是 Docker 分配的私网地址外部无法直接访问端口映射是发布容器服务的唯一通道。容器间通信则走 Docker 内部的网络不经过宿主机端口。第三组是数据卷与容器文件系统的区别。容器可写层在容器删除后一并销毁数据卷是独立于容器生命周期的持久化存储。MySQL 的数据、上传的文件、生成的日志必须放到卷或挂载目录里否则容器一删数据就没了。这一点在设计 compose 文件时就要规划好临时补救往往损失惨重。10.3 完成入门后下一步该怎么选刷完这套完整流程后下一步的方向可以从三个维度选择。如果对镜像本身感兴趣可以研究多阶段构建。它通过在一个 Dockerfile 里使用多个 FROM把编译环境和运行环境分离最终只保留运行产物镜像体积能做到非常小。如果对编排感兴趣可以开始了解 Docker Swarm 或 Kubernetes。两者都能管理多台服务器上的容器但上手复杂度差别很大。个人项目和小团队建议先用 Swarm它和 Docker 的命令体系同源学习成本低很多Kubernetes 功能强大但概念众多不适合作为入门的第一选择。如果对运维自动化感兴趣可以从日志收集和监控告警入手。容器化的服务分解之后日志分散在每个容器的 stdout 里专门的日志平台可以将它们聚合配合告警规则可以让服务在出问题时主动通知你而不是等你收到用户反馈才后知后觉。我个人的建议是先把 docker compose 和私有仓库这两块彻底玩熟再视业务复杂度决定要不要上编排平台。很多团队花大量时间运维 Kubernetes实际业务负载用 compose 就能压得住。工具的选择跟着业务走而不是跟着热度走。这次在腾讯云轻量服务器上从零部署容器化应用的完整过程基本覆盖了 Docker 在日常项目中的全部核心触点。从一个简单的 Node.js 容器开始到数据库和缓存的编排再到 HTTPS 接入和自动化发布每一步踩过的坑和最后的解决思路都在上面了。希望这篇记录能帮你少走一些弯路把精力留在业务本身。
返回列表