上周帮一个朋友排查线上问题,他一脸茫然地问我:"为什么我本地跑得好好的容器,到服务器上就起不来了?"我进服务器一看------镜像tag是latest,启动命令写的是npm start,连Dockerfile都没有打版本号。这是一个典型"会用但没真正理解Docker"的状态。
本文不讲虚的,从镜像拉取→容器运行→Dockerfile定制→镜像仓库推送→多容器编排Compose→生产环境部署,一条龙把Docker的所有基础但核心的操作讲透。全文每个命令都有场景说明和踩坑提示,让你从"会敲命令"变成"真懂容器"。
一、Docker核心概念(一句话建立认知模型)
-
镜像(Image) :一个只读的模板,包含运行环境和代码,类比"类"。
-
容器(Container) :镜像的运行实例,类比"对象"。
-
仓库(Registry) :存放镜像的地方,Docker Hub是官方仓库。
-
Dockerfile:描述如何构建镜像的脚本。
-
docker-compose:定义和运行多容器的工具(YAML配置)。
理解这5个词,你已经掌握了50%的Docker思想。
二、镜像拉取与容器基础操作
2.1 拉取镜像(pull)
bash
# 从Docker Hub拉取nginx最新版
docker pull nginx
# 拉取指定版本
docker pull nginx:1.25
# 从私有仓库拉取(需先登录)
docker pull myregistry.com/myapp:1.0
踩坑提醒 :不指定tag默认latest,但在生产环境中永远不要用latest------你不知道它指向哪个具体版本,下次拉取可能变了导致行为不一致。
2.2 查看本地镜像
bash
docker images
# 或
docker image ls
2.3 运行容器(run)
bash
# 最简单的运行(前台运行,Ctrl+C退出)
docker run nginx
# 后台运行 -d,映射端口 -p 主机端口:容器端口,命名 --name
docker run -d --name mynginx -p 8080:80 nginx
# 进入容器内部交互(常用调试)
docker exec -it mynginx /bin/bash
# 运行一次性命令(如查看环境变量)
docker exec mynginx env
常用run参数速查:
| 参数 | 说明 | 示例 |
|---|---|---|
-d |
后台运行 | -d |
-p |
端口映射 | -p 8080:80 |
-v |
挂载卷(持久化) | -v /host/data:/app/data |
-e |
设置环境变量 | -e MYSQL_ROOT_PASSWORD=123 |
--restart |
重启策略 | --restart=always |
--network |
指定网络 | --network mynet |
2.4 管理容器
bash
# 查看运行中容器
docker ps
# 查看所有容器(含已停止)
docker ps -a
# 停止/启动/重启
docker stop mynginx
docker start mynginx
docker restart mynginx
# 删除容器(需先停止)
docker rm mynginx
# 强制删除运行中容器
docker rm -f mynginx
# 查看容器日志
docker logs mynginx
docker logs -f mynginx # 实时跟踪
三、Dockerfile:定制自己的镜像
3.1 一个生产级Dockerfile示例(Node.js)
dockerfile
# 1. 指定基础镜像(尽量用alpine瘦身版)
FROM node:18-alpine AS builder
# 2. 设置工作目录
WORKDIR /app
# 3. 复制依赖文件(利用缓存层)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# 4. 复制源代码
COPY . .
# 5. 多阶段构建:最终镜像只复制产物
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
# 6. 声明运行时用户(非root安全)
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
# 7. 暴露端口
EXPOSE 3000
# 8. 启动命令(建议用JSON数组形式)
CMD ["node", "dist/index.js"]
3.2 Dockerfile最佳实践
-
利用构建缓存:把不常变的指令(如COPY package.json)放在前面,源代码放在后面。
-
多阶段构建:最终镜像只包含运行必需文件,体积可减少80%以上。
-
指定具体基础镜像tag :不要用
node:latest,用node:18.17.0-alpine。 -
最小权限原则:不要用root运行应用,创建普通用户。
-
合并RUN命令 :减少镜像层数,如
RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/*。
3.3 构建镜像(build)
bash
# 基本构建
docker build -t myapp:1.0 .
# 指定Dockerfile路径
docker build -t myapp:1.0 -f Dockerfile.prod .
# 构建时不使用缓存
docker build --no-cache -t myapp:1.0 .
3.4 查看镜像层历史
bash
docker history myapp:1.0
四、镜像仓库:推送与拉取
4.1 登录仓库
bash
# Docker Hub
docker login
# 私有仓库(如阿里云/自建Harbor)
docker login myregistry.com -u username -p password
4.2 标记镜像(tag)
bash
# 语法:docker tag 源镜像 目标仓库地址/命名空间/镜像名:tag
docker tag myapp:1.0 myregistry.com/dev/myapp:1.0
4.3 推送镜像
bash
docker push myregistry.com/dev/myapp:1.0
4.4 拉取私有镜像
bash
docker pull myregistry.com/dev/myapp:1.0
4.5 仓库管理常用命令
bash
# 搜索镜像(Docker Hub)
docker search nginx
# 删除本地镜像
docker rmi myapp:1.0
# 清理未使用的镜像
docker image prune -a
五、数据持久化:Volume与Bind Mount
5.1 两种挂载方式对比
| 类型 | 命令示例 | 特点 | 使用场景 |
|---|---|---|---|
| Volume(卷) | -v myvolume:/data |
Docker管理,存于/var/lib/docker/volumes/ |
数据库、配置文件持久化 |
| Bind Mount(绑定挂载) | -v /host/path:/container/path |
直接挂载宿主机目录 | 开发热加载、日志输出 |
5.2 Volume操作
bash
# 创建卷
docker volume create mydata
# 查看卷列表
docker volume ls
# 挂载卷运行容器
docker run -d -v mydata:/var/lib/mysql --name mysql mysql
# 查看卷详情
docker volume inspect mydata
# 删除无用卷
docker volume prune
5.3 容器间共享卷
bash
# 创建一个共享卷
docker volume create shared
# 容器A挂载
docker run -d --name app1 -v shared:/app/data busybox
# 容器B挂载同一个卷
docker run -d --name app2 -v shared:/app/data busybox
六、docker-compose:多容器编排
6.1 一个完整的docker-compose.yml示例(LNMP)
yaml
version: '3.8'
services:
nginx:
image: nginx:1.25-alpine
container_name: my-nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./html:/usr/share/nginx/html
depends_on:
- php
- mysql
networks:
- appnet
php:
build:
context: ./php
dockerfile: Dockerfile
container_name: my-php
volumes:
- ./html:/var/www/html
environment:
- MYSQL_HOST=mysql
- MYSQL_DB=mydb
depends_on:
- mysql
networks:
- appnet
mysql:
image: mysql:8.0
container_name: my-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: mydb
MYSQL_USER: app
MYSQL_PASSWORD: app123
volumes:
- mysql-data:/var/lib/mysql
ports:
- "3306:3306"
networks:
- appnet
volumes:
mysql-data:
networks:
appnet:
driver: bridge
6.2 常用compose命令
bash
# 启动所有服务(-d后台运行)
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f
# 停止并删除容器、网络(默认不删volumes)
docker-compose down
# 停止并删除volumes
docker-compose down -v
# 重新构建镜像后启动
docker-compose up -d --build
# 扩容某个服务(比如php启动3个实例)
docker-compose up -d --scale php=3
# 执行一次性命令(如数据库迁移)
docker-compose run php php artisan migrate
6.3 Compose生产配置要点
-
指定镜像tag :不要用
latest,写具体版本。 -
资源限制:避免容器耗尽主机资源。
yaml
services:
mysql:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
memory: 1G
- 健康检查:
yaml
services:
php:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
- 配置外部网络(让多个compose项目互通):
bash
docker network create shared_network
yaml
networks:
default:
external:
name: shared_network
七、进阶编排:docker swarm vs kubernetes(简要)
虽然Compose适合单机,但在生产环境往往需要集群编排。这里简要对比:
| 特性 | Docker Swarm | Kubernetes |
|---|---|---|
| 复杂度 | 低,集成在Docker中 | 高,需独立部署 |
| 服务发现 | 内置DNS | 内置DNS+Service |
| 负载均衡 | 内置 | 多种Ingress |
| 滚动更新 | 支持 | 高级策略 |
| 学习曲线 | 平缓 | 陡峭 |
Swarm快速上手
bash
# 初始化集群
docker swarm init --advertise-addr 192.168.1.10
# 加入工作节点(在其他机器执行)
docker swarm join --token <token> 192.168.1.10:2377
# 部署stack(类似compose但支持集群)
docker stack deploy -c docker-compose.yml myapp
# 查看服务
docker service ls
# 扩容
docker service scale myapp_php=5
八、生产部署实战:一个完整的CI/CD视角
8.1 流程概览
-
开发者推送代码到Git
-
CI服务器拉取代码,执行测试
-
构建Docker镜像,推送私有仓库
-
部署服务器拉取新镜像,滚动更新容器
8.2 镜像版本规范
建议使用:<registry>/<project>:<git-commit-hash> 或 :build-<build-number>
示例:myregistry.com/order-service:a1f9e3d
8.3 部署脚本示例(bash)
bash
#!/bin/bash
IMAGE_TAG=$(git rev-parse --short HEAD)
REGISTRY="myregistry.com"
PROJECT="order-api"
# 构建
docker build -t $REGISTRY/$PROJECT:$IMAGE_TAG .
# 推送
docker push $REGISTRY/$PROJECT:$IMAGE_TAG
# 在生产服务器上拉取并更新(假设使用docker-compose)
ssh user@prod-server "
docker pull $REGISTRY/$PROJECT:$IMAGE_TAG
cd /app/$PROJECT
sed -i 's|image:.*|image: $REGISTRY/$PROJECT:$IMAGE_TAG|' docker-compose.yml
docker-compose up -d --no-deps --scale $PROJECT=3 $PROJECT
"
8.4 镜像清理策略
bash
# 定期删除旧镜像(保留最近5个)
docker image prune -a --filter "until=168h" -f
九、常见问题与排障
9.1 容器无法启动
bash
# 查看日志
docker logs <container>
# 检查退出码
docker ps -a
# 进入停止状态的容器(需要先启动一个调试容器)
docker run -it --entrypoint /bin/sh myapp:1.0
9.2 端口冲突
bash
# 查看宿主机端口占用
netstat -tulnp | grep :8080
# 或者改docker映射端口
docker run -p 8081:80 ...
9.3 磁盘占满
bash
# 查看docker占用空间
docker system df
# 清理未使用资源
docker system prune -a --volumes
9.4 容器内时区不对
yaml
# compose中挂载时区文件
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
或Dockerfile中设置环境变量:
dockerfile
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
十、总结:Docker命令脑图与学习路线
text
docker
├── 镜像管理
│ ├── pull / push
│ ├── build (Dockerfile)
│ ├── tag / rmi
│ └── history / inspect
├── 容器生命周期
│ ├── run ( -d -p -v -e --name --restart )
│ ├── start / stop / restart
│ ├── rm ( -f )
│ └── exec / logs
├── 存储
│ ├── volume (create / ls / inspect / prune)
│ └── bind mount
├── 网络
│ ├── network create / ls / inspect
│ └── bridge / overlay / host
├── 编排
│ ├── docker-compose (up / down / logs / exec)
│ └── docker stack (swarm)
└── 系统
├── system df / prune
└── info / version
学习建议:
-
先敲熟
run、exec、logs、ps这四个最常用命令。 -
自己写一个简单的 Dockerfile(比如一个 Python Flask 应用),构建并运行。
-
学会用 Compose 启动 LNMP/MEAN 完整环境。
-
最后再接触 Swarm/K8s。
掌握 Docker 并不难,难的是形成"任何应用都应该容器化"的思维习惯。当你看到一个新项目,脑子里自动浮现出 Dockerfile 和 compose 的结构时,你就真正入门了。