Docker 简介

Docker 是一个广泛使用的容器化平台,它允许开发人员将应用程序及其依赖项打包到一个轻量级、可移植的容器中,从而实现应用程序在任何环境中快速而一致地运行。下面是对 Docker 的详细介绍:


1. Docker 基本概念

  • 容器(Container)

容器是 Docker 的核心概念。它类似于一个轻量级的虚拟机,但不需要完整的操作系统,在共享宿主机操作系统内核的情况下,提供独立的运行环境。每个容器都包含应用程序以及运行该应用程序所需的全部依赖,使得应用能够在不同环境下一致运行。

  • 镜像(Image)

镜像是容器运行所依赖的静态模板,类似于虚拟机的快照。它包含操作系统、运行时环境、应用程序代码和依赖包。可以将镜像看作是构建容器实例的蓝图。Docker Hub 是一个主要的公共镜像仓库,也支持私有镜像仓库。

  • Dockerfile

Dockerfile 是构建 Docker 镜像的脚本文件,它包含一系列指令,用于从基础镜像开始,安装依赖、拷贝代码、配置环境变量、暴露端口等步骤。使用 Dockerfile,可以自动化构建镜像,使得镜像版本管理变得简单和一致。

  • 容器化与虚拟化的区别

虚拟机(VM)在硬件级别上模拟完整的计算机系统,每个虚拟机需要单独加载操作系统;而 Docker 容器则共享宿主机的操作系统内核,但保持应用之间的隔离,因此容器更轻量、启动速度更快、资源占用更少。


2. Docker 的主要特点和优势

  • 轻量级和高效

由于容器共享宿主机内核,不需要完整的操作系统,各容器的大小通常比虚拟机小得多,启动速度也非常快,适合快速部署和扩展服务。

  • 便携性

Docker 镜像包含了所有的依赖和配置,保证在不同环境(开发、测试、生产)中运行一致。只要有 Docker 引擎,镜像就可以在不同平台间轻松迁移和部署。

  • 隔离性

每个容器都运行在独立的环境中,可以避免依赖冲突,并且提高安全性。不同的容器之间彼此隔离,同时可以共享宿主机资源,从而实现资源的高效利用。

  • 简化部署和持续集成

通过将应用程序和依赖打包到容器中,部署过程变得简单、标准化,适合自动化部署和持续集成/持续交付(CI/CD)流程。

  • 生态系统与工具链

Docker 拥有丰富的生态系统,公共镜像仓库 Docker Hub、镜像管理工具、编排工具(如 Docker Compose、Kubernetes)等,使得容器管理变得更加简单和高效。

Docker 相关教程:

  1. 前言 · Docker – 从入门到实践
  1. Docker 入门教程 - 阮一峰的网络日志
  1. Docker 教程 | 菜鸟教程

使用 Docker 初步部署 Django 项目开发环境

标准流程

使用 Docker 部署 Django 项目的标准流程一般如下:

项目准备(生成依赖文件、配置环境变量等) —> 编写 Dockerfile —> 编写 Docker Compose 组合各项服务 —> 调整 Django 配置 —> 创建并启动容器 —> 验证部署。

项目准备

  • ​生成依赖文件​​:确保项目根目录有 requirements.txt,包含所有Python依赖。

  • ​配置环境变量​​:将敏感信息(如 SECRET_KEY、数据库密码)存储在 .env 文件或Docker Compose的环境变量中。

  • ​创建 .dockerignore​:忽略虚拟环境、IDE文件等,例如:

1
2
3
4
5
6
7
8
9

    venv/

    .git

    __pycache__

    *.sqlite3

编写 Dockerfile

在项目根目录创建 Dockerfile,内容可参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

# 开发环境 Dockerfile,也可以使用3.9-slim进一步瘦身

FROM python:3.9



# 设置工作目录,根据自己的需求进行修改

WORKDIR /app



# 设置环境变量

# 镜像瘦身,阻止 Python 生成 .pyc 字节码文件,减小镜像体积

ENV PYTHONDONTWRITEBYTECODE=1

# 关闭输出缓冲,保证日志和 print 语句能实时打印到容器日志

ENV PYTHONUNBUFFERED=1



# 安装依赖,先复制依赖文件,再安装依赖

COPY requirements.txt /app/

# --no-cache-dir 避免 pip 缓存残留在镜像层,进一步瘦身

