Docker Guide

在现代软件开发和部署中,Docker 作为一种容器技术,极大地简化了运行环境的搭建和应用程序的部署。与传统的虚拟机(VM)不同,Docker 容器利用宿主机的操作系统内核,而不是虚拟出一整套硬件和操作系统,从而实现轻量级和高效的资源利用。

安装

Windows 11

Windows 下的 Docker Desktop 运行于 WSL 上,会默认安装 WSL,并创建两个仅供 Docker 使用的 Linux 发行版(distro): docker-desktopdocker-desktop-data。其中,docker-desktop 用于运行 Docker engine,docker-desktop-data 用于存储 containers 和 images。

1
2
3
4
5
6
7
wsl --list -v # 查看已安装 Linux 发行版
NAME STATE VERSION
* Ubuntu Running 2
docker-desktop Running 2
docker-desktop-data Running 2

wsl -d docker-desktop # 登录 docker-desktop,注,docker-desktop-data 不能登录

WSL 发行版使用 ext4.vhdx 虚拟硬盘文件进行存储,在 %LOCALAPPDATA%/Docker/wsl 目录下。Docker Desktop 创建的 Linux 发行版也存储在此虚拟存储硬盘中,其中,docker-desktop 存储在 distro/ext4.vhdxdocker-desktop-data 存储在 data/ext4.vhdx

1
2
3
# 注销卸载这两个发行版
wsl --unregister docker-desktop-data
wsl --unregister docker-desktop
  • 迁移 docker-desktop-data

可将 docker-desktop-data 迁移至其它盘或其它机器。

1
2
3
4
# 导出 
# wsl --export <Distribution Name> <FileName>
wsl --export docker-desktop-data D:\docker\docker-desktop-data.tar
wsl --export docker-desktop D:\docker\docker-desktop.tar
1
2
3
4
5
6
7
8
9
# 导入 
# wsl --import <Distribution Name> <InstallLocation> <FileName>
# 导入至原本目录,%LOCALAPPDATA%/Docker/wsl/data
wsl --import docker-desktop-data D:\docker\data D:\docker\docker-desktop-data.tar --version 2
wsl --import docker-desktop D:\docker\docker-desktop D:\docker\docker-desktop.tar --version 2 # 一般只需迁移 docker-desktop-data

# 注意,如果发行版已存在,需先卸载,再导入
wsl --unregister docker-desktop
wsl --unregister docker-desktop-data

注意,在导出或导入前,需使用 wsl --shutdown 关闭所有 WSL 发行版,以确保虚拟磁盘未被使用。

配置

Docker 的配置文件为 daemon.json,在 C:\Users\用户名\.docker 目录下,可以直接编辑此文件,也可以在 Docker Desktop 中修改。

1
2
3
4
5
6
{
// 镜像源
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn" // 中科大源
]
}

基本命令

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
# 镜像仓库
docker login -u <user-name> -p <password> [host] # 登录 docker 镜像仓库,不指定 host,则默认为 docker hub 官方仓库

# 镜像操作
docker pull <contain-name> # 拉取镜像,不加 tag,则默认 latest 最新
docker push <image-name[:tag]> # 上传镜像至 docker 镜像仓库
docker search <contain-name> # 查找镜像
docker images <image-name> # 查看已拉取镜像
docker rmi <image-name> # 删除镜像
docker build -t <image-name[:tag]> <dockerfile-path> # 构建镜像

# 容器操作
docker run <image-name> # 运行容器(创建并启动)
docker start <contain> # 启动容器
docker stop <contain> # 停止容器
docker restart <contain> # 重启容器
docker ps # 查看正在运行容器,docker ps -a 查看所有容器
docker rm <contain> # 删除指定容器,docker rm `docker ps -a -q` 删除所有容器
docker exec -it <contain> /bin/bash # 进入容器终端,-it 即 -i (以交互模式运行容器) -t (为容器分配一个终端)
docker inspect <contain> # 查看容器信息
docker logs <contain> # 查看容器日志

