第一次听说 Docker 的时候,我以为是某种船的名字。
后来才知道是个装软件的东西。但你上网一搜,全是"容器化"“微服务"“镜像编排"这种词,看得人头大。我当初就是被这些概念劝退了好几次。
直到有一天,我需要在服务器上搭个东西,折腾了俩小时环境配置都没弄好,旁边同事五分钟就搞定了。我问他怎么弄的,他说:“Docker 啊,一条命令的事。”
那一刻我决定认真学一下。
先别管概念,你就把它想象成一个盒子
Docker 是干嘛的?说白了就是把一个软件和它需要的所有东西打包成一个盒子,你拿到任何电脑上都能直接用。
以前装软件有多痛苦大家都经历过吧?装个 Python 要配环境变量,装个数据库要改一堆配置文件,换台电脑又得重来一遍。而且最恶心的是,你电脑上装的 A 软件需要 Python 3.8,B 软件需要 Python 3.11,两个版本打架,最后谁也跑不起来。
Docker 解决的就是这个问题。每个盒子(容器)里面自带一套完整的运行环境,互不干扰。你电脑上装的是 Python 3.11 没关系,盒子里面用的是 3.8,各玩各的。
镜像和容器的关系
这里要分清两个概念:镜像和容器。
镜像就像一个安装光盘,里面打包好了软件和环境。容器就像你用这个光盘装出来的软件实例。一个镜像可以装出很多个容器,就像一张光盘可以装很多台电脑。
你改了容器里的东西不会影响镜像,就像你装完软件后的操作不会改变安装光盘。但反过来,如果你想保存容器里的改动,可以把它"提交"成一个新的镜像。
用一张图来理解:
镜像 (Image) 容器 (Container)
┌─────────────┐ ┌─────────────┐
│ Ubuntu 基础 │ docker run │ Ubuntu 基础 │
│ + Python 3.8 │ ─────────→ │ + Python 3.8 │
│ + 你的代码 │ │ + 你的代码 │
│ + 依赖库 │ │ + 依赖库 │
│ (只读模板) │ │ + 运行状态 │
└─────────────┘ │ (可读可写) │
└─────────────┘
一个镜像可以创建多个容器
Docker 和虚拟机的区别
很多人会问:Docker 和虚拟机有什么区别?不都是隔离环境吗?
虚拟机是模拟一整台电脑,包括操作系统。所以虚拟机很重,启动要几分钟,占内存也多。
Docker 容器只隔离应用层,共用宿主机的操作系统内核。所以容器很轻,启动只要几秒钟,内存占用也小得多。
打个比方:虚拟机就像在一栋楼里隔出独立的房间,每个房间都要装自己的水电。Docker 就像在同一套房子里隔出不同的工位,水电是共用的,但各自的东西互不干扰。
虚拟机 Docker 容器
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ App1 │ │ App2 │ │ App3 │ │ App1 │ │ App2 │ │ App3 │
│ │ │ │ │ │ ├──────┤ ├──────┤ ├──────┤
│ Bins │ │ Bins │ │ Bins │ │ Bins │ │ Bins │ │ Bins │
│ │ │ │ │ │ └──────┘ └──────┘ └──────┘
│ OS │ │ OS │ │ OS │ ┌──────────────────────────┐
├──────┤ ├──────┤ ├──────┤ │ Docker Engine │
│Hypervisor │ │ (共享宿主机内核) │
├──────────────┤ ├──────────────────────────┤
│ 宿主机 OS │ │ 宿主机 OS │
└──────────────┘ └──────────────────────────┘
| 虚拟机 | Docker 容器 | |
|---|---|---|
| 启动时间 | 分钟级 | 秒级 |
| 内存占用 | GB 级 | MB 级 |
| 镜像大小 | GB 级 | MB 级 |
| 隔离级别 | 完全隔离(含内核) | 进程级隔离(共享内核) |
| 性能 | 有损耗 | 接近原生 |
装 Docker
说实话装 Docker 这一步是最简单的。
Linux(Ubuntu/Debian)
# 一键安装脚本
curl -fsSL https://get.docker.com | sh
# 把当前用户加到 docker 组(不用每次都 sudo)
sudo usermod -aG docker $USER
然后重新登录一下终端(或者重启),让组权限生效。
CentOS/RHEL/Fedora
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
Windows 和 Mac
去 Docker 官网 下载 Docker Desktop,装好打开就行。跟装个微信差不多。
Windows 用户注意:需要开启 WSL2(Windows Subsystem for Linux)。Docker Desktop 安装过程中会引导你开启,跟着提示走就行。
验证安装
docker --version
# Docker version 24.x.x, build xxxxx
docker run hello-world
能看到版本号,跑 hello-world 能看到 “Hello from Docker!",就说明装好了。
配置镜像加速(国内用户必做)
国内直接拉 Docker Hub 的镜像很慢,需要配置镜像加速器:
sudo tee /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
镜像操作:你的软件仓库
搜索镜像
想找什么软件,先搜一下:
docker search nginx
不过说实话,我一般直接去 Docker Hub 网站上搜,能看到详细说明和下载量,比命令行方便。
拉取镜像
下载一个镜像到本地:
docker pull nginx # 默认拉最新版
docker pull nginx:1.24 # 拉指定版本
docker pull mysql:8.0 # 拉 MySQL 8.0
版本号不写的话默认是 latest。建议在生产环境里明确指定版本,不然哪天自动更新了可能会出问题。
查看本地镜像
docker images
会列出你本地所有镜像,包括仓库名、版本、大小、创建时间等。
删除镜像
docker rmi nginx:latest # 删除指定镜像
docker image prune # 清理没用的镜像(悬空镜像)
docker image prune -a # 清理所有没被容器使用的镜像
容器操作:核心中的核心
运行容器
docker run -d -p 8080:80 --name my-nginx nginx
这条命令拆开看:
docker run:创建并启动一个容器-d:后台运行(detach),不占着你的终端-p 8080:80:端口映射,把电脑的 8080 端口映射到容器里的 80 端口--name my-nginx:给容器起个名字,方便后面操作nginx:用哪个镜像
跑完之后打开浏览器访问 http://localhost:8080,你会看到 nginx 的欢迎页面。
查看运行中的容器
docker ps
能看到容器的 ID、用的什么镜像、端口映射、运行了多久等等。
如果想看所有容器(包括已经停了的):
docker ps -a
停止、启动、重启容器
docker stop my-nginx # 停止
docker start my-nginx # 启动
docker restart my-nginx # 重启
删除容器
docker rm my-nginx # 删除已停止的容器
docker rm -f my-nginx # 强制删除(即使还在运行)
查看容器日志
出问题了第一件事就是看日志:
docker logs my-nginx # 查看全部日志
docker logs -f my-nginx # 实时跟踪日志(类似 tail -f)
docker logs --tail 100 my-nginx # 只看最后 100 行
进入容器内部
有时候你需要进到容器里面看看:
docker exec -it my-nginx /bin/bash # 进入容器的 bash
docker exec -it my-nginx sh # 如果没有 bash,用 sh
进去之后就像登录了一台新的 Linux 一样,可以执行各种命令。输入 exit 退出。
查看容器资源占用
docker stats
实时显示每个容器的 CPU、内存、网络、磁盘使用情况。
数据持久化:Volume
这是新手最容易踩的坑:容器里的数据默认不保存,容器一删数据就没了。
为什么需要 Volume
容器的设计理念是"用完即弃”。你可以随时删除容器再用同一个镜像创建一个新的。但数据库里的数据、用户上传的文件这些不能丢啊。
Volume 就是把容器里的某个目录映射到宿主机上,这样即使容器删了,数据还在宿主机上。
使用 Volume
# 命名卷(Docker 管理)
docker run -d -p 3306:3306 \
-v mysql_data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name my-mysql \
mysql:8.0
# 绑定挂载(指定宿主机目录)
docker run -d -p 8080:80 \
-v /home/xyf/website:/usr/share/nginx/html \
--name my-nginx \
nginx
命名卷由 Docker 管理,存在 Docker 的数据目录里。绑定挂载是你自己指定宿主机的哪个目录。
管理 Volume
docker volume ls # 列出所有卷
docker volume inspect my_vol # 查看卷详情
docker volume rm my_vol # 删除卷
docker volume prune # 清理没用的卷
网络:容器之间怎么通信
端口映射
前面已经说了,-p 参数做端口映射:
-p 8080:80 # 宿主机 8080 → 容器 80
-p 80:80 # 宿主机 80 → 容器 80
-p 127.0.0.1:8080:80 # 只允许本机访问
容器间通信
同一个 Docker 网络里的容器可以用容器名互相访问:
# 创建网络
docker network create mynet
# 在同一个网络里启动两个容器
docker run -d --name web --network mynet nginx
docker run -d --name db --network mynet mysql:8.0
这样 web 容器里可以直接用 db 作为主机名访问 MySQL 容器,不需要知道 IP 地址。
Dockerfile:构建自己的镜像
你可能会问,那些镜像是谁做的?大部分是软件官方做的。但有时候你需要把自己的代码打包成镜像。
基本结构
创建一个叫 Dockerfile 的文件(没有后缀名):
# 基础镜像
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制代码
COPY . .
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["python", "app.py"]
构建镜像
docker build -t myapp:1.0 .
-t myapp:1.0:给镜像起名字和版本号.:Dockerfile 所在的目录(当前目录)
常用指令
FROM python:3.11 # 基础镜像
WORKDIR /app # 设置工作目录
COPY . . # 复制文件到容器里
RUN pip install -r requirements.txt # 构建时执行的命令
ENV APP_ENV=production # 设置环境变量
EXPOSE 8000 # 声明端口(文档作用,不实际开放)
CMD ["python", "app.py"] # 容器启动时执行的命令
ENTRYPOINT ["python"] # 入口点,CMD 作为参数追加
.dockerignore 文件
和 .gitignore 类似,告诉 Docker 哪些文件不要复制到镜像里:
.git
node_modules
__pycache__
*.pyc
.env
多阶段构建:让镜像更小
# 构建阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# 运行阶段
FROM alpine:latest
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
这样最终镜像只有几 MB,而不是几百 MB。
Docker Compose:多容器编排
单个容器跑起来不难,但实际用的时候一般需要好几个服务配合。比如搭个网站可能需要:Web 服务器 + 数据库 + 缓存 + 后端 API。
一个一个手动启动太麻烦了,Docker Compose 就是干这个的——用一个配置文件把所有容器的启动方式写好,一条命令全部启动。
安装
Docker Desktop 自带 Docker Compose。Linux 上需要单独安装:
sudo apt install docker-compose-plugin
验证:
docker compose version
配置文件
创建一个 docker-compose.yml 文件:
services:
web:
image: nginx:1.24
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- api
api:
build: ./api
ports:
- "8000:8000"
environment:
- DATABASE_URL=mysql://root:123456@db:3306/myapp
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: myapp
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
常用命令
# 启动所有服务(后台运行)
docker compose up -d
# 查看状态
docker compose ps
# 查看日志
docker compose logs -f
# 停止所有服务
docker compose down
# 停止并删除卷(数据会丢!)
docker compose down -v
# 重新构建并启动
docker compose up -d --build
# 进入某个容器
docker compose exec api bash
实际案例:一键搭建 WordPress
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: 123456
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: wordpress
volumes:
- db_data:/var/lib/mysql
volumes:
wp_data:
db_data:
docker compose up -d,打开 http://localhost:8080,就能看到 WordPress 的安装界面了。
常见问题和踩坑记录
1. 容器启动就退出了
docker ps -a # 看看退出码
docker logs 容器名 # 看日志找原因
最常见的原因是主进程结束了。Docker 容器的生命和主进程绑定,主进程一结束容器就停了。确保你的应用在前台运行,不要用 & 后台执行。
2. 端口被占用
Error: bind: address already in use
说明宿主机上已经有东西占了这个端口。换个端口映射就行,或者先停掉占用端口的服务。
3. 容器内无法访问外网
可能是 DNS 问题。试试:
docker run --dns 223.5.5.5 alpine ping google.com
4. 镜像太大
用多阶段构建减小镜像体积(见上面 Dockerfile 章节)。
5. 数据丢失
再说一遍:容器删了数据就没了。 重要的数据一定要用 Volume 挂载出来。
6. 时区问题
容器默认是 UTC 时区。设置中国时区:
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime
或者在 docker-compose.yml 里:
environment:
- TZ=Asia/Shanghai
7. 权限问题
挂载的目录可能会有权限问题:
# 查看容器内用户
docker exec -it my-nginx id
# 修改挂载目录权限
chmod -R 755 /home/xyf/website
一些实用技巧
清理空间
Docker 用久了会占用很多磁盘空间:
docker system df # 查看占用
docker system prune # 清理停止的容器、没用的网络、悬空镜像
docker system prune -a # 清理所有没被使用的镜像(慎用)
导出和导入镜像
没有网络的环境下很有用:
docker save myapp:1.0 > myapp.tar # 导出
docker load < myapp.tar # 导入
限制容器资源
docker run -d --memory=512m --cpus=1.5 nginx
限制容器最多用 512MB 内存和 1.5 个 CPU 核心。在共享服务器上很有用,防止某个容器把资源吃光。
查看容器的详细信息
docker inspect my-nginx
返回 JSON 格式的详细信息,包括网络配置、挂载点、环境变量等。
最后
Docker 这东西,入门真的不难。你不需要把所有概念都搞懂才能用,先跑起来,遇到问题再查。
我现在的习惯是,能用 Docker 跑的东西尽量用 Docker。换电脑、换服务器的时候直接把 docker-compose.yml 一拷,环境就全好了,再也不用从零开始配环境了。
如果你也被环境配置折磨过,试试 Docker 吧,真的能省不少事。
写给和我一样被环境配置搞崩溃过的朋友们。