RUN pip install --no-cache-dir -r requirements.txt

# 安装 VS Code / PyCharm 等 IDE 常用的远程调试工具 debugpy,方便在容器内打断点调试

RUN pip install debugpy



# 将项目所有代码(除 .dockerignore 排除项)拷贝到镜像内 /app

COPY . /app/



# 创建必要的目录,如果代码本身已经有这些目录了,可以不做

RUN mkdir -p /app/media \

    /app/upload_files \

    /app/function_test_files \

    /app/function_test_other_files \

    /app/eyes_test_files \

    /app/xray_test_files \

    /app/report_files \

    /app/dev_report_files \

    /app/dev_report_pdf_files \

    /app/fun_report_files \

    /app/fun_report_pdf_files \

    /app/dev_fun_report_pdf_files \

    /app/temp_order_files \

    /app/feedback_files \

    /app/temp_feedback_files \

    /app/question_files \

    /app/report_pdf_files \

    /app/bug_report_pdf_files \

    /app/batch_report_pdf_files \

    /app/temporary_files \

    && chmod -R 755 /app



# 暴露端口 - Django服务和调试端口

EXPOSE 8000 5678



# 启动命令 - 开发模式下启用热重载,调试友好

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

这里有几个地方需要注意:

WORKDIR

  1. 容器内部的路径

设置的工作目录是 Docker 容器内部的绝对路径,不是宿主机(你的本地电脑)的路径;

这个目录会在构建镜像时自动创建(如果不存在),类似在容器内运行 mkdir /app && cd /app

  1. 后续操作的工作目录

所有后续命令(如 COPY . .)都会将文件复制到容器的 /app 目录下。例如:

* COPY requirements.txt . 会将宿主机当前目录的 requirements.txt 复制到容器的 /app/requirements.txt

* RUN pip install ... 会在容器的 /app 目录下执行。

  1. 类比理解

如果将容器看作一个独立的 Linux 系统:

* /app 是这个系统中的一个普通文件夹(类似本地的 ~/myproject)。

* WORKDIR 只是定义了这个系统的“当前工作目录”,宿主机完全看不到这个目录(除非你通过 volumes 挂载)。

为什么看不到容器内的 /app 目录?因为容器内的文件系统是隔离的,默认情况下宿主机无法直接访问。若需要查看:

1
2
3
4
5
6
7
8
9

# 启动容器并进入 Shell,这里的 web 是服务名称

docker-compose run web bash

# 此时会进入容器的 /app 目录(由 WORKDIR 设置)

ls  # 查看容器内 /app 目录的内容

COPY

COPY 是 Dockerfile 中用于将文件或目录从宿主机(构建上下文)复制到容器镜像中的核心指令。具体用法可以参考上边给出的一些教程。

这里先创建了一些文件夹,然后再 COPY 文件到容器中,如果项目中有创建文件夹的代码,其实也可以不用创建文件夹;如果宿主机这些文件夹里有文件,通过 COPY 命令也可以直接复制过去,这里是为了保险期间做了冗余操作。

RUN

这里的 RUN 命令主要是用来安装依赖,确保项目可以正常运行。

EXPOSE

EXPOSE 是 Dockerfile 中用于 ​​声明容器运行时监听的端口​​ 的指令。它不会自动将端口映射到宿主机,而是作为元数据帮助开发者和工具理解容器需要哪些端口。

CMD

这是 Dockerfile 中用于启动 Django 开发服务器的典型命令,其作用是为容器定义默认执行行为。

为什么要绑定到 0.0.0.0?​

  • ​默认行为​​:Django 的 runserver 默认监听 127.0.0.1:8000,仅允许容器内部访问。

  • ​容器网络特性​​:若需从宿主机或外部网络访问服务,必须显式绑定到 0.0.0.0(表示监听所有网络接口)。这里我们需要宿主机进行访问,所以需要绑定到 0.0.0.0

与 Docker 端口映射的关系​​

  • ​容器内监听​​:此命令确保 Django 服务器在容器内的 8000 端口运行。

  • ​宿主机访问​​:需通过 -p 参数将容器端口映射到宿主机(否则外部无法访问):

1
2
3

docker run -d -p 8080:8000 your-image  # 宿主机 8080 → 容器 8000

此时可通过 http://宿主机IP:8080 访问服务。

这里适用于开发环境,如果需要部署生产环境,可能需要再配合生产级服务器如 Nginx。