# 数据卷操作
docker volume create <volume-name> # 创建数据卷
docker volume ls # 查看所有数据卷
docker volume inspect <volume-name> # 查看指定数据卷详情信息
docker volume rm <volume-name> # 删除数据卷
docker volume prune # 删除所有未使用数据卷

# 网络
docker network create [--driver=<bridge | host | overlay | ipvlan| macvlan | none>] [--gateway=<getway>] [--subnet=<subnet>] <network-name> # 创建网络,--driver 指定驱动程序类型,默认 bridge,--gateway 指定网关,--subnet 指定子网,gateway 和 subnet 不指定则会自动生成
docker network ls # 查看网络
docker network rm <network-name> # 删除网络
docker network prune # 删除所有未使用网络
docker network connect <network-name> <contain> # 将容器连接到指定网络
docker network disconnect <network-name> <contain> # 断开容器的网络
docker network inspect <network> # 查看网络信息

注:上面命令中 contain 可以是 contain-id 也可以是 contain-name

docker run

1
2
3
4
5
6
7
–-name:容器名,不指定则采用默认
-e:配置信息
-p:端口映射,":" 前为主机端口,之后为容器端口。使宿主机本地的应用程序能够访问容器内的服务
-d:后台运行容器,保证在退出终端后容器继续运行
-v:主机和容器的目录映射关系,":" 前为主机目录或数据卷,之后为容器目录。需要注意的是 docker-compose 中 volumes 支持相对路径,但 docker run -v 不支持,需写为绝对路径(可使用环境变量)
--network: 指定容器使用的网络模式
--restart:指定容器重启策略

执行 docker run 时,如果本地没有镜像,则会自动 pull 拉取。

注意:$(pwd)、`pwd`、$PWD 在 PowerShell 中返回的是 Windows 中反斜杠路径风格 D:\project\docker-data\wordpress,Git Bash 中虽然能返回左斜杆,但是路径不对,需要再加一个 /,即 //d/project/docker-data/wordpress

1
2
# Windows Git Bash 中添加 /,PowerShell 不用
docker run --name wordpress -v /$(pwd):/var/www/html -p 8080:80 -d wordpress

实践中为了方便管理、复用以及自动化,常将 docker run 保存为 *.sh *.ps1 Bash 或 PowerShell 脚本。

1
2
3
4
5
6
7
8
9
10
11
12
# bash run-mysql.sh or powershell run-mysql.ps1
docker run \
--name mysql \
-d \
-p 3306:3306 \
--restart unless-stopped \
-v /home/mysql/log:/var/log/mysql \ # 日志文件挂载
-v /home/mysql/data:/var/lib/mysql \ # 数据文件挂载
-v /home/mysql/conf/my.cnf:/etc/mysql/my.cnf \ # 配置文件挂载
-e TZ=Asia/Shanghai \ # 时区设置
-e MYSQL_ROOT_PASSWORD=123456 \ # MySQL root 用户密码
mysql:5.7.38 # 使用的 MySQL 镜像版本
1
2
3
4
5
# run-nginx.sh
docker run --name nginx -d -p 80:80 \
-v /home/user/nginx/html:/usr/share/nginx/html:ro \ # 静态资源文件挂载,ro 即 read-only 只读,表示容器内部只能读取宿主机文件不能修改
-v /home/user/nginx/conf.d:/etc/nginx/conf.d:ro \ # 配置文件挂载
nginx

docker build

docker build 命令用于根据 Dockerfile 创建 Docker 镜像。用来实现基础镜像的扩展和应用程序的发布。在实践中,通过 docker build 将应用程序代码(COPY)、依赖(RUN)和运行时环境(FROM)打包成一个镜像,方便应用程序的部署,还可以与 CI/CD 配合使用,实现自动化的镜像构建和部署。

1
docker build -t image-name:tag dockerfile-path # -t 指定要创建的目标镜像名及 tag(tag 默认为 latest),dockerfile-path 为 Dockerfile 所在目录

Dockerfile

Dockerfile 是 docker build 的描述文件,文本内容包含了一条条构建镜像所需的指令。常用指令有:

1
2
3
4
5
6
7
FROM: 指定基础镜像
WORKDIR: 设置工作目录。后续的指令(如 RUN、CMD、ENTRYPOINT 等)都将在这个目录下执行
ENV: 设置环境变量
COPY: 从构建主机复制文件到镜像中
RUN: 镜像构建时执行的命令
CMD: 指定容器默认执行的命令
EXPOSE: 指定容器暴露的端口

注:COPYWORKDIR 指令会自动创建目录,无需 RUN mkdir -p /test

以打包部署前端应用程序镜像为例,其 Dockerfiledefault.conf 配置如下所示:

1
2
3
4
FROM nginx:alpine
COPY dist /usr/share/nginx/html/
COPY ./default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
listen [::]:80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}
}
1
2
docker build -t geki:1.0.0 . # 使用当前目录的 Dockerfile 构建镜像
docker run --name geki -p 8080:80 -d geki:1.0.0

构建缓存 Build Cache

Docker 镜像是分层构建的,每个 Dockerfile 指令(如 FROMCOPYRUN 等)都会创建一个新的镜像层(Layer),这些层是只读的,且会被缓存。首次构建时,所有层均会执行,生成缓存,后续构建,如果 Docker 发现某一层已经构建过,并且前面的层没有发生变化,就会直接复用缓存,避免重复执行。

1
2
3
4
5
6
7
8
9
FROM node:18-alpine
WORKDIR /white-page
ENV NODE_ENV=production
COPY package.json pnpm-lock.yaml ./
RUN npm config set registry https://registry.npmmirror.com/ && npm i -g pnpm && pnpm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

注意:设置 .dockerignore 文件,否则 COPY . . 会拷贝 node_modules,不但慢,而且不利于缓存。

  • 优化缓存利用的原则

固定不变的内容放前面,容易变化的内容放后面,避免缓存层失效:

1
2
3
4
5
6
# 先安装依赖,利用缓存
COPY package.json package-lock.json ./
RUN npm install

# 再复制代码,减少影响
COPY . .

合并 RUN 命令,减少层数:

1
RUN apt-get update && apt-get install -y curl git

利用 .dockerignore 文件,避免不必要的文件影响 COPY 指令的缓存:

1
2
3
node_modules
.git
.dockerignore

多阶段构建

在 Dockerfile 中,多阶段构建(multi-stage build)是指使用多个 FROM 语句来构建镜像,每个 FROM 代表一个独立的构建阶段(Stage),主要意义是减少最终镜像的体积,并确保最终运行环境只包含必要的文件。AS <stage> 定义阶段名,,--from=<stage> 用来跨阶段复制文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /white-page
ENV NODE_ENV=production
COPY package.json pnpm-lock.yaml ./
RUN npm config set registry https://registry.npmmirror.com/ && \
npm i -g pnpm && \
pnpm install
COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine
WORKDIR /white-page
ENV NODE_ENV=production
COPY --from=builder /white-page/.output ./.output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

数据卷

Docker Volumes (数据卷) 用于解决容器的数据持久化和数据共享(宿主机到容器,容器间)问题。Docker Volumes 有三种类型:Volumes (卷)、Bind mounts (绑定挂载)、tmpfs mounts (临时挂载)

  • Volumes

Volumes 分为命名卷(Named Volumes)和匿名卷(Anonymous Volumes)。Linux 下默认存储在 /var/lib/docker/volumes/<卷名>,Windows 下的 Docker Volumes 则存储在 WSL 2 虚拟机的文件系统中。

命名卷的生命周期独立于容器,即使容器被删除,卷仍然存在,直到手动删除 (docker volume rm),而匿名卷的生命周期与容器绑定,当容器删除时,匿名卷也会被删除,除非使用 docker run --rm 运行临时容器。

1
2
3
4
5
# wsl
wsl
cd /mnt/wsl/docker-desktop-data/version-pack-data/community/docker/volumes
# PowderShell
cd \\wsl$\docker-desktop-data\version-pack-data\community\docker\volumes

匿名卷不指定卷名称,仅指定容器内的挂载路径,Docker 会自动分配随机卷名。-v 容器目录/文件

1
2
# 匿名卷挂载(仅指定容器内路径)
docker run -v /app/data my-image
1
2
3
4
5
6
# 创建命名卷
docker volume create my-named-volume
# 挂载到容器
docker run -v my-named-volume:/app/data my-image
# 指定卷的权限,限制容器内对挂载文件的修改权限
docker run -v my-named-volume:/app/data:ro my-image

命名卷适合数据持久化(比如数据库存储、日志存储)和跨容器数据共享(比如 Nginx 直接托管 Nuxt 静态资源,做动静分离)。“卷”虽然初始化配置稍微复杂一点,灵活性也不如“绑定挂载”,但它不依赖宿主机的文件系统,直接由 Docker 管理,具有更高的可移植性、安全性和 I/O 性能。

  • 绑定挂载

直接将宿主机的目录或文件挂载到容器中 -v 主机目录/文件:容器目录/文件。绑定挂载直接依赖宿主机的目录,Docker 不会管理它的生命周期,数据不会随容器删除而丢失。

1
docker run -d -v /home/user/data:/app/data my-image

绑定挂载灵活,简单,但依赖宿主机文件系统(ext4、xfs、NTFS…),可移植性差,如果主机文件发生变化,会影响容器运行,而且由于额外的抽象层,I/O 性能也可能不如 Docker 直接管理的卷。

绑定挂载通常用在本地开发环境中将本地代码目录挂载到容器以及共享配置(比如共享宿主机 Nginx 配置),实现热更新,方便开发调试。在生产环境中,应用程序代码(如 NodeJS、Nuxt 的业务代码)和配置(如 Nginx 配置)会通过自定义构建镜像,COPY 进镜像中,而不是通过数据卷挂载。数据卷通常用于存储动态数据,如数据库、日志等,而非静态的应用程序代码。

1
2
3
4
5
# 开发环境(Windows Git Bash 加 /)
docker run -d -p 80:80 \
-v /$(pwd)/dist:/usr/share/nginx/html \
-v /$(pwd)/default.conf:/etc/nginx/conf.d/default.conf \
--name nginx-test nginx:alpine
1
2
3
4
5
# 生产环境
FROM nginx:alpine
COPY dist /usr/share/nginx/html/
COPY ./default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

注意:挂载主机目录,docker run -v 相对路径是相对于命令执行的目录,即当前 shell 终端所在的路径(pwd),docker-composer.yml 中相对路径则相对于 docker-compose.yml 文件所在的目录。

  • 临时挂载

只适用于 Linux,数据存储在内存中,不会写入磁盘,容器停止后数据会丢失。适用于存储临时数据(如 session、缓存)。

1
docker run -d --tmpfs /app/data:rw,size=64m my-image

TODO:Docker Volumes 数据的迁移、备份、恢复

网络

Docker 安装完成后会自动创建三个网络,其中 name 为 bridge 的网络使用 bridge 驱动,是容器的默认网络。

1
2
3
4
5
6
7
C:\Users\tracy>docker network ls
NETWORK ID NAME DRIVER SCOPE
95f596f81598 bridge bridge local
065201a9720e host host local
20ae04041c1c none null local

C:\Users\tracy>docker network inspect bridge
  • 创建自定义网络
1
docker network create [--driver=<bridge | host | overlay | ipvlan| macvlan | none>] [--gateway=<gateway>] [--subnet=<subnet>] [network-name]

Docker 的几种网络模式:

1
2
3
4
5
bridge 桥接模式,是默认的网络驱动程序,用于多个容器在同一个宿主机上通信
host 主机模式,不创建任何网络接口,直接使用宿主机的 ip 地址与外界进行通信,不再需要额外的 NAT 转换。(host 模式下,无需指定 -p 端口映射)
none 无网络模式,不为容器进行任何网络配置。容器没有网卡、ip、路由等信息,只有一个lo接口,无法与外界通信
overlay 模式,网络基于 Linux 网桥和 Vxlan,实现跨主机的容器通信
macvlan 模式,用于跨主机通信场景

默认情况下,Docker 容器会使用 bridge 网络,各个容器可以通过桥接网络(即 docker0 网桥)访问彼此,但只有通过明确的链接或自定义网络,容器才能知道彼此的存在。

1
2
# 创建自定义网络
docker network create mynetwork
  • 指定自定义网络
1
2
3
4
# 创建一个桥接类型的网络
docker network create --driver=bridge test-net
# 创建并运行容器时指定网络
docker run --network test-net
1
2
# 为已存在的容器指定网络
docker network connect test-net [container-name]
1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用 docker-compose 给一组容器指定网络
# docker-compose 编排的一组容器会默认创建一个网络,这组容器全部都会加入到此网络中。也可使用 `networks` 字段自定义网络
# docker-compose.yaml
version: "3"
services:
s1:
networks:
- test
s2:
networks:
- test
networks:
test:

Docker Compose

Docker Compose 是 Docker 提供的单机容器编排工具,用来定义和运行多个容器。Docker Compose 使用一个名为 docker-compose.yml 的 YAML 文件来配置应用程序的服务,这个文件中定义了一系列 docker volumedocker networkdocker builddocker run 的组合,从而使得管理和编排多个容器变得更加方便。

docker-compose.yaml 配置说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
service-name: 
image: 指定镜像
container_name: 设置容器名称,即 -name
environment: 设置环境变量,即 -e
volumes: 挂载宿主机目录或命令卷到容器,即 -v
ports: 端口映射,即 -p
restart: 设置重启策略,no,always,no-failure,unless-stopped,即 -restart
depends_on: 设置依赖关系和启动顺序
networks: 指定已定义网络
build: 指定构建配件
context: 指定 Dockerfile 文件所在的路径,即 path
dockerfile: 指定 Dockerfile 的名称,即 -f
volumes: 定义数据卷,即 docker volume create volume-name
networks: 定义网络

docker-compose 常用命令:

1
2
3
4
5
6
7
8
9
docker-compose build # 构建镜像(当前 docker-compose 下的所有服务)。如果 docker-compose.yml 中已配置 build 字段,通常不需要单独运行 docker-compose build,首次 docker-compose up -d 时,会自动构建镜像
docker-compose [-p <compose-name>] [-f <configuration-file>] up -d # 构建镜像并启动容器,-p 指定 compose-name,默认为文件夹名,-f 指定配置文件,默认为当前目录 docker-compose.yaml,--build 强制重新构建镜像
docker-compose [-p <compose-name>] down # 停止并删除 Docker Compose 管理的容器、网络和匿名卷,-v 参数还会删除命名卷(但不会删除外部挂载卷)
docker-compose ps # 列出所有运行容器
docker-compose start # 启动服务
docker-compose stop # 停止服务

docker-compose restart # 重启服务
docker-compose logs # 日志

常用的 docker-compose,lnmp, wordpress,更多查看此项目 awesome-compose

私有镜像仓库

