1. 为什么你需要Docker?一个“搬家”的故事

如果你是一个刚开始接触服务器或者应用部署的新手,听到“Docker”这个词可能会觉得有点高大上,甚至有点懵。别担心,我第一次听说的时候也这样。让我用一个你可能每天都在经历的场景来解释它:搬家

想象一下,你开发了一个非常棒的博客网站,它需要运行在特定的环境里,比如需要Python 3.9、一个特定版本的数据库,还有一堆依赖库。现在,你想把这个网站从你自己的电脑(开发环境)搬到云服务器(生产环境)上去。传统的方式,就像搬家时把家具一件件拆了,运到新家再重新组装。你得在服务器上重新安装Python、配置数据库、安装所有依赖,任何一个环节版本不对或者配置出错,你的网站就可能跑不起来,这就是经典的“在我机器上能跑”问题。

Docker做的事情,就是帮你把整个“家”打包。它把你的应用代码、运行环境、系统工具、系统库、配置文件等等,全部打包成一个标准化的“集装箱”,我们称之为镜像。这个镜像在任何安装了Docker引擎的机器上,都能以完全相同的方式运行起来,这个运行起来的实例就叫容器。所以,你搬家的时候,不用再拆家具了,直接把整个打包好的集装箱吊过去,在新地方一放,打开门,里面的一切都和原来一模一样,立刻就能住人。

对于初学者来说,Docker最大的好处就是环境隔离一致性。你再也不用为“缺少某个库”或者“版本冲突”这种问题头疼了。而且,它非常轻量,启动一个容器就像启动一个进程一样快,远比传统的虚拟机要省资源。接下来,我们就从零开始,把这个强大的“搬家工具”装到你的机器上。

2. 手把手安装Docker:告别环境配置噩梦

安装Docker其实比你想象的要简单得多,尤其是现在官方提供了非常便捷的脚本。不过,为了让你理解得更透彻,我们还是会分步骤进行,并解释每一步在做什么。这里我们以最常用的Ubuntu 22.04 LTS系统为例,其他Linux发行版的思路也大同小异。

2.1 清理旧版本与准备环境

在安装新软件之前,最好先清理一下系统里可能存在的旧版本Docker,避免冲突。打开你的终端,依次执行下面的命令。

首先,更新一下系统的软件包列表,这是一个好习惯:

sudo apt-get update

接着,卸载任何可能存在的旧版本Docker(如果之前没装过,执行这些命令也不会有什么影响):

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

这里移除的是一些可能来自系统仓库的较老版本或不同分支的Docker相关包。

Docker需要一个稳定的存储库来安装,我们需要安装一些工具来让apt可以通过HTTPS使用存储库:

sudo apt-get install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

2.2 添加Docker官方GPG密钥和存储库

为了保证我们下载的软件包是来自Docker官方且未被篡改的,需要添加Docker的官方GPG密钥。GPG密钥就像软件的“数字指纹”,用于验证软件来源的合法性。

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

接下来,告诉你的系统,Docker的软件仓库地址在哪里。这条命令会创建一个源列表文件。

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

命令看起来有点长,我解释一下:$(dpkg --print-architecture)会自动获取你系统的架构(比如amd64),$(lsb_release -cs)会获取你的Ubuntu版本代号(比如jammy)。这样就能确保添加的是对应你系统版本的仓库。

2.3 正式安装Docker引擎

添加完仓库后,再次更新软件包列表,这次就能看到来自Docker仓库的软件信息了:

sudo apt-get update

现在,可以安装Docker引擎及其相关组件了:

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

这里安装的docker-ce是社区版,containerd.io是容器运行时,而docker-compose-plugin是Docker官方现在推荐的Compose工具(我们后面会用到)。

安装完成后,Docker服务(守护进程)默认是不会启动的。我们启动它,并设置成开机自启:

sudo systemctl start docker
sudo systemctl enable docker

