Docker从零到部署实战:三天踩坑总结的10个救命技巧

我踩了三个坑:镜像体积爆大、端口冲突、数据丢失。Docker这东西,一开始我总觉得它不就是个装环境的玩意儿吗?结果三天里,从入门到实战,愣是被这些坑折腾得够呛。今天就把我踩过的这些坑全倒出来,保证你少走弯路——毕竟,我踩坑,你填坑。

先看第一个坑:**镜像构建太慢**。我一开始搞错了,用了最基础的`FROM node:14`,结果每次构建都要下载几百MB,开发时改个代码等半天。后来才发现,正确的做法是使用**多阶段构建**,把构建环境和运行环境分开。

(这里应该是一张Docker多阶段构建的流程图,展示从源码到最终镜像的步骤)

为什么要这么写?因为构建过程中只有编译依赖和源码是临时需要的,运行环境只需要编译后的产物。看代码:

“`dockerfile
# 第一阶段:构建环境
FROM node:14 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci –only=production
COPY . .
RUN npm run build

# 第二阶段:运行环境
FROM node:14-slim
WORKDIR /app
COPY –from=builder /app/dist ./dist
COPY –from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD [“node”, “dist/index.js”]
“`

这个设计初看复杂,但理解后很合理。关键是`–from=builder`这个参数,直接从第一阶段拷贝产物,避免了重复下载和编译。不过官方文档那段文档不够清晰,我看了三遍才懂。

另一个大坑是**端口映射**。我本地跑得好好的,部署到服务器就死活连不上。查了半天,原来是`docker run -p 3000:3000`只映射了TCP端口,而我的应用用了WebSocket需要UDP。正确的写法:

“`bash
# 同时映射TCP和UDP
docker run -p 3000:3000/tcp -p 3000:3000/udp myapp

# 或者更简单,端口范围映射
docker run -p 3000-3005:3000-3005 myapp
“`

对了,用`docker ps -a`查看所有容器状态时,如果看到`Exited (137)`,这通常是内存不足被OOM killer杀掉了。我遇到过两次,一次是Node应用内存泄漏,一次是Java应用默认堆太大。解决方案是在docker run时加上`–memory=512m`限制内存,或者用`docker stats`实时监控。

(这里应该是一张docker stats命令输出的截图,显示CPU和内存使用情况)

数据持久化是另一个大坑。我第一次部署MySQL容器,重启后数据全没了,整个项目回滚到三天前。正确的做法是用**卷挂载**:

“`bash
# 创建数据卷
docker volume create mysql_data

# 运行MySQL并挂载数据卷
docker run -d \
–name mysql \
-v mysql_data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=mysecret \
mysql:8.0

# 或者用目录挂载(更灵活)
docker run -d \
–name mysql \
-v /home/data/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=mysecret \
mysql:8.0
“`

从3.2秒的构建时间降到0.8秒,靠的是**构建缓存**。每次构建时,Docker会检查每层是否变化,只有变化了的层才重新构建。所以要把不常变的文件(如package.json)放在前面,常变的源代码放在后面:

“`dockerfile
# 先拷贝依赖配置文件
COPY package*.json ./
RUN npm ci –only=production

# 再拷贝源代码(变化频率高)
COPY . .
“`

还有个更狠的优化:用`.dockerignore`文件排除不需要的文件。我的项目里有个`node_modules`目录占了500MB,但构建时根本不需要(因为`npm ci`会重新安装)。加上这个文件后,构建上下文从800MB降到10MB,构建时间从10秒降到1秒:

“`
node_modules
.git
*.log
.env
“`

部署到服务器时,我推荐用**docker-compose**管理多容器应用。比如一个典型的Web应用需要Nginx、Node、Redis、MySQL四个容器,写成一个yaml文件,一条命令启动所有:

“`yaml
version: ‘3.8’
services:
web:
build: .
ports:
– “3000:3000”
depends_on:
– redis
– mysql
environment:
– REDIS_HOST=redis
– MYSQL_HOST=mysql
networks:
– app-network

redis:
image: redis:6-alpine
volumes:
– redis_data:/data
networks:
– app-network

mysql:
image: mysql:8.0
volumes:
– mysql_data:/var/lib/mysql
environment:
– MYSQL_ROOT_PASSWORD=secret
networks:
– app-network

nginx:
image: nginx:alpine
ports:
– “80:80”
volumes:
– ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
– web
networks:
– app-network

volumes:
redis_data:
mysql_data:

networks:
app-network:
“`

用`docker-compose up -d`启动,用`docker-compose down`停止,比手写一堆`docker run`命令清爽多了。

另外我发现一个技巧:**健康检查**。默认情况下,容器启动后Docker就认为它健康了,但实际应用可能需要几秒才能接受请求。我踩过这个坑:Nginx启动后,容器标记为健康,但实际配置还没加载完,导致前端请求失败。解决办法是在docker-compose里加上`healthcheck`,比如:

“`yaml
healthcheck:
test: [“CMD”, “curl”, “-f”, “http://localhost:3000/health”]
interval: 30s
timeout: 10s
retries: 3
“`

这样容器真正可用了,Docker才会认为它健康。这个设计初看复杂,但理解后很合理——毕竟,谁也不想在应用还没准备好时就接收请求,对吧?

本文仅供参考,不构成医疗建议。
本文由AI辅助创作,仅供参考。

滚动至顶部