☰
Docker容器化实战:从安装配置到多容器编排部署完整指南
2026/10/14 16:23:05 网站建设 项目流程

1. 为什么现在还要聊Docker

如果你最近两年才入行,可能觉得容器化已经是天经地义的事——新项目上来就是Dockerfile,本地跑通了直接推到服务器,中间几乎不用操心环境问题。但往前倒五六年,部署一个Web应用是什么体验?装Python、装数据库、配Nginx、调环境变量、处理各种依赖冲突,一套流程走下来半天没了,换台机器还得重来一遍。Docker解决的正是这个“在我机器上明明能跑”的经典难题。

这篇文章不是官方文档的搬运,而是把我自己从零开始接触Docker、踩坑、到最终把几个项目稳定跑在容器里的完整过程梳理出来。内容会覆盖Docker到底是什么、核心概念怎么理解、安装配置怎么做、以及如何把一个真实的应用打包部署上去。适合两类人看:一类是刚接触容器技术、想快速上手的新手;另一类是用过Docker但一直停留在docker run层面、想系统理解背后逻辑的开发者。我会尽量用生活化的类比来解释概念,同时给出可以直接复制执行的命令和配置,让你看完就能动手。

2. Docker到底是什么,和虚拟机有什么区别

2.1 从“集装箱”这个比喻说起

Docker的Logo是一条鲸鱼驮着一堆集装箱,这个比喻其实非常精准。在没有集装箱之前,货物装卸靠人力,不同形状的货物堆在一起,装船效率极低。集装箱出现后,不管里面装的是衣服还是电子产品,外面尺寸统一,吊装、堆放、运输全部标准化。Docker做的事情类似:它把你的应用和它需要的一切(代码、运行时、系统工具、库、配置)打包成一个标准化的“容器”,这个容器可以在任何安装了Docker的机器上运行,里面的东西完全一致。

这里的关键词是标准化和隔离。标准化保证了可移植性,隔离保证了不同应用之间互不干扰。你可以在同一台机器上跑一个Python 3.8的应用和一个Python 3.12的应用,它们各自带着自己的依赖,互不影响。

2.2 容器和虚拟机的本质区别

很多人第一次接触Docker会问:这不就是虚拟机吗?两者确实都是隔离环境,但实现层次完全不同。

虚拟机是在硬件层面做虚拟化。它通过Hypervisor(比如VMware、KVM)模拟出一整套硬件,然后在上面装一个完整的操作系统。每个虚拟机都有自己的内核、自己的系统进程,资源开销很大。启动一个虚拟机通常要几十秒甚至几分钟,占用的内存也是GB级别。

容器是在操作系统层面做隔离。所有容器共享宿主机的内核,Docker利用Linux的Namespace做资源隔离、Cgroups做资源限制。容器里没有独立的操作系统内核,只有一个精简的用户空间。所以容器启动是秒级甚至毫秒级的,占用内存通常只有几十MB。

对比维度虚拟机Docker容器
隔离级别硬件级虚拟化操作系统级隔离
启动速度分钟级秒级/毫秒级
资源开销高,每台独占内存和CPU低,共享内核,按需分配
镜像大小GB级别通常几十到几百MB
隔离程度完全隔离进程级隔离,共享内核
适用场景需要不同操作系统的场景微服务、快速部署、CI/CD

理解这个区别很重要,因为它决定了你什么时候该用Docker、什么时候不该用。如果你需要在一个Linux主机上跑Windows应用,那还是得用虚拟机。但如果你只是想把一个Web服务快速部署到多台服务器上,Docker是更轻、更快的选择。

2.3 三个核心概念:镜像、容器、仓库

Docker的世界里有三个必须搞清楚的概念,我用做菜来类比。

镜像(Image)就像一份菜谱。它是一份只读的模板,里面写清楚了这道菜需要什么材料、按什么步骤做。镜像本身不能吃,但你可以根据它做出无数份菜。技术上,镜像是一个分层存储的文件系统,每一层代表一次修改操作。

容器(Container)就是按照菜谱实际做出来的那道菜。它是镜像的运行实例,可以被启动、停止、删除。每个容器都是独立的,你在一个容器里加盐,不会影响另一个容器。容器在镜像之上加了一个可写层,所有运行时的修改都写在这一层。