最后,验证一下安装是否成功。最经典的方式就是运行一个测试容器:

sudo docker run hello-world

如果看到终端打印出“Hello from Docker!”等欢迎信息,并且详细解释了Docker的各个步骤,那么恭喜你,Docker已经成功安装并运行了!

注意:上面的命令都使用了sudo,因为安装软件和启动服务需要管理员权限。默认情况下,运行Docker命令也需要sudo。如果你想免去每次打sudo的麻烦,可以将你的用户加入docker用户组:sudo usermod -aG docker $USER操作后需要注销并重新登录才能生效

3. Docker核心操作:像管理应用一样管理容器

安装好了,我们来玩点真的。Docker的核心操作其实就围绕几个概念:镜像容器。镜像好比是软件的安装包(比如mysql:8.0),容器则是用这个安装包运行起来的一个具体实例。下面这些命令是你一定会用到的,我结合自己的使用经验给你讲讲。

3.1 镜像的拉取与管理

镜像通常托管在公共仓库里,最大的是Docker Hub。你可以把它想象成手机的应用商店。

拉取镜像:当你第一次运行某个镜像时,Docker会自动去拉取。你也可以手动先拉取下来。

docker pull nginx:latest

这条命令会从Docker Hub拉取最新版本的Nginx镜像。我建议你总是带上标签(如:latest, :alpine),明确指定版本,这样在生产环境中可以避免因默认版本更新带来的意外。

查看本地镜像

docker images
# 或者更详细的格式
docker image ls

这个命令会列出所有你已经下载到本地的镜像,包括仓库名、标签、镜像ID、创建时间和大小。

删除镜像:如果磁盘空间紧张,或者有些测试镜像不需要了,可以删除。

# 通过镜像ID删除
docker rmi <镜像ID>
# 强制删除(如果镜像正在被容器使用)
docker rmi -f <镜像ID>

删除前记得确认没有容器依赖它,否则会删除失败。

3.2 容器的生命周期管理

有了镜像,我们就可以创建和运行容器了。这是最有趣的部分。

运行一个容器:最简单的运行命令。

docker run nginx

但这样运行,容器会占用你的终端,并且按下Ctrl+C容器就会停止。这通常不是我们想要的。

后台运行容器:使用-d参数让容器在后台运行。

docker run -d --name my-nginx nginx

--name参数给容器起个名字,不然Docker会随机分配一个,以后管理起来不方便。

端口映射:这是让容器服务能被外部访问的关键。容器内部有自己独立的网络,比如Nginx默认在80端口监听。我们需要把宿主机的端口映射到容器的端口。

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

现在,你访问宿主机的http://localhost:8080,流量就会被转发到容器的80端口,你就能看到Nginx的欢迎页面了。

数据持久化:容器内的数据是易失的,容器删除,数据就没了。为了持久化数据(比如数据库文件),我们需要使用

docker run -d --name my-mysql \
  -p 3306:3306 \
  -v /home/user/mysql_data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mysql:8.0

这个命令做了几件事:

  1. -p 3306:3306: 将宿主机的3306端口映射到容器的3306端口。
  2. -v /home/user/mysql_data:/var/lib/mysql: 将宿主机目录/home/user/mysql_data挂载到容器内的/var/lib/mysql(MySQL存放数据的地方)。这样,即使容器被删除,数据依然保留在宿主机上。
  3. -e MYSQL_ROOT_PASSWORD=my-secret-pw: 设置环境变量,这里是MySQL的root密码。

查看与进入容器

# 查看正在运行的容器
docker ps
# 查看所有容器(包括已停止的)
docker ps -a

# 查看容器日志,调试神器
docker logs my-nginx
# 实时查看日志
docker logs -f my-nginx

# 进入一个正在运行的容器内部,就像SSH进去一样
docker exec -it my-nginx /bin/bash

-it-i(交互式)和-t(分配一个伪终端)的组合,让你可以像在普通服务器里一样输入命令。

