Docker

浪费了很多时间在重新配置环境上面,我不中了

原理:

理解成一个平台中搭建不同容器,执行不同进程,需要不同进程相互不可见,完全独立出去,就需要将进程隔离出去。

在Linux系统中,对一个进程进行隔离,主要是通过Namespace和Cgroup两大机制实现的。

Namespace(命名空间)机制是Linux内核提供的一种资源隔离技术,它将全局系统资源(如进程ID、网络、挂载点)划分成多个独立的空间,使不同进程组感觉拥有独立的操作系统视图。

  • Mount (挂载) (CLONE_NEWNS): 隔离文件系统挂载点,使进程拥有独立的挂载视图。
  • UTS (Unix Timesharing System) (CLONE_NEWUTS): 隔离主机名和域名。
  • IPC (Interprocess Communication) (CLONE_NEWIPC): 隔离进程间通信资源(如消息队列、信号量)。
  • PID (Process ID) (CLONE_NEWPID): 隔离进程ID,不同命名空间可有相同的PID(例如每个容器内都有1号进程)。
  • Network (网络) (CLONE_NEWNET): 隔离网络设备、IP地址、端口和路由表。
  • User (用户) (CLONE_NEWUSER): 隔离用户和组ID,可实现容器内root用户映射为宿主机上的非特权用户。

Cgroup (Control Groups) 是 Linux 内核的一项功能,用于对进程组进行物理资源(CPU、内存、磁盘 I/O、网络)的限制、控制和隔离。

在 Linux 中,# 符号就代表当前是 root 用户,而 $ 才代表普通用户

一个容器进程本质上是一个运行在沙盒中的隔离进程,由Linux系统本身负责隔离,Docker只是提供了一系列工具,帮助我们设置好隔离环境后,启动这个进程。

  • 三层隔离

最基本的隔离就是进程之间看不到彼此,这是由Linux的Namespace机制实现的。进程隔离的结果就是以隔离方式启动的进程看到的自身进程ID总是1,且看不到系统的其他进程。

第二种隔离就是隔离系统真实的文件系统。Docker利用Linux的mount机制,给每个隔离进程挂载了一个虚拟的文件系统,使得一个隔离进程只能访问这个虚拟的文件系统,无法看到系统真实的文件系统。相当于就是一个去获取一个真实文件(实际文件被隔离出去),投射到一个虚拟文件系统里。再把投射的这个虚拟文件和需要用到的第三方库或者需要使其中程序正常进行,其他的配置系统一起打包成为一个Dockerfile,再把Dockerfile和Docker运行的依赖打包就成为Docker镜像。启动进程的时候就相当于逐层解压,最后成为一个虚拟的文件系统。

第三种隔离就是网络协议栈的隔离

Redis(Remote Dictionary Server)是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(key-value)存储系统,常用于缓存、消息队列、会话存储等应用场景。

在Docker中运行一个Redis,用宿主机去连接Redis,这里Redis的监听相当于也是发生在Docker内部。由于Linux的网络隔离,Redis进程拥有与宿主机不同的网络空间,所以需要把Redis进程的端口号映射到宿主机

如果同时运行相当于两个进程的话,通过将不同进程服务名称也解析为动态分配的IP地址,相当于是同一IP下的不同端口的分配的不同活动

表面上看上去一致的内容,类似于不同进程看本身的ID都是1,表现的就是无法直接从IP地址上去区别实际内容。

Docker Compose 是一个用于定义和运行多容器 Docker 应用的官方工具。它使用 YAML 文件(docker-compose.yml)配置多个容器服务、网络和数据卷,通过 docker-compose up 一键启动、停止和管理整个应用堆栈,特别适用于开发、测试和单机生产环境的复杂应用编排。

解决镜像拉取问题的方案:

配置镜像加速器
这是最标准、最持久有效的方案。通过修改Docker守护进程的配置文件,为所有镜像拉取请求指定一个高速缓存代理 。