仓库(Registry)是存放菜谱的图书馆。最常用的是Docker Hub,上面有大量官方和社区维护的镜像。你可以从仓库拉取镜像,也可以把自己的镜像推上去。企业内网通常会搭建私有仓库,比如Harbor或Registry。

这三者的关系是:从仓库拉取镜像,运行镜像产生容器,容器里的修改可以通过commit保存为新镜像,再推回仓库。日常工作中,我们更多是通过Dockerfile来构建镜像,而不是手动commit。

3. 安装Docker:不同系统的实操步骤

3.1 Linux上的安装(以主流发行版为例)

Linux是Docker的原生平台,安装最顺畅。我以Ubuntu和CentOS这两个最常见的发行版为例,其他发行版思路类似。

Ubuntu上的安装:

先卸载可能存在的旧版本,避免冲突:

sudo apt-get remove docker docker-engine docker.io containerd runc

然后安装依赖包,添加Docker的官方软件源。这里注意,不同Ubuntu版本代号不同,需要先确认:

sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

更新源并安装:

sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

安装完成后验证:

sudo docker run hello-world

如果看到“Hello from Docker!”的输出,说明安装成功。

CentOS上的安装:

CentOS 7和CentOS 8的步骤略有差异,但核心逻辑一样。先安装yum-utils,添加仓库:

sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

然后安装:

sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin

启动并设置开机自启:

sudo systemctl start docker sudo systemctl enable docker

注意:CentOS 8已经停止维护,官方源可能有问题,建议用CentOS 7或者迁移到其他发行版。如果遇到源不可用,可以换成阿里云或清华的镜像源。

3.2 免sudo使用Docker的配置

默认情况下,只有root用户和docker组的成员才能操作Docker。每次敲命令都要加sudo很烦,解决办法是把当前用户加入docker组:

sudo groupadd docker sudo usermod -aG docker $USER

执行完后需要退出终端重新登录,或者执行newgrp docker让组变更生效。验证一下:

docker run hello-world

不加sudo也能跑,说明配置成功。

这里有个安全提醒:docker组实际上等同于root权限,因为容器可以挂载宿主机的任意目录。所以在生产服务器上,不要把无关用户加进docker组。

3.3 配置国内镜像加速器

从Docker Hub拉取镜像在国内网络环境下可能很慢,配置镜像加速器是必做的一步。编辑/etc/docker/daemon.json:

{ "registry-mirrors": [ "https://mirror.ccs.tencentyun.com", "https://docker.mirrors.ustc.edu.cn" ] }

然后重启Docker服务:

sudo systemctl daemon-reload sudo systemctl restart docker

验证加速器是否生效:

docker info

在输出里找到Registry Mirrors字段,看看配置的地址是否出现。

实操心得:镜像加速器地址不是一成不变的,有些会失效。如果发现拉取镜像还是很慢,可以多试几个地址,或者用docker pull时指定具体的镜像仓库地址。另外,企业内网通常有自己的私有仓库,配置方式类似,把地址换成内网仓库即可。

4. Docker核心操作:从拉取镜像到运行容器

4.1 镜像管理的常用命令

安装完Docker后,第一件事通常是拉取一个镜像。以Nginx为例:

docker pull nginx

不加标签默认拉取latest,也就是最新版。但生产环境不建议用latest,因为版本会变,可能导致不可预期的行为。最好指定具体版本:

docker pull nginx:1.25.3

查看本地已有的镜像:

docker images

输出会显示仓库名、标签、镜像ID、创建时间和大小。删除镜像用docker rmi,可以跟镜像ID或名称加标签:

docker rmi nginx:1.25.3

如果镜像正在被容器使用,需要先删除容器再删镜像,或者加-f强制删除。

4.2 容器的生命周期管理

运行一个Nginx容器:

docker run -d -p 8080:80 --name my-nginx nginx:1.25.3

这条命令拆开看:-d表示后台运行,-p 8080:80把宿主机的8080端口映射到容器的80端口,--name给容器起个名字方便管理,最后是镜像名和标签。

查看运行中的容器:

docker ps

加上-a可以看到所有容器,包括已停止的。

停止、启动、重启容器:

docker stop my-nginx docker start my-nginx docker restart my-nginx

删除容器:

docker rm my-nginx

如果容器还在运行,需要先停止,或者用-f强制删除。

进入正在运行的容器内部:

docker exec -it my-nginx /bin/bash

这个命令非常常用,-it表示交互式终端,后面跟容器名和要执行的命令。进去之后可以查看日志、修改配置、调试问题。退出用exit。