停止、启动、删除容器

# 停止容器
docker stop my-nginx
# 启动已停止的容器
docker start my-nginx
# 重启容器
docker restart my-nginx
# 删除一个已停止的容器
docker rm my-nginx
# 强制删除一个运行中的容器
docker rm -f my-nginx

4. Docker Compose登场:一键管理多个容器的神器

当你只需要运行一个容器时,用docker run命令加上一堆参数还能接受。但现实中的项目,比如一个Web应用,往往需要多个容器协同工作:一个运行Python Django应用的容器,一个运行PostgreSQL数据库的容器,再加一个Redis做缓存。手动一个个启动、管理它们之间的网络连接、依赖顺序,会变得非常繁琐且容易出错。

这时候,Docker Compose就是你的救星。它允许你用一个YAML格式的配置文件(docker-compose.yml)来定义所有服务(容器),然后用一条命令启动、停止整个应用栈。它把多个容器组成了一个“项目”来管理。

4.1 安装Docker Compose

如果你按照我前面第二节的方法安装了docker-compose-plugin,那么你已经拥有了最新的docker compose命令(注意是空格,不是横杠)。你可以通过以下命令验证:

docker compose version

如果显示出版本号,说明安装成功。这是Docker官方现在推荐的方式,它作为Docker CLI的一个插件存在,兼容性更好。

如果你想安装独立的docker-compose(带横杠的旧版命令),也可以使用以下方法(以安装v2.20.3为例):

# 下载二进制文件到 /usr/local/bin/docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 赋予可执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

4.2 编写你的第一个docker-compose.yml

让我们来部署一个最经典的组合:WordPress博客。它需要一个Web服务器(WordPress镜像)和一个数据库(MySQL镜像)。我们通过一个docker-compose.yml文件来定义它们。

首先,创建一个项目目录,并进入该目录:

mkdir my-wordpress && cd my-wordpress

然后,创建docker-compose.yml文件:

version: '3.8'  # 指定Compose文件格式的版本

services:
  # 定义数据库服务
  db:
    image: mysql:8.0  # 使用MySQL 8.0镜像
    container_name: wordpress_db
    # 如果容器停止或重启,数据会丢失,所以需要卷来持久化
    volumes:
      - db_data:/var/lib/mysql  # 使用命名卷‘db_data’挂载到容器内的MySQL数据目录
    restart: always  # 总是重启,确保服务高可用
    environment:  # 设置环境变量,这里是MySQL的配置
      MYSQL_ROOT_PASSWORD: some_root_password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress_password
    networks:
      - wp-network  # 将服务连接到自定义网络

  # 定义WordPress服务
  wordpress:
    depends_on:  # 显式声明依赖关系,确保db服务先启动
      - db
    image: wordpress:latest
    container_name: wordpress_app
    ports:
      - "8080:80"  # 将宿主机的8080端口映射到容器的80端口
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306  # 注意这里,使用服务名‘db’作为主机名,Compose会自动进行DNS解析
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress_password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html  # 持久化WordPress的网站文件(主题、插件、上传内容等)
    networks:
      - wp-network

# 定义卷,用于数据持久化
volumes:
  db_data:  # 声明一个名为‘db_data’的卷,Docker会自动管理它在宿主机上的存储位置
  wp_data:

# 定义网络,让两个服务在同一个隔离网络中通信,与宿主机和其他容器隔离
networks:
  wp-network:
    driver: bridge