但是我在搭这个环境的时候,不知道什么原因,配置加速器反而拉取失败了,感觉可能是连接问题,好吧我在配代理的时候也遇到了这个问题,现在串起来了好家伙

编辑(或创建)Docker的配置文件 sudo vi /etc/docker/daemon.json

输入以下内容(可配置多个加速地址以实现负载均衡):

1
2
3
4
5
6
7
{
"registry-mirrors": [
"https://docker.mirrors.sjtug.sjtu.edu.cn", // 上海交通大学镜像站
"https://mirror.baidubce.com", // 百度云镜像站
"https://hub-mirror.c.163.com" // 网易镜像站
]
}

重启Docker服务使配置生效:sudo systemctl daemon-reload && sudo systemctl restart docker

验证配置:docker info | grep "Registry Mirrors" -A 1,看到配置的地址列表即表示成功 。

让科学上网方法生效
科学上网工具可以让Docker守护进程的请求通过代理发送。为Docker服务设置HTTP/HTTPS代理。

操作步骤:

  • 为Docker服务创建独立的配置目录:sudo mkdir -p /etc/systemd/system/docker.service.d

  • 创建配置文件:sudo vi /etc/systemd/system/docker.service.d/http-proxy.conf

  • 填入代理地址(假设宿主机代理端口是1080,且Docker守护进程有权限访问):

    1
    2
    3
    4
    [Service]
    Environment="HTTP_PROXY=http://宿主机IP:1080"
    Environment="HTTPS_PROXY=http://宿主机IP:1080"
    Environment="NO_PROXY=localhost,127.0.0.1,.local"
  • 重新加载配置并重启:sudo systemctl daemon-reload && sudo systemctl restart docker

Dockerfile

Dockerfile举例

1
2
3
4
5
6
7
8
9
FROM php:7.4-apache
COPY src/ /var/www/html/
COPY flag /flag
WORKDIR /var/www/html
ENV FLAG=flag{Th1s_is_a_fake_flag}
RUN chmod 444 /flag
RUN a2enmod rewrite
EXPOSE 80
CMD ["apache2-foreground"]

其中

  • FROM php:7.4-apache指定基础镜像,这个镜像Apache 已安装并配置好,PHP 已与 Apache 集成,默认网站目录:/var/www/html 一步到位。
  • COPY src/ /var/www/html/把本地源码拷贝进容器。宿主机当前目录下的 src 文件夹内容,复制到容器的 /var/www/html/(网页根目录)
  • 这样访问http://ip:port/index.php就可以执行源码。如果有多个文件,都可以访问http://ip:port/phpinfo.php
  • COPY flag /flag把宿主机当前目录下的 flag 文件,复制到容器根目录 /flag
  • RUN chmod 444 /flag 设置 flag 文件权限为“只读” 444 = r-- r-- r--也可以设置为400只允许root读,这样就不得不提权了。
  • RUN a2enmod rewrite 启用 Apache 的 rewrite 重写模块,实现伪静态 URL,路由转发,访问控制。(这个不是每道题都要用,但还是写上)
  • ENV FLAG=flag{Th1s_is_a_fake_flag}添加环境变量。
  • EXPOSE 80 声明容器对外暴露 80 端口
  • WORKDIR /var/www/html设置工作目录
  • CMD ["apache2-foreground"]默认启动apache

docker的相关命令:

命令 作用
docker pull <镜像名>:<标签> 从仓库拉取镜像,如 docker pull ubuntu:22.04
docker images 列出本地所有镜像
docker rmi <镜像ID或名称> 删除本地一个或多个镜像
docker build -t <镜像名>:<标签> . 根据当前目录下的 Dockerfile 构建镜像
docker tag <源镜像> <目标镜像:标签> 给镜像打标签,常用于推送前重命名
docker push <镜像名>:<标签> 将镜像推送到远程仓库(需先登录)
docker search <关键词> 在 Docker Hub 上搜索镜像
docker run [选项] <镜像> [命令] 创建并启动一个新容器(最常用,选项如 -d 后台、-it 交互、--name 命名、-p 端口映射、-v 挂载)
docker ps 列出正在运行的容器(加 -a 列出所有容器,包括已停止的)
docker stop <容器ID或名称> 停止一个运行中的容器
docker start <容器ID或名称> 启动一个已停止的容器
docker restart <容器ID或名称> 重启容器
docker rm <容器ID或名称> 删除一个或多个已停止的容器(加 -f 强制删除运行中的)
docker logs <容器ID或名称> 查看容器的日志输出(加 -f 持续跟踪)
docker exec -it <容器ID或名称> <命令> 在运行中的容器内执行命令(例如 docker exec -it mycontainer bash 进入容器终端)
docker inspect <容器ID或名称> 查看容器的详细信息(JSON 格式)
docker version 显示 Docker 客户端和服务端版本信息
docker info 显示 Docker 系统信息(容器数、镜像数、存储驱动等)
docker system df 查看镜像、容器、卷所占用的磁盘空间
docker system prune 清理不再使用的资源(容器、网络、镜像、构建缓存),加 -a 清理更彻底
docker network ls 列出所有网络
docker network create <网络名> 创建一个新的桥接网络
docker network connect <网络名> <容器> 将容器连接到指定网络
docker network disconnect <网络名> <容器> 将容器从网络断开
docker network inspect <网络名> 查看网络的详细信息
docker volume ls 列出所有卷
docker volume create <卷名> 创建一个数据卷
docker volume inspect <卷名> 查看卷的详细信息
docker volume rm <卷名> 删除一个卷
docker volume prune 删除所有未使用的卷
docker cp <源路径> <容器:目标路径> 在宿主机和容器之间复制文件/目录
docker commit <容器> <镜像名:标签> 将容器的当前状态保存为新镜像
docker export <容器> -o 文件名.tar 将容器的文件系统导出为 tar 包
docker import 文件名.tar <镜像名:标签> 从 tar 包导入镜像
docker save <镜像> -o 文件名.tar 将一个或多个镜像保存为 tar 包
docker load -i 文件名.tar 从 tar 包加载镜像
docker login 登录 Docker Hub 或其他私有仓库
docker logout 登出仓库

比如docker run -d,运行容器,如果没有对应容器就会创建之后再运行

可以用docker exec -it pear_test bash可以在控制台进入容器,方便调试。

创建成功之后,返回的字符就是容器id

拿之前挂载靶场文件使用的命令举例

  • docker run
    Docker 的核心命令,用于基于指定镜像创建并运行一个新容器。
  • -d
    后台运行容器(detach 模式),容器启动后不会占用当前终端,而是在后台运行。
  • --name lfi-lab
    为容器指定一个名称 lfi-lab,方便后续通过该名称管理容器(如停止、查看日志等),若不指定则 Docker 会随机生成一个名称。
  • -p 8082:80
    端口映射,将宿主机的 8082 端口映射到容器的 80 端口。这样访问宿主机 http://localhost:8082 即可访问容器内运行的 Web 服务(容器内 Apache 默认监听 80 端口)。
  • -v "D:\phpstudy_pro\WWW\lfi-labs-master:/var/www/html"
    卷挂载(volume mount),将宿主机上的目录 D:\phpstudy_pro\WWW\lfi-labs-master 挂载到容器内的 /var/www/html 目录。
    • /var/www/html 是 Apache 的默认 Web 根目录。
    • 这样做可以将本地的 PHP 项目代码直接同步到容器中,方便开发调试或运行特定应用(如 LFI 测试靶场),且代码在宿主机上修改后会实时反映在容器内。
  • php:7.3-apache
    指定使用的 Docker 镜像,这里是一个预装了 PHP 7.3 和 Apache 的官方镜像。容器将以此镜像为基础运行。