注意事项:用docker exec进入容器后做的修改,在容器删除后会丢失。如果需要持久化,应该通过挂载卷或者构建新镜像的方式。另外,不是所有镜像都有bash,有些精简镜像只有sh,这时候把/bin/bash换成/bin/sh。

4.3 数据卷:让数据活得更久

容器本身是无状态的,删除容器后里面的数据就没了。但数据库、上传文件这些需要持久化,这时候就要用数据卷。

最简单的挂载方式是把宿主机的目录映射到容器里:

docker run -d -p 3306:3306 -v /data/mysql:/var/lib/mysql --name mysql-db -e MYSQL_ROOT_PASSWORD=yourpassword mysql:8.0

-v /data/mysql:/var/lib/mysql把宿主机的/data/mysql目录挂载到容器的/var/lib/mysql。这样MySQL的数据文件就存在宿主机上,容器删了数据还在。

另一种方式是使用命名卷,由Docker管理存储位置:

docker volume create mysql-data docker run -d -v mysql-data:/var/lib/mysql --name mysql-db mysql:8.0

命名卷的好处是Docker会统一管理,备份和迁移更方便。查看所有卷:

docker volume ls

实操心得:挂载宿主机目录时,如果宿主机目录不存在,Docker会自动创建,但权限可能不对。比如MySQL容器需要写权限,如果宿主机目录是root所有,容器里的mysql用户可能写不了。解决办法是提前创建目录并设置合适的权限,或者用命名卷让Docker自己处理。

5. Dockerfile:把应用打包成镜像

5.1 Dockerfile的基本结构

手动commit容器来制作镜像不是好习惯,因为过程不可追溯、不可重复。正确做法是写Dockerfile,用声明式的方式描述镜像怎么构建。

一个典型的Python Web应用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"]

逐行解释:FROM指定基础镜像,WORKDIR设置工作目录,COPY把文件复制进镜像,RUN在构建时执行命令,EXPOSE声明容器监听的端口(只是声明,实际映射靠-p),CMD指定容器启动时执行的命令。

5.2 构建镜像的实操过程

在Dockerfile所在目录执行:

docker build -t my-app:1.0 .

-t指定镜像名和标签,最后的.表示构建上下文是当前目录。构建过程中Docker会按顺序执行每条指令,每条指令生成一层。如果某条指令失败,构建会中断,但之前的层会缓存下来,下次构建时如果前面的指令没变,会直接复用缓存,速度很快。

查看构建好的镜像:

docker images | grep my-app

运行这个镜像:

docker run -d -p 8000:8000 --name my-app-container my-app:1.0

5.3 多阶段构建:让镜像更小

直接构建的镜像往往包含编译工具、测试文件等运行时不需要的东西,导致镜像臃肿。多阶段构建可以解决这个问题。

以Go应用为例:

# 构建阶段 FROM golang:1.21 AS builder WORKDIR /build COPY . . RUN CGO_ENABLED=0 go build -o myapp . # 运行阶段 FROM alpine:3.18 WORKDIR /app COPY --from=builder /build/myapp . EXPOSE 8080 CMD ["./myapp"]

第一个阶段用完整的Go镜像编译出二进制文件,第二个阶段只复制编译好的二进制到一个精简的Alpine镜像里。最终镜像可能只有几十MB,而不是几百MB。

注意事项:多阶段构建时,COPY --from=builder的路径要写对。另外,Alpine用的是musl libc而不是glibc,如果程序依赖glibc,需要静态编译或者换用debian-slim作为运行阶段的基础镜像。

6. Docker Compose:多容器应用编排

6.1 为什么需要Compose

一个真实的Web应用通常不止一个容器:Web服务、数据库、缓存、消息队列,可能还有Nginx做反向代理。如果每个容器都手动docker run,命令又长又容易出错,而且容器之间的网络连接、启动顺序都要手动处理。Docker Compose用一个YAML文件描述所有服务,一条命令全部启动。

6.2 编写docker-compose.yml

一个典型的Web应用加数据库的配置:

version: '3.8' services: web: build: . ports: - "8000:8000" environment: - DATABASE_URL=postgresql://user:pass@db:5432/mydb depends_on: - db volumes: - ./app:/app db: image: postgres:15 environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:

这里定义了两个服务:web和db。web用当前目录的Dockerfile构建,db用官方Postgres镜像。depends_on保证db先启动,但注意它只保证启动顺序,不保证db已经准备好接受连接。environment设置环境变量,web服务里用db作为主机名连接数据库,因为Compose会自动创建一个网络,服务之间可以用服务名互相访问。

6.3 Compose的常用操作

启动所有服务:

docker compose up -d

注意新版本是docker compose(中间有空格),老版本是docker-compose(中间有横线)。查看服务状态:

docker compose ps

查看某个服务的日志:

docker compose logs -f web

停止并删除所有容器:

docker compose down

如果还想删除数据卷,加-v:

docker compose down -v

实操心得:depends_on不保证服务就绪,如果web启动时db还没准备好,连接会失败。解决办法是在应用里加重试逻辑,或者用健康检查配合condition: service_healthy。健康检查的配置是在服务下加healthcheck字段,指定检测命令和间隔。

7. 常见问题与排查技巧实录

7.1 容器启动就退出

这是新手最常遇到的问题。docker run之后docker ps看不到容器,docker ps -a显示状态是Exited。原因通常是容器的主进程执行完就结束了。容器不是虚拟机,它需要一个前台进程持续运行,如果主进程退出,容器就退出。

排查方法:先看日志。

docker logs <容器ID或名称>

日志里通常会显示错误信息。如果是命令执行完就退出,比如docker run ubuntu,因为ubuntu镜像默认命令是bash,没有终端交互就退出了。解决办法是让容器跑一个持续运行的进程,比如docker run -d ubuntu sleep infinity。

7.2 端口被占用

启动容器时报错:

Error response from daemon: driver failed programming external connectivity: Bind for 0.0.0.0:8080 failed: port is already allocated

说明宿主机的8080端口已经被占用了。查看谁占用了:

sudo lsof -i :8080 # 或者 sudo netstat -tlnp | grep 8080

要么停掉占用端口的进程,要么换一个宿主机端口映射,比如-p 8081:80。

7.3 容器内无法访问外网

有时候容器里ping外网不通,或者apt-get update失败。先检查宿主机的网络转发是否开启:

sysctl net.ipv4.ip_forward

如果是0,需要开启:

sudo sysctl -w net.ipv4.ip_forward=1

还要检查防火墙规则,有些防火墙会阻止Docker的NAT转发。另外,如果Docker的默认网段和宿主机所在网段冲突,也会导致网络异常,可以通过修改/etc/docker/daemon.json里的bip字段来调整。

7.4 镜像构建缓存导致代码没更新

Dockerfile里COPY . .这一行,如果前面的指令没变,Docker会复用缓存,导致新代码没被复制进去。解决办法是在构建时加--no-cache:

docker build --no-cache -t my-app:1.0 .

但这样每次都要重新安装依赖,很慢。更好的做法是优化Dockerfile的层顺序,把不常变的部分放前面,常变的部分放后面。比如先COPY requirements.txt并安装依赖,再COPY源代码。

7.5 常见问题速查表

问题现象可能原因排查命令解决方案
容器启动即退出主进程执行完毕docker logs <容器>让主进程前台运行
端口冲突宿主机端口被占用lsof -i :端口换端口或停占用进程
容器无外网转发未开启或防火墙sysctl net.ipv4.ip_forward开启转发,检查防火墙
代码未更新构建缓存docker build --no-cache优化层顺序或禁用缓存
数据丢失未挂载卷docker inspect <容器>配置数据卷挂载
权限拒绝用户不在docker组groups加入docker组并重登

独家避坑技巧:遇到问题时,docker inspect是你的好朋友。它能输出容器的完整配置,包括网络、挂载、环境变量、状态等。配合docker logs和docker exec,大部分问题都能定位。另外,养成看容器日志的习惯,很多错误在日志里写得清清楚楚,比瞎猜快得多。

8. 把应用真正部署上去:一个完整的流程

8.1 从本地到服务器的完整链路

假设你在本地开发了一个Web应用,现在要部署到一台云服务器上。完整流程是这样的:

第一步,在本地写好Dockerfile和docker-compose.yml,确保docker compose up能正常跑起来。第二步,把代码推送到代码仓库。第三步,登录服务器,拉取代码。第四步,在服务器上执行docker compose up -d。第五步,配置Nginx反向代理或者直接开放端口。第六步,验证服务是否正常。

如果每次更新都要手动登录服务器拉代码,效率太低。更好的做法是用CI/CD工具自动化这个过程。代码推送到仓库后,自动构建镜像、推送到镜像仓库、然后在服务器上拉取新镜像并重启容器。

8.2 生产环境的注意事项

生产环境和本地开发有几个关键区别。

日志管理:本地开发看终端输出就行,生产环境需要把日志收集起来。Docker默认把日志写到JSON文件里,可以用docker logs查看,但长期存储需要配置日志驱动,把日志发送到集中式日志系统。

资源限制:生产环境必须限制容器能使用的CPU和内存,防止某个容器把整台机器拖垮。在Compose里可以配置:

services: web: deploy: resources: limits: cpus: '1.0' memory: 512M

重启策略:服务器重启或者容器意外退出时,需要自动重启。配置restart: always或者restart: unless-stopped。

镜像标签:生产环境绝对不要用latest,每次构建用具体的版本号或者git commit hash作为标签,方便回滚。

安全加固:容器默认以root运行,生产环境应该创建非root用户。在Dockerfile里加:

RUN useradd -m appuser USER appuser

8.3 一次真实的部署踩坑记录

我之前部署一个Python应用时遇到一个问题:本地跑得好好的,服务器上启动就报数据库连接失败。排查了半天,发现是Compose里depends_on只保证db容器先启动,但Postgres初始化需要几秒钟,web容器启动太快,连不上就退出了。

解决办法有两个:一是在应用里加连接重试逻辑,二是用健康检查。我选了后者,在db服务下加:

healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5

然后在web服务里把depends_on改成:

depends_on: db: condition: service_healthy

这样web会等db健康检查通过后才启动,问题解决。

另一个坑是时区。容器默认用UTC时间,应用里记录的时间比北京时间少8小时。解决办法是在Dockerfile里设置时区:

ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

或者在Compose里挂载宿主机的时区文件:

volumes: - /etc/localtime:/etc/localtime:ro

这些都是文档里不会特意写、但实际部署一定会遇到的问题。踩过一次就记住了。

9. 镜像体积优化与构建速度提升

9.1 选择合适的基础镜像

基础镜像的选择直接决定最终镜像的大小。同样是Python环境,python:3.11完整版可能有1GB,python:3.11-slim只有150MB左右,python:3.11-alpine更小,大概50MB。但Alpine用的是musl libc,某些依赖glibc的包可能装不上或者运行异常。

我的经验是:优先用slim版本,除非有特殊需求。Alpine适合对体积极度敏感的场景,但要做好踩坑的准备。如果应用依赖numpy、pandas这类科学计算库,Alpine上编译会很痛苦,直接用slim更省心。

9.2 减少镜像层数

Dockerfile里每条RUN、COPY、ADD指令都会生成一层。层数越多,镜像越大,构建也越慢。把多个RUN命令用&&连接起来:

RUN apt-get update && \ apt-get install -y --no-install-recommends \ gcc \ libpq-dev \ && rm -rf /var/lib/apt/lists/*

注意最后清理apt缓存,否则缓存文件也会被打进镜像里,白白增加几十MB。

9.3 利用构建缓存加速

Docker构建时会逐层检查缓存,如果某一层的内容没变,就直接复用。所以Dockerfile的指令顺序很关键:把不常变的部分放前面,常变的部分放后面。

比如Node.js应用,package.json不常变,源代码经常变。正确的顺序是:

COPY package.json package-lock.json ./ RUN npm ci COPY . .

这样只要package.json没改,npm ci这一层就一直用缓存,只有COPY . .之后的层需要重新构建。如果反过来先COPY . .再npm ci,那每次改代码都会导致依赖重新安装,构建时间从几秒变成几分钟。

9.4 使用.dockerignore

和.gitignore类似,.dockerignore文件告诉Docker在构建时忽略哪些文件。如果不配置,COPY . .会把node_modules、.git、日志文件等全部复制进镜像,导致镜像巨大、构建缓慢。

一个典型的.dockerignore:

.git node_modules __pycache__ *.pyc .env *.log .vscode .idea

实操心得:.dockerignore经常被忽略,但它对构建速度和镜像大小的影响非常大。我见过一个项目因为没配.dockerignore,把整个.git目录和node_modules打进了镜像,镜像从200MB膨胀到1.5GB。养成习惯,创建Dockerfile的同时就创建.dockerignore。

10. 容器网络:理解Docker的网络模型

10.1 默认网络模式

Docker安装后会创建三种默认网络:bridge、host、none。

bridge是默认模式。每个容器分配一个独立的IP,通过Docker创建的虚拟网桥和宿主机通信。容器之间可以通过IP互相访问,但需要端口映射才能从外部访问。Compose创建的网络也是bridge类型,但提供了服务名DNS解析,所以容器之间可以用服务名通信。

host模式让容器直接使用宿主机的网络栈,没有隔离。容器里监听的端口就是宿主机的端口,不需要-p映射。性能最好,但失去了网络隔离,端口冲突的风险也高。

none模式给容器一个只有loopback的网络,完全隔离。适合不需要网络的批处理任务。

10.2 自定义网络的好处

默认的bridge网络里,容器之间只能用IP互相访问,IP在容器重启后可能变化,很不方便。自定义网络提供了自动DNS解析,容器可以用名字互相访问:

docker network create my-network docker run -d --name app --network my-network my-app docker run -d --name db --network my-network postgres

这样app容器里可以直接用db作为主机名连接数据库,不用关心IP是多少。

10.3 端口映射的几种写法

-p参数的写法有几种,含义不同:

  • -p 8080:80:把宿主机所有网卡的8080端口映射到容器的80端口
  • -p 127.0.0.1:8080:80:只映射到宿主机的loopback地址,外部无法访问
  • -p 8080:80/udp:映射UDP端口
  • -P:随机映射容器暴露的所有端口到宿主机的高端口

生产环境如果前面有Nginx做反向代理,通常只需要映射到127.0.0.1,让Nginx转发,避免直接暴露容器端口。

11. 数据持久化与备份策略

11.1 三种挂载方式的区别

Docker支持三种挂载方式:绑定挂载(bind mount)、命名卷(volume)、临时文件系统(tmpfs)。

绑定挂载直接把宿主机目录映射到容器,路径必须存在,权限由宿主机控制。适合开发环境,因为改代码后容器里立即生效。

命名卷由Docker管理,存储在/var/lib/docker/volumes/下。适合生产环境的数据持久化,因为Docker会处理权限和生命周期。

tmpfs挂载在内存里,容器停止后数据消失。适合存储敏感信息或者临时缓存。

11.2 数据库容器的备份实操

以MySQL为例,备份数据有两种方式。

第一种是用mysqldump导出:

docker exec mysql-db mysqldump -u root -p'password' mydb > backup.sql

第二种是直接备份数据卷。先找到卷的挂载点:

docker volume inspect mysql-data

输出里的Mountpoint就是宿主机上的实际路径。停掉容器后,直接打包这个目录:

docker stop mysql-db sudo tar -czf mysql-backup.tar.gz /var/lib/docker/volumes/mysql-data/_data docker start mysql-db

恢复时把备份解压回原路径即可。

注意事项:直接备份数据卷时一定要先停容器,否则可能备份到不一致的数据。用mysqldump则不需要停容器,但导出期间最好避免大量写入。生产环境建议用专业的备份工具,或者配置主从复制做实时备份。

12. 从单机到多机:Docker的边界在哪里

Docker本身是单机工具,它管理的是同一台机器上的容器。当应用规模变大,需要多台机器协同工作时,Docker就力不从心了。这时候需要更上层的编排工具,比如Kubernetes或者Docker Swarm。

但这不意味着Docker不重要。恰恰相反,Kubernetes调度的最小单位就是容器,底层用的还是Docker(或者containerd)那套东西。理解了Docker的镜像、容器、网络、存储,再学Kubernetes会顺畅很多。

对于中小规模的应用,单机Docker加Compose完全够用。一台配置不错的服务器,跑十几个容器轻轻松松。等到真的需要多机编排了,再上Kubernetes也不迟。技术选型要看实际需求,不要为了用而用。

我个人在实际操作中的体会是,Docker最大的价值不是技术本身有多高深,而是它改变了部署的思维方式。以前部署是“登录服务器、装环境、传代码、配服务”,现在是“构建镜像、推送、拉取、运行”。整个过程标准化、可重复、可回滚。一旦习惯了这种方式,就再也回不去了。最后分享一个小技巧:如果你在本地开发时经常需要重建环境,可以写一个Makefile或者shell脚本,把常用的docker compose命令封装起来,比如make up、make down、make logs,能省不少敲键盘的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询