这个配置文件虽然看起来有点长,但结构非常清晰。我把它拆解一下:

  • services: 定义了两个服务:db(数据库)和wordpress(应用)。
  • image: 指定每个服务使用哪个镜像。
  • volumes: 定义了数据卷。db_datawp_data命名卷,Docker会管理它们的存储位置,比直接绑定宿主机目录更便携,适合生产环境。文件底部volumes:声明了这些卷。
  • environment: 设置容器内的环境变量,这是向容器传递配置(如数据库密码)的标准方式。
  • ports: 端口映射。只有需要从外部访问的服务(如wordpress)才需要映射端口。db服务只在内部网络中被wordpress访问,所以不映射端口更安全。
  • depends_on: 声明服务依赖,确保db先启动,再启动wordpress
  • networks: 定义了一个自定义的桥接网络wp-network。两个服务加入同一个网络后,可以直接用服务名(如db)作为主机名互相访问,这是Compose提供的非常便利的功能。
  • restart: always: 确保容器意外退出时自动重启,增加稳定性。

4.3 使用Compose管理项目

配置文件写好了,管理就变得异常简单。所有的操作都在项目目录(即docker-compose.yml所在的目录)下进行。

启动整个应用栈

docker compose up -d

-d代表后台运行。执行这条命令后,Docker会做以下几件事:

  1. 检查并拉取本地不存在的镜像(mysql:8.0wordpress:latest)。
  2. 创建wp-network网络。
  3. 创建db_datawp_data命名卷。
  4. 按照依赖顺序启动db容器,然后启动wordpress容器。

现在,打开浏览器访问 http://你的服务器IP:8080,你应该能看到WordPress的安装界面了!

查看服务状态

docker compose ps

这个命令只列出当前docker-compose.yml定义的项目中的容器,比docker ps更聚焦。

查看服务日志

# 查看所有服务的日志
docker compose logs
# 实时查看所有服务的日志
docker compose logs -f
# 只看某个服务的日志,比如数据库
docker compose logs -f db

在排查问题时,查看日志是第一步。

停止服务

docker compose stop

这会停止所有容器,但不会删除它们和相关的网络、卷。你的数据(数据库、WordPress文件)都还保留着。

停止并清理

docker compose down

这条命令会停止并删除所有容器、网络(默认的桥接网络不会被删),但不会删除命名卷db_data, wp_data),所以你的数据是安全的。如果你想连数据卷也一并删除(警告:数据会丢失!),可以加-v参数:

docker compose down -v

重启服务

docker compose restart

5. 实战:部署一个现代化的Python Flask应用栈

光看WordPress可能还不够过瘾,我们再来一个更贴近开发者日常的实战:部署一个由Python Flask后端PostgreSQL数据库pgAdmin管理界面组成的完整应用栈。这个例子会用到更多实用的Compose特性。

假设我们有一个简单的Flask应用,文件结构如下:

my-flask-app/
├── docker-compose.yml
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   └── app.py
└── .env

1. Flask应用代码 (backend/app.py):

from flask import Flask, jsonify
import os
import psycopg2
from psycopg2.extras import RealDictCursor

app = Flask(__name__)

# 从环境变量读取数据库配置
DB_HOST = os.getenv('DB_HOST', 'db')  # 默认使用服务名‘db’
DB_NAME = os.getenv('DB_NAME', 'mydb')
DB_USER = os.getenv('DB_USER', 'postgres')
DB_PASSWORD = os.getenv('DB_PASSWORD', 'secret')

def get_db_connection():
    conn = psycopg2.connect(
        host=DB_HOST,
        database=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        cursor_factory=RealDictCursor
    )
    return conn

@app.route('/')
def hello():
    return jsonify({"message": "Welcome to Flask API with Docker Compose!"})

@app.route('/users')
def get_users():
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('SELECT id, username, email FROM users;')
    users = cur.fetchall()
    cur.close()
    conn.close()
    return jsonify(users)

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

2. 依赖文件 (backend/requirements.txt):

flask==2.3.2
psycopg2-binary==2.9.7

3. Dockerfile (backend/Dockerfile):

# 使用官方Python轻量级镜像
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 5000

# 启动命令
CMD ["python", "app.py"]

4. 环境变量文件 (.env): 我们使用.env文件来管理敏感配置,避免硬编码在docker-compose.yml中。