复制文件时关于忽略部分文件的方式:

解决方案是使用 .dockerignore 文件,它的工作原理类似于.gitignore

.gitignore 文件用于定义 Git 项目中无需追踪和提交的文件或目录(如日志、编译产物、IDE 配置)。在仓库根目录创建该文件,按行写入匹配模式(支持通配符),即可实现忽略。已提交的文件需用 git rm –cached 移除追踪。

  • 在执行docker build命令的目录下(也就是构建上下文的根目录),创建一个名为 .dockerignore 的文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# .dockerignore 示例(注释用 # 开头) 
# 忽略 node_modules 目录(整目录)
node_modules/
# 忽略 logs 目录
logs/
# 忽略 .git 版本控制目录
.git/
# 忽略单个文件
.env
temp.txt
# 忽略所有 .log 后缀的文件
*.log
# 忽略所有以 temp 开头的文件/目录
temp*
# 例外:如果想忽略所有 .txt 但保留 important.txt
*.txt
!important.txt

当执行 COPY . /app 时,Docker 会先读取 .dockerignore 中的规则,自动过滤掉这些文件,只复制不没有包含在这些文件中的内容。

Docker-compose.ymlDocker-compose.yaml文件

Dockerfile只可以用来构建单个镜像,在docker-compose.yml文件中,可以定义多个服务,每个服务可以包含一系列配置选项,例如镜像名称、容器端口、环境变量等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 指定 compose 文件版本(推荐用 3.x,兼容性最好)
version: '3.8'

# 定义所有服务(每个服务对应一个容器)
services:
# 服务名称(自定义,比如叫 my-app)
服务名:
# 【核心】自动构建镜像的配置
build:
context: . # 构建上下文(Dockerfile 所在目录,. 表示当前目录)
dockerfile: Dockerfile # 指定 Dockerfile 文件名(默认就是 Dockerfile,可省略)
# 【核心】端口映射(替代 docker run -p)
ports:
- "主机端口1:容器端口1" # 比如 "8080:80"
- "主机端口2:容器端口2" # 可选,支持多端口映射
# 可选:给容器自定义名称(替代 docker run --name)
container_name: 自定义容器名
# 可选:容器后台运行(默认就是,对应 docker run -d)
restart: always # 容器退出时自动重启(可选,推荐加)

一个依赖Redis数据库的web应用

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
version: '3.8'

services:
# Web 应用服务
web:
# 使用当前目录下的 Dockerfile 进行构建
build: .
# 端口映射:将宿主机的8000端口映射到容器的8000端口
ports:
- "8000:8000"
# 环境变量:告诉web应用,redis服务的主机名是"redis"(服务名)
environment:
- REDIS_HOST=redis
# 依赖关系:确保redis服务先于web服务启动
depends_on:
- redis
# 卷挂载:将当前代码挂载进去,方便开发时热更新
volumes:
- .:/app

# Redis 数据库服务
redis:
# 直接使用官方镜像
image: "redis:alpine"
# 没有暴露端口到宿主机,仅供内部网络访问
  • web 服务:基于当前目录下的 Dockerfile 构建一个 Web 应用镜像,并将容器内的 8000 端口映射到宿主机的 8000 端口。通过环境变量 REDIS_HOST=redis 告诉 Web 应用如何连接到 Redis(服务名 redis 在 Docker Compose 内部网络中会自动解析为对应容器的 IP)。它还设置了容器启动顺序依赖(depends_on),确保 Redis 服务先启动。另外通过卷挂载将当前代码目录挂载到容器的 /app,便于开发时实时更新代码。
  • redis 服务:使用官方的 redis:alpine 镜像,没有向宿主机暴露端口,仅供内部网络中的 Web 服务访问。
Icon
致谢名单
本作品由 LwhalE 于 2026-03-14 17:07:00 发布
作品地址:Docker
除特别声明外,本站作品均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自 LwhalE's blog
Logo