编写 Docker Compose 组合各项服务

样例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

services:

  web:

    build:

      # 构建镜像时的上下文目录,即当前目录。Docker 会在此目录及子目录里寻找 Dockerfile 和相关文件。

      context: .

      # 告诉 Docker 使用 Dockerfile.dev 而非默认的 Dockerfile 进行构建

      dockerfile: Dockerfile.dev

    # 指定启动后容器的名称,方便在命令行(docker ps、docker logs)中直观识别

    container_name: cnooc-backend-dev

    ports:

      # 将宿主机的 8000 端口映射到容器的 8000 端口,用于访问 Django 开发服务器

      - "8000:8000"

      # 将宿主机的 5678 端口映射到容器的 5678 端口,通常用于 IDE(如 VS Code)通过 debugpy 进行远程调试

      - "5678:5678"

    volumes:

      # 只读挂载项目代码,防止容器意外改写

      - .:/app:ro

      # 挂载剩余文件路径                  

      - ./media:/app/media

      - ./upload_files:/app/upload_files

      - ./report_files:/app/report_files

      - ./dev_report_files:/app/dev_report_files

      - ./function_test_files:/app/function_test_files

      - ./fun_report_files:/app/fun_report_files

      - ./fun_report_pdf_files:/app/fun_report_pdf_files

      - ./dev_fun_report_pdf_files:/app/dev_fun_report_pdf_files

    environment:

      # 这里需要写入一些配置,建议通过配置文件的方式进行,更加安全,下面只是几个例子

      - DJANGO_SECRET_KEY=django-insecure-t-70t&48!*#^x^9w^i)f(6_g%9094aet4#4_vkqf_rz!8wqe&^

      - DJANGO_DEBUG=True

      - DJANGO_SETTINGS_MODULE=config.settings.development

    # 容器退出后或宿主机重启后,Docker 始终自动重启此服务,保证开发环境持续可用

    restart: always

    # 覆盖镜像内 Dockerfile 中的默认 CMD,用 Django 自带的开发服务器启动,绑定所有网络接口以便外部访问

    command: python manage.py runserver 0.0.0.0:8000

    networks:

    # 将 web 服务加入自定义的 backend-dev-network 网络,实现与同网络下其他容器(如 redis)之间的通信隔离

      - backend-dev-network

    depends_on:

      # 指出 web 服务依赖于 redis 服务。Compose 会先创建并启动 redis,再启动 web。注意:它不保证 redis 已“可用”,仅控制启动顺序

      - redis



  redis:

    image: redis:latest

    container_name: cnooc-redis-dev

    ports:

      - "6379:6379"

    volumes:

      # 使用命名卷 redis-data 持久化 Redis 数据,避免容器重建时数据丢失,这个可以根据需求进行设置

      - redis-data:/data

      # 将本地的 redis.conf 配置文件挂载到容器中,覆盖默认配置,尤其对于清华大学校园网取消 bind 127.0.0.1 的配置

      - ./redis.conf:/usr/local/etc/redis/redis.conf

    # 覆盖镜像默认命令,启动 Redis 并加载挂载进来的自定义配置文件

    command: redis-server /usr/local/etc/redis/redis.conf

    networks:

      # 将 Redis 服务也加入相同的自定义网络,以便 web 服务通过容器名称 redis 进行访问

      - backend-dev-network



networks:

  backend-dev-network:

    # 使用桥接网络驱动,为 web 和 redis 创建一个隔离的网络环境,它们可以通过容器名互相访问

    driver: bridge



# 定义一个名为 redis-data 的持久化卷,用于存储 Redis 的数据目录,从而实现数据在容器重启/重建间持久化

volumes:

  redis-data:

上述配置文件主要配置了以下几个服务:

Web 服务(Django 应用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

web:

  build:

    # 构建镜像时的上下文目录,即当前目录。Docker 会在此目录及子目录里寻找 Dockerfile 和相关文件。

    context: .

    # 告诉 Docker 使用 Dockerfile.dev 而非默认的 Dockerfile 进行构建

    dockerfile: Dockerfile.dev

  # 指定启动后容器的名称,方便在命令行(docker ps、docker logs)中直观识别

  container_name: cnooc-backend-dev

  ports:

    # 将宿主机的 8000 端口映射到容器的 8000 端口,用于访问 Django 开发服务器

    - "8000:8000"

    # 将宿主机的 5678 端口映射到容器的 5678 端口,通常用于 IDE(如 VS Code)通过 debugpy 进行远程调试

    - "5678:5678"

  volumes:

    # 只读挂载项目代码,防止容器意外改写

    - .:/app:ro

    # 挂载剩余文件路径                  

    - ./media:/app/media

    - ./upload_files:/app/upload_files

    - ./report_files:/app/report_files

    - ./dev_report_files:/app/dev_report_files

    - ./function_test_files:/app/function_test_files

    - ./fun_report_files:/app/fun_report_files

    - ./fun_report_pdf_files:/app/fun_report_pdf_files

    - ./dev_fun_report_pdf_files:/app/dev_fun_report_pdf_files

  environment:

    # 这里需要写入一些配置,建议通过配置文件的方式进行,更加安全,下面只是几个例子

    - DJANGO_SECRET_KEY=django-insecure-t-70t&48!*#^x^9w^i)f(6_g%9094aet4#4_vkqf_rz!8wqe&^

    - DJANGO_DEBUG=True

    - DJANGO_SETTINGS_MODULE=config.settings.development

  # 容器退出后或宿主机重启后,Docker 始终自动重启此服务,保证开发环境持续可用

  restart: always

  # 覆盖镜像内 Dockerfile 中的默认 CMD,用 Django 自带的开发服务器启动,绑定所有网络接口以便外部访问

  command: python manage.py runserver 0.0.0.0:8000

  networks:

  # 将 web 服务加入自定义的 backend-dev-network 网络,实现与同网络下其他容器(如 redis)之间的通信隔离

    - backend-dev-network

  depends_on:

    # 指出 web 服务依赖于 redis 服务。Compose 会先创建并启动 redis,再启动 web。注意:它不保证 redis 已“可用”,仅控制启动顺序

    - redis

Redis 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

redis:

  image: redis:latest

  container_name: cnooc-redis-dev

  ports:

    - "6379:6379"

  volumes:

    # 使用命名卷 redis-data 持久化 Redis 数据,避免容器重建时数据丢失,这个可以根据需求进行设置

    - redis-data:/data

    # 将本地的 redis.conf 配置文件挂载到容器中,覆盖默认配置,尤其对于清华大学校园网取消 bind 127.0.0.1 的配置

    - ./redis.conf:/usr/local/etc/redis/redis.conf

  # 覆盖镜像默认命令,启动 Redis 并加载挂载进来的自定义配置文件

  command: redis-server /usr/local/etc/redis/redis.conf

  networks:

    # 将 Redis 服务也加入相同的自定义网络,以便 web 服务通过容器名称 redis 进行访问

    - backend-dev-network

关键配置

  • ​代码实时同步​

volumes: .:/app:ro 将宿主机代码以只读模式挂载到容器,实现修改后即时生效(适合开发,但生产环境应避免,因为生产环境应该构建独立镜像)。

  • 挂载本地文件

通过挂载本地文件,实现容器内文件与宿主机文件同步,确保文件的持久化。

安全风险​​

这种开发环境配置是为了更加方便快速使用,但是也存在一些安全隐患,生产环境中应该避免如下情况:

  • ​硬编码密钥​

DJANGO_SECRET_KEY 明文写入 Compose 文件不安全,应改用环境变量文件(.env):

1
2
3

env_file: .env  # 从外部文件加载环境变量

  • ​Redis 端口暴露​

ports: "6379:6379" 允许外部直接访问 Redis,建议移除端口映射,仅限 Docker 内部网络访问。


使用 Docker 初步部署 Django 项目生产环境

对于生产环境来讲,最好使用 gunicorn 作为 WSGI 服务器,性能更好。其次可能需要使用 Nginx 进行反向代理。这里的 Nginx 仅仅是示例,我并没有使用 Nginx 部署过,可以根据需要进行调整。

这里创建了一个 appuser 用户,避免容器内进程以 root 身份运行,增加安全性。

依赖安装也进行了优化,先打包成 wheel,再安装,省去了源码编译时间;最终镜像仅含运行所需的 wheel,体积小且启动快。

生产级服务器使用的是Gunicorn,Gunicorn 多 worker、超时设置可以根据需求进行调整。替换掉 Django 自带的开发服务器,满足生产环境性能与稳定性。

其余解析多参考开发环境部署,基本大差不差。

不需要 Nginx 的话,删除掉所有 Nginx 相关配置即可。

生产环境 Dockerfile.prod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

# 生产环境 Dockerfile - 多阶段构建

FROM python:3.9-slim AS builder



# 设置工作目录

WORKDIR /app



# 设置环境变量

ENV PYTHONDONTWRITEBYTECODE=1

ENV PYTHONUNBUFFERED=1



# 安装构建依赖

RUN apt-get update && \

    apt-get install -y --no-install-recommends gcc libpq-dev && \

    apt-get clean && \

    rm -rf /var/lib/apt/lists/*



# 安装Python依赖

COPY requirements.txt .

RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt



# 第二阶段:最终镜像

FROM python:3.9-slim



# 创建非root用户

RUN useradd -ms /bin/bash appuser



# 设置工作目录

WORKDIR /app



# 设置环境变量

ENV PYTHONDONTWRITEBYTECODE=1

ENV PYTHONUNBUFFERED=1

ENV DJANGO_SETTINGS_MODULE=config.settings.production

ENV DJANGO_DEBUG=False



# 安装运行时依赖

RUN apt-get update && \

    apt-get install -y --no-install-recommends libpq-dev && \

    apt-get clean && \

    rm -rf /var/lib/apt/lists/*



# 从builder阶段复制wheel文件并安装

COPY --from=builder /app/wheels /wheels

RUN pip install --no-cache /wheels/*



# 复制项目文件

COPY . .



# 创建必要的目录并设置权限

RUN mkdir -p /app/media \

    /app/upload_files \

    /app/function_test_files \

    /app/function_test_other_files \

    /app/eyes_test_files \

    /app/xray_test_files \

    /app/report_files \

    /app/dev_report_files \

    /app/dev_report_pdf_files \

    /app/fun_report_files \

    /app/fun_report_pdf_files \

    /app/dev_fun_report_pdf_files \

    /app/temp_order_files \

    /app/feedback_files \

    /app/temp_feedback_files \

    /app/question_files \

    /app/report_pdf_files \

    /app/bug_report_pdf_files \

    /app/batch_report_pdf_files \

    /app/temporary_files \

    && chown -R appuser:appuser /app



# 切换到非root用户

USER appuser



# 收集静态文件

RUN python manage.py collectstatic --noinput



# 暴露端口

EXPOSE 8000



# 生产环境启动命令 - 使用gunicorn

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "120", "config.wsgi:application"]

Docker-compose.prod.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

services:

  web:

    build:

      context: .

      dockerfile: Dockerfile.prod

    container_name: cnooc-backend-prod

    restart: always

    ports:

      - "8000:8000"

    volumes:

      - static_volume:/app/staticfiles

      - media_volume:/app/media

      - ./upload_files:/app/upload_files

      - ./report_files:/app/report_files

      - ./dev_report_files:/app/dev_report_files

      - ./function_test_files:/app/function_test_files

      - ./fun_report_files:/app/fun_report_files

      - ./fun_report_pdf_files:/app/fun_report_pdf_files

      - ./dev_fun_report_pdf_files:/app/dev_fun_report_pdf_files

    environment:

      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}

      - DJANGO_ALLOWED_HOSTS=${DJANGO_ALLOWED_HOSTS}

    networks:

      - backend-prod-network

    depends_on:

      - redis

    healthcheck:

      test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]

      interval: 30s

      timeout: 10s

      retries: 3

      start_period: 40s



  nginx:

    image: nginx:1.23-alpine

    container_name: cnooc-nginx

    restart: always

    ports:

      - "80:80"

      - "443:443"

    volumes:

      - static_volume:/usr/share/nginx/html/static

      - media_volume:/usr/share/nginx/html/media

      - ./nginx/conf.d:/etc/nginx/conf.d

      - ./nginx/ssl:/etc/nginx/ssl

    networks:

      - backend-prod-network

    depends_on:

      - web



  redis:

    image: redis:latest

    container_name: cnooc-redis-prod

    command: redis-server /usr/local/etc/redis/redis.conf

    volumes:

      - redis-data:/data

      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf

    networks:

      - backend-prod-network

    restart: always

    healthcheck:

      test: ["CMD", "redis-cli", "ping"]

      interval: 30s

      timeout: 10s

      retries: 3



networks:

  backend-prod-network:

    driver: bridge



volumes:

  static_volume:

  media_volume:

  redis-data: