Docker核心概念与实战指南
Docker容器技术从入门到实践
引言
在现代软件开发和部署中,Docker已经成为一个不可或缺的工具。无论你是前端开发者、后端工程师还是DevOps专家,掌握Docker都能极大地提升你的开发效率和部署流程。本文将从基础安装到核心概念,再到实战演练,带你全面掌握Docker技术。
Docker概述
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows机器上。Docker容器在任何支持的系统上都能以完全相同的方式运行,这解决了经典的"在我的机器上能跑"的问题。
Docker的核心优势
- 一致的运行环境:消除了"在我机器上可以运行"的问题
- 轻量级:容器共享操作系统内核,比虚拟机更高效
- 快速部署:秒级启动容器
- 版本控制:类似于代码的版本控制,可以追踪容器镜像的变更
- 隔离性:应用程序及其依赖在容器内运行,不会相互干扰
Docker安装指南
Linux安装Docker
在大多数Linux发行版上,可以通过包管理器安装Docker:
# Ubuntu/Debian
sudo apt update
sudo apt install docker.io
sudo systemctl enable --now docker
# CentOS
sudo yum install -y docker
sudo systemctl enable --now docker
macOS安装Docker
macOS上安装Docker最简单的方法是使用Docker Desktop:
- 访问Docker官网下载Docker Desktop
- 双击下载的.dmg文件,将Docker拖到Applications文件夹
- 打开Docker应用程序,等待Docker启动完成
Windows安装Docker
Windows上也使用Docker Desktop:
- 确保你的Windows系统支持Hyper-V或WSL 2
- 从官网下载Docker Desktop
- 运行安装程序,按照向导完成安装
- 安装完成后启动Docker Desktop
验证安装
安装完成后,打开终端运行:
docker --version
docker run hello-world
如果一切正常,hello-world
容器会成功运行并输出确认消息。
Docker核心概念
1. Docker镜像(Image)
重要概念
Docker镜像是Docker容器的基础,它是一个只读的模板,包含了运行容器所需的文件系统结构和内容。
镜像由多个层(Layer)组成,每层都是前一层变化的增量。这种分层结构使得镜像可以共享基础层,节省存储空间和传输时间。
基本操作:
# 搜索镜像
docker search nginx
# 拉取镜像
docker pull nginx:latest
# 列出本地镜像
docker images
# 删除镜像
docker rmi nginx:latest
2. Docker容器(Container)
重要概念
容器是镜像的运行实例,可以被创建、启动、停止、删除和暂停。
容器在镜像的基础上添加了一个可写层,所有对容器的更改都保存在这个层中。
基本操作:
# 创建并启动容器
docker run -d -p 80:80 --name my-nginx nginx
# 列出运行中的容器
docker ps
# 列出所有容器(包括已停止的)
docker ps -a
# 停止容器
docker stop my-nginx
# 启动已停止的容器
docker start my-nginx
# 进入容器内部
docker exec -it my-nginx bash
# 删除容器
docker rm my-nginx
3. Docker仓库(Registry)
Docker Registry是用于存储和分发Docker镜像的服务。Docker Hub是Docker官方维护的公共仓库,包含了大量的官方镜像和社区镜像。
# 登录Docker Hub
docker login
# 为镜像添加标签
docker tag my-app:latest username/my-app:latest
# 推送镜像到Docker Hub
docker push username/my-app:latest
企业通常会搭建私有Registry,用于存储内部使用的镜像。
Dockerfile详解
重要概念
Dockerfile是用于构建Docker镜像的文本文件,包含了一系列的指令和参数,用来定义镜像的构建过程和内容。
Dockerfile基本语法
Dockerfile由一系列指令和参数组成,每条指令都会在镜像中创建一个新的层。下面是一个基本的Dockerfile示例:
# 基础镜像
FROM node:14-alpine
# 设置工作目录
WORKDIR /app
# 复制package.json和package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install
# 复制所有源代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "app.js"]
常用Dockerfile指令详解
FROM
指定基础镜像,所有构建都必须从一个基础镜像开始:
FROM node:14-alpine # 指定Node.js 14版本的Alpine变种
FROM ubuntu:20.04 # 指定Ubuntu 20.04
FROM scratch # 空白镜像,用于构建基础镜像
小提示
选择合适的基础镜像非常重要。尽量使用官方镜像,并考虑使用alpine版本减小镜像体积。
WORKDIR
设置工作目录,后续的RUN、CMD、ENTRYPOINT、COPY、ADD指令都会在这个目录下执行:
WORKDIR /app
最佳实践是使用WORKDIR而不是复杂的RUN cd /some/path
命令。
COPY和ADD
复制文件到容器中:
# 复制单个文件
COPY package.json .
# 复制多个文件
COPY package.json package-lock.json ./
# 使用通配符
COPY *.json .
# 指定目标目录
COPY src/ /app/src/
ADD与COPY类似,但ADD有额外功能:
# 可以解压tar文件
ADD app.tar.gz /app/
# 可以从URL下载文件
ADD https://example.com/file.txt /app/
注意事项
通常推荐使用COPY而不是ADD,除非你需要ADD的特殊功能。
RUN
在当前镜像的顶部执行命令并创建新层:
# 单行命令
RUN npm install
# 多行命令(推荐使用反斜杠连接)
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
优化技巧
将多个相关命令合并为一个RUN指令可以减少镜像层数,从而减小镜像体积。
ENV
设置环境变量:
# 单个环境变量
ENV NODE_ENV production
# 多个环境变量
ENV APP_HOME=/app \
APP_VERSION=1.0.0 \
DEBUG=false
环境变量可以在Dockerfile中使用,也可以在容器运行时使用。
EXPOSE
声明容器运行时监听的端口:
EXPOSE 3000 # TCP端口
EXPOSE 3000/tcp # 显式指定TCP
EXPOSE 3000/udp # UDP端口
注意事项
EXPOSE只是声明容器使用的端口,实际上要在容器外访问,还需要在运行时使用-p参数映射端口。
VOLUME
创建挂载点,用于持久化数据或与主机共享数据:
VOLUME /app/data
VOLUME ["/app/data", "/app/logs"]
ARG
定义构建时的变量,可以在构建命令中传入:
ARG NODE_VERSION=14
FROM node:${NODE_VERSION}-alpine
使用ARG构建镜像:
docker build --build-arg NODE_VERSION=16 -t my-node-app .
CMD和ENTRYPOINT
这两个指令用于定义容器启动时执行的命令:
CMD:指定容器启动时的默认命令,可以被docker run命令行参数覆盖:
# exec形式(推荐)
CMD ["node", "app.js"]
# shell形式
CMD node app.js
ENTRYPOINT:配置容器启动时运行的可执行文件,不会被docker run的命令行参数覆盖:
ENTRYPOINT ["node", "app.js"]
CMD和ENTRYPOINT的组合使用:
ENTRYPOINT ["node"]
CMD ["app.js"] # 可被覆盖的参数
运行以上配置的容器:
# 使用默认参数运行 → node app.js
docker run my-app
# 覆盖默认参数 → node other-script.js
docker run my-app other-script.js
多阶段构建详解
多阶段构建是优化Docker镜像大小的强大技术,特别适合编译型语言或前端应用:
# 构建阶段
FROM node:14 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 从构建阶段复制构建结果
COPY --from=builder /app/dist /usr/share/nginx/html
# 可选:复制自定义nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
多阶段构建的优势:
- 最终镜像只包含运行所需的文件
- 不包含构建工具和中间文件
- 大幅减小镜像体积
- 减少潜在的安全漏洞
.dockerignore文件
类似于.gitignore,用于排除不需要复制到Docker镜像中的文件和目录:
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.github
.gitignore
README.md
docker-compose.yml
*.env
使用.dockerignore可以加快构建过程并减小镜像大小。
Docker Compose实战详解
重要概念
Docker Compose是一个用于定义和运行多容器Docker应用的工具。使用YAML文件配置应用的服务,然后通过一个命令创建并启动所有服务。
docker-compose.yml文件结构
一个完整的docker-compose.yml文件通常包含以下主要部分:
version: '3.8' # Compose文件格式版本
services: # 定义所有服务
# 服务1定义
web:
image: nginx:latest # 使用的镜像
build: ./web # 或者从Dockerfile构建
ports:
- "80:80" # 端口映射
volumes:
- ./web:/usr/share/nginx/html # 挂载数据卷
environment:
- NGINX_HOST=example.com # 环境变量
depends_on:
- api # 依赖关系
restart: always # 重启策略
networks:
- frontend # 网络配置
# 服务2定义
api:
build:
context: ./api # 构建上下文
dockerfile: Dockerfile.dev # 自定义Dockerfile路径
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_USER=postgres
volumes:
- ./api:/app # 代码挂载,便于开发
- /app/node_modules # 排除node_modules
depends_on:
- db
networks:
- frontend
- backend
# 服务3定义
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_USER=postgres
- POSTGRES_DB=myapp
networks:
- backend
# 定义所有网络
networks:
frontend:
backend:
# 定义所有数据卷
volumes:
postgres_data:
核心配置项详解
服务定义(services)
每个服务可以使用以下常用配置项:
image: 使用的Docker镜像
image: mongodb:4.4
build: 从Dockerfile构建镜像
# 简单形式
build: ./dir
# 完整形式
build:
context: ./dir # 构建上下文
dockerfile: Dockerfile.dev # 指定Dockerfile
args: # 构建参数
NODE_ENV: development
ports: 端口映射
ports:
- "3000:3000" # 主机端口:容器端口
- "8080" # 容器端口,主机端口自动分配
- "127.0.0.1:8001:8001" # 指定主机IP的端口映射
volumes: 数据卷挂载
volumes:
- ./app:/app # 绑定挂载
- data:/var/lib/mysql # 命名数据卷
- /var/log # 匿名数据卷
- /app/node_modules # 保持容器内目录不被主机覆盖
environment: 环境变量
environment:
- NODE_ENV=production
- DEBUG=false
# 或使用键值对形式
environment:
NODE_ENV: production
DEBUG: false
env_file: 从文件加载环境变量
env_file:
- ./common.env
- ./app.env
depends_on: 服务依赖关系
depends_on:
- db
- redis
restart: 重启策略
restart: no # 不自动重启(默认)
restart: always # 总是重启
restart: on-failure # 非0状态退出时重启
restart: unless-stopped # 除非手动停止,否则总是重启
networks: 网络配置
networks:
- frontend
- backend
网络配置(networks)
Docker Compose允许创建自定义网络:
networks:
frontend:
driver: bridge # 网络驱动类型
backend:
driver: bridge
ipam: # IP地址管理
driver: default
config:
- subnet: 172.28.0.0/16
数据卷配置(volumes)
自定义数据卷配置:
volumes:
db_data:
driver: local
redis_data:
external: true # 使用已存在的外部数据卷
Docker Compose常用命令
# 启动所有服务
docker-compose up
# 后台启动所有服务
docker-compose up -d
# 仅构建服务
docker-compose build
# 启动特定服务
docker-compose up -d web
# 查看服务状态
docker-compose ps
# 查看服务日志
docker-compose logs
# 实时跟踪日志
docker-compose logs -f
# 查看特定服务的日志
docker-compose logs api
# 停止所有服务但不删除容器
docker-compose stop
# 停止并删除所有容器、网络
docker-compose down
# 停止、删除容器、网络,同时删除数据卷
docker-compose down -v
# 执行一次性命令
docker-compose run web npm install
# 在运行中的服务容器中执行命令
docker-compose exec db psql -U postgres
Docker Compose环境变量与扩展配置
使用.env文件
在Compose文件目录创建.env文件,自动加载其中的环境变量:
# .env文件
POSTGRES_VERSION=13
APP_PORT=3000
在docker-compose.yml中使用:
services:
db:
image: postgres:${POSTGRES_VERSION}
web:
ports:
- "${APP_PORT}:3000"
使用多个Compose文件
可以组合多个Compose文件来适应不同环境:
# 基础+开发环境配置
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# 基础+生产环境配置
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
扩展服务实例
水平扩展服务实例:
# 启动3个web服务实例
docker-compose up -d --scale web=3
请注意,使用这种方式扩展服务时,需要确保:
- 服务不会绑定到固定端口(如后端服务不应在compose文件中映射到固定宿主机端口)
- 使用负载均衡器(如Nginx)在多个后端实例间分发请求
容器管理实用技巧
容器生命周期管理
后台运行容器
# 后台运行容器
docker run -d nginx
# 后台运行容器并命名
docker run -d --name my-nginx nginx
# 后台运行且自动重启
docker run -d --restart=always nginx
重启策略选项:
no
:默认策略,不自动重启on-failure[:max-retries]
:非正常退出时重启,可指定最大重试次数always
:总是重启,无论退出状态如何unless-stopped
:除非手动停止,否则总是重启
停止和删除容器
# 停止容器
docker stop my-container
# 强制停止容器
docker kill my-container
# 删除容器
docker rm my-container
# 停止并删除容器
docker rm -f my-container
# 删除所有停止的容器
docker container prune
进入容器和容器交互
进入正在运行的容器
# 进入容器并启动bash终端
docker exec -it my-container bash
# 对于没有bash的容器(如Alpine镜像)
docker exec -it my-container sh
# 以特定用户身份进入容器
docker exec -it -u root my-container bash
查看容器日志
# 查看容器日志
docker logs my-container
# 实时跟踪日志
docker logs -f my-container
# 显示时间戳
docker logs -t my-container
# 显示最近10条日志
docker logs --tail 10 my-container
复制文件
在容器和主机之间复制文件:
# 从主机复制到容器
docker cp ./local-file.txt my-container:/app/
# 从容器复制到主机
docker cp my-container:/app/log.txt ./local-log.txt
文件挂载详解
Docker提供三种主要的挂载方式:
1. 绑定挂载(Bind Mounts)
将主机文件系统的路径直接挂载到容器中:
# 挂载单个文件
docker run -v /host/path/file.conf:/container/path/file.conf nginx
# 挂载目录
docker run -v /host/data:/app/data nodejs-app
# 只读挂载
docker run -v /host/config:/app/config:ro nginx
使用绑定挂载的常见场景:
- 开发环境中挂载源代码
- 配置文件挂载
- 共享数据
2. 数据卷(Volumes)
数据卷是Docker管理的持久化数据存储机制,比绑定挂载更灵活:
# 创建命名数据卷
docker volume create my-data
# 使用数据卷
docker run -v my-data:/app/data mysql
# 匿名数据卷
docker run -v /app/data mysql
数据卷优势:
- 跨容器共享
- 备份和恢复更方便
- 可以使用第三方驱动
- 数据持久性不依赖容器生命周期
数据卷管理命令:
# 列出所有数据卷
docker volume ls
# 检查数据卷详情
docker volume inspect my-data
# 删除数据卷
docker volume rm my-data
# 删除未使用的数据卷
docker volume prune
3. tmpfs挂载
在宿主机内存中创建临时文件系统:
# 创建tmpfs挂载
docker run --tmpfs /app/temp nginx
# 指定tmpfs选项
docker run --tmpfs /app/temp:rw,noexec,nosuid,size=100m nginx
适用场景:
- 存储非持久化的敏感数据
- 高性能临时存储
多容器挂载共享
多个容器可以共享同一个数据卷:
# 创建共享数据卷
docker volume create shared-data
# 容器1使用该数据卷
docker run -v shared-data:/app/data --name container1 nginx
# 容器2使用同一数据卷
docker run -v shared-data:/app/data --name container2 nginx
或在Docker Compose中共享数据卷:
services:
web:
image: nginx
volumes:
- shared-data:/usr/share/nginx/html
backup:
image: backup-service
volumes:
- shared-data:/data/backup
volumes:
shared-data:
挂载实践示例
以下是一个实际的挂载示例,用于开发环境中的Node.js应用:
# 创建项目目录
mkdir -p node-app/src
echo "console.log('Hello from volume');" > node-app/src/app.js
# 运行开发容器,挂载源代码目录
docker run -d \
--name node-dev \
-v $(pwd)/node-app/src:/app/src \
-v node_modules:/app/node_modules \
-p 3000:3000 \
node:14-alpine \
sh -c "cd /app && npm install express && node src/app.js"
这个例子演示了两种常见的挂载模式:
- 绑定挂载用于源代码,便于实时开发
- 命名数据卷用于node_modules,避免本地环境干扰
实战示例:完整的Web应用部署
让我们创建一个完整的实例,包含前端、API服务和数据库的Web应用。
项目结构
web-app/
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── nginx/
│ └── default.conf
├── .env
└── docker-compose.yml
前端Dockerfile (React应用)
# 构建阶段
FROM node:16-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 从构建阶段复制构建产物
COPY --from=build /app/build /usr/share/nginx/html
# 暴露80端口
EXPOSE 80
# 使用默认命令启动nginx
后端Dockerfile (Node.js API)
FROM node:16-alpine
# 创建应用目录
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm install
# 复制源代码
COPY . .
# 暴露API端口
EXPOSE 3000
# 启动命令
CMD ["node", "src/server.js"]
Nginx配置 (用于反向代理)
server {
listen 80;
# 静态文件服务(前端)
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# API反向代理
location /api {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
.env文件
POSTGRES_USER=webuser
POSTGRES_PASSWORD=secret
POSTGRES_DB=webapp
NODE_ENV=production
docker-compose.yml
version: '3.8'
services:
# 前端服务
frontend:
build: ./frontend
restart: always
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- backend
networks:
- app-network
# 后端API服务
backend:
build: ./backend
restart: always
# 在开发中可以映射端口,生产环境中可以只暴露给内部网络
# ports:
# - "3000:3000"
volumes:
- ./backend/uploads:/app/uploads
- backend_logs:/app/logs
environment:
- DB_HOST=database
- DB_USER=${POSTGRES_USER}
- DB_PASSWORD=${POSTGRES_PASSWORD}
- DB_NAME=${POSTGRES_DB}
- NODE_ENV=${NODE_ENV}
depends_on:
- database
networks:
- app-network
# 数据库服务
database:
image: postgres:13-alpine
restart: always
volumes:
- postgres_data:/var/lib/postgresql/data
# 初始化脚本
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
networks:
- app-network
# 定义网络
networks:
app-network:
driver: bridge
# 定义数据卷
volumes:
postgres_data:
backend_logs:
部署和管理应用
# 构建并启动所有服务
docker-compose up -d
# 查看运行状态
docker-compose ps
# 查看日志
docker-compose logs -f
# 进入后端容器执行命令
docker-compose exec backend sh
# 备份数据库
docker-compose exec database pg_dump -U webuser webapp > backup.sql
# 更新应用(重新构建并部署)
docker-compose up -d --build
# 查看资源使用情况
docker stats
# 关闭所有服务
docker-compose down
应用扩缩容
在生产环境中,你可能需要运行多个API实例来处理高流量。使用Docker Swarm或Kubernetes等编排工具可以更好地管理扩缩容,但使用基本的Docker Compose也可以:
# 扩展后端服务到3个实例
docker-compose up -d --scale backend=3
请注意,使用这种方式扩展服务时,需要确保:
- 服务不会绑定到固定端口(如后端服务不应在compose文件中映射到固定宿主机端口)
- 使用负载均衡器(如Nginx)在多个后端实例间分发请求
结语
Docker容器技术已经深刻改变了软件开发、测试和部署的方式。掌握Docker不仅能让你的环境配置更加一致和可靠,还能极大地提高应用部署的效率。
本文介绍了Docker的基础知识和核心概念,通过实际的案例展示了Docker的强大功能。但Docker的生态系统还有更多值得探索的内容,如Docker Swarm、Kubernetes等容器编排工具,以及更多的高级特性和最佳实践。
希望这篇文章能帮助你开始Docker之旅,在实际工作中充分利用Docker带来的便利和效率。