# 数据库配置
POSTGRES_DB=mydb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=mysecretpassword

# Flask应用配置
DB_HOST=db
DB_NAME=mydb
DB_USER=postgres
DB_PASSWORD=mysecretpassword

5. 核心:docker-compose.yml:

version: '3.8'

services:
  # PostgreSQL数据库服务
  db:
    image: postgres:15-alpine  # 使用Alpine版本,更小巧
    container_name: flask_db
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data  # 持久化数据库数据
    networks:
      - app-network
    healthcheck:  # 健康检查,确保数据库完全就绪后,再启动依赖它的服务
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Flask后端应用服务
  backend:
    build: ./backend  # 使用当前目录下的backend文件夹中的Dockerfile构建镜像
    container_name: flask_backend
    depends_on:
      db:
        condition: service_healthy  # 依赖db服务,并且要等它通过健康检查
    environment:
      DB_HOST: ${DB_HOST}
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
    ports:
      - "5000:5000"  # 映射Flask默认端口
    volumes:
      - ./backend:/app  # 开发时使用:将本地代码目录挂载到容器,实现代码热更新
    networks:
      - app-network
    # 开发模式下的命令,开启调试和热重载
    command: python app.py
    # 生产环境可以注释掉上面的volumes和command,使用构建好的镜像直接运行

  # pgAdmin 4 - PostgreSQL图形化管理工具
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: flask_pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com  # pgAdmin登录邮箱
      PGADMIN_DEFAULT_PASSWORD: adminpassword    # pgAdmin登录密码
    ports:
      - "5050:80"  # 映射pgAdmin的Web界面端口
    depends_on:
      - db
    networks:
      - app-network

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

这个配置的亮点在于:

  • build指令backend服务没有直接用现成的镜像,而是指定了build: ./backend。当你运行docker compose up时,Compose会先根据backend/Dockerfile构建一个本地镜像,然后运行它。这对于开发迭代非常方便。
  • 开发卷挂载backend服务中配置了- ./backend:/app。这会将宿主机的backend目录实时挂载到容器的/app工作目录。这样,你在本地修改app.py代码后,容器内的代码也会立刻更新(配合Flask的调试模式,甚至能自动重载),无需重新构建镜像。
  • 健康检查db服务定义了healthcheck。这确保了backend服务(通过condition: service_healthy)会等到数据库真正可以接受连接时才启动,避免了应用启动时因数据库未就绪而报错。
  • 环境变量文件:我们在文件顶部没有硬编码密码,而是通过.env文件引用(${POSTGRES_PASSWORD})。Compose会自动读取同目录下的.env文件。切记要将.env文件加入.gitignore,不要提交到代码仓库!
  • 多服务协作:三个服务(db, backend, pgadmin)通过自定义的app-network互联。backend应用可以通过服务名db直接访问数据库,pgadmin也可以通过db作为主机名来管理数据库。

部署与验证: 在my-flask-app目录下,执行:

# 启动所有服务
docker compose up -d

# 查看构建和启动日志
docker compose logs -f

等待所有服务启动完成后:

  1. 访问 http://localhost:5000/,你会看到Flask的欢迎JSON。
  2. 访问 http://localhost:5000/users(数据库里还没数据,可能返回空数组[])。
  3. 访问 http://localhost:5050,用.env里设置的邮箱和密码登录pgAdmin,可以添加服务器来管理你的PostgreSQL数据库。

要停止整个环境,运行docker compose down。因为使用了命名卷postgres_data,你的数据库数据会被保留,下次up时数据还在。

从这两个实战案例可以看出,Docker Compose通过一个声明式的配置文件,把多容器应用的部署、网络、存储、依赖关系管理得井井有条。它极大地简化了从开发到测试再到生产的环境一致性保障工作。一旦你熟悉了docker-compose.yml的语法,部署一个复杂的微服务架构也不再是令人望而生畏的任务。

更多推荐