1
2
3
4
5
6
# 启动私有镜像仓库
docker run -d -p 5000:5000 --name registry registry:2
# 登录私有镜像仓库
docker login localhost:5000 # 在没有配置身份验证的情况下,Docker 会接受任何用户名和密码组合。指定用户名密码 docker login -u ${user-name} -p ${password} localhost:5000
# 退出登录
docker login localhost:5000 # 不指定仓库地址,默认官方 https://index.docker.io/v1/
1
2
3
4
5
6
7
8
9
10
11
12
# 构建镜像
docker build -t <image-name>:<tag> . # docker build -t tracyblog:1.0.0 .
# 标记镜像
docker tag <image-name>:<tag> <registry-domain>/<image-name>:<tag> # docker tag 命令用于将本地镜像标记为特定的仓库地址。如果不使用 docker tag,Docker 不会知道你要将哪个本地镜像推送到哪个远程仓库。docker tag tracyblog:1.0.0 localhost:5000/tracyblog:1.0.0
# 构建完整仓库地址的镜像,这样就不用在 push 前额外打标签
docker build -t <registry-domain>/<image-name>:<tag> . # docker build -t localhost:5000/tracyblog:1.0.0 .
# 推送镜像
docker push <registry-domain>/<image-name>:<tag> # docker push localhost:5000/tracyblog:1.0.0
# 拉取镜像
docker pull <registry-domain>/<image-name>:<tag> # docker pull localhost:5000/tracyblog:1.0.0
# 启动容器
docker run --name <contain-name> -p 8080:80 -d <registry-domain>/<image-name>:<tag> # docker run --name tracyblog -p 8080:80 -d localhost:5000/tracyblog:1.0.0

注:registry-domain 不能带 http://https://,另外,直接在 docker build 命令中指定镜像的完整名称和标签(比如,docker build -t localhost:5000/tracyblog:1.0.0 .),这样在构建镜像时就已经给镜像打上了标签,docker tag 步骤可省略。

1
2
3
# 验证镜像
curl <registry-domain>/v2/_catalog # 比如 http://localhost:5000/v2/_catalog
curl <registry-domain>/v2/<image-name>/tags/list # curl http://localhost:5000/v2/tracyblog/tags/list
  • 用户认证
1
2
3
# 使用 htpasswd 工具生成认证文件
mkdir auth
docker run --entrypoint htpasswd httpd:2 -Bbn abc 123 > auth/htpasswd # registry 只支持 bcrypt 格式的密钥,所以需要带上 -B 参数

注意:Windows PowerShell 中使用 > 重定向符号时,默认会将输出保存为 UTF-16 LE (带 BOM) 编码。而 htpasswd 工具生成的密码文件要求是 ASCII 或 UTF-8 (无 BOM) 编码。PowerShell 中应使用以下方式生成。

1
docker run --rm --entrypoint htpasswd httpd:2 -Bbn abc 123 | Set-Content -Encoding ASCII auth/htpasswd
1
2
3
4
5
6
7
8
9
10
11
# 运行带有身份验证的私有 Registry
docker run -d `
--restart always `
--name registry `
-p 5000:5000 `
-e REGISTRY_AUTH=htpasswd `
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd `
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" `
-v "${PWD}/data:/var/lib/registry" `
-v "${PWD}/auth:/auth" `
registry:2

注意:上述命令在 Windows Git Bash 上执行时,需添加 /,即 /${PWD}/$(pwd)。另外,Git Bash 会自动将 REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd 转为 REGISTRY_AUTH_HTPASSWD_PATH=C:/Program Files/Git/auth/htpasswd,改为 ./auth/htpasswd 则不转换。

1
2
3
4
5
6
7
8
9
10
docker run -d \
--restart always \
--name registry \
-p 5000:5000 \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_PATH=./auth/htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-v "/${PWD}/data:/var/lib/registry" \
-v "/${PWD}/auth:/auth" \
registry:2
1
2
3
4
5
# 验证环境变量设置
docker exec -it registry sh
env | grep REGISTRY_AUTH_HTPASSWD_PATH # 合并一行 docker exec registry env | grep REGISTRY_AUTH_HTPASSWD_PATH
# 验证挂载
docker exec -it registry sh -c "cat /auth/htpasswd"

访问 http://localhost:5000/v2/curl -u abc:123 http://localhost:5000/v2/,或者 docker login 测试登录。

docker-compose.yml 是更好的选择,不用考虑 Windows 路径规范和自动路径转换的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '3.8'

services:
registry:
restart: always
image: registry:2
ports:
- 5000:5000
environment:
# REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
# REGISTRY_HTTP_TLS_KEY: /certs/domain.key
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
volumes:
- ./data:/var/lib/registry
# - ./certs:/certs
- ./auth:/auth