什么是 Docker

在我看来, Docker 是一个为了方便而存在的东西, 通过容器化运行来轻松地移植自己的程序到不同平台, 从而避免在不同 Linux 发行版中的各种问题. 至于 Windows 上的 Docker 其实本质上还是一个 Linux, 是由 WSL 来实现的.

但是不同于虚拟机, 对于容器而言主进程就是它的全部. 即容器是为了运行主进程而存在的, 主进程退出容器也就没有存在的意义了, 比如一个 nginx 容器的 nginx 进程挂掉了, 那么这个容器其实也就挂掉了.

Podman 的替代品罢了
吐槽一句, Podman Desktop 真难用, 当然 Docker 那个 GUI 也没好到哪里去. 除了能够在后台给 WSL2 保活不知道还有什么用.
我也不关心什么漏洞风险, 我只是基于其他镜像编译的, 那是他们该操心的事.

我的环境: 本地和服务器都安装 docker, 同时服务器还做私有存储但不负责编译, 本地主要负责编译和测试, 运行的容器很少. (因为服务器太弱导致其实本地更快, 哪怕是 E5 都不会差的.

安装 Docker

一般来说软件源自己都会有, 直接安装就好了. 但是如果需要最新版本可以选择按照官方的安装文档来, 比如 Debian 官方源就落后了 Arch 四个大版本, 如果是 CentOS 可能还要再落后四个.

这是我安装的软件包

$ yay -Qs docker                                                                                                                                                                                                                                                           
local/docker 1:24.0.7-1
    Pack, ship and run any application as a lightweight container
local/docker-buildx 0.12.0-1
    Docker CLI plugin for extended build capabilities with BuildKit
local/docker-compose 2.23.3-1
    Fast, isolated development environments using Docker

拉取镜像

拉取命令, 其中 registry 默认为 docker.io, tag 默认为 latest, 部分官方镜像只需要一个 repository.

docker pull registry/username/repository:tag
docker pull nginx

一些可能用到的镜像

  • olbat/cupsd
  • zoeyvid/curl-quic
  • adolfintel/speedtest
  • cloudreve/cloudreve
  • nextcloud
  • alpine

运行容器

要想运行容器首先需要拉取对应的镜像, 如果指定镜像不在本地会自动下载, 但还是推荐先手动拉取. 拉取命令在官网对应镜像页面上都有.

Docker 有很多命令和参数, 绝大部分其实我都用不上, 下面给出几条示例解释参数.

docker run --rm -it --pull always --name curl zoeyvid/curl-quic --http3 -sL https://cloudflare-quic.com
docker run -d --name=wxedge --restart=always --privileged --net=host  --tmpfs /run --tmpfs /tmp -v ./:/storage:rw -e LISTEN_ADDR="0.0.0.0:18888" onething1/wxedge:latest
docker run -it --name test-env alpine:3.18.4 /bin/sh
docker compose up -d -f path/to/xxx.yaml
  • --rm 停止容器时自动删除
  • -it 进入交互式环境
  • -pull always 强制拉取远程镜像, 确保版本最新
  • --name 给镜像命名
  • zoeyvid/curl 镜像名, 完整的应该是 docker.io/zoeyvid/curl:latest
  • -d 后台运行, 否则会在当前终端运行
  • --restart=always 定义重启容器策略
  • --net=host 否则是桥接 (bridge)
  • -v 挂载卷映射
  • -e 设置环境变量, 具体作用需要看镜像说明
  • -p 端口映射
  • 其他 需要传递给主进程的参数, 如 --http3 -sL https://cloudflare-quic.com

映射格式为 host:container, 可能是端口也可能是绝对 / 相对路径.

Docker compose

可以理解为 docker run 命令的文件形式, 我更喜欢用这种方法配置.
compose 和用 docker 直接起的服务不太一样, 而且配置一些持久化存储和网络互通更简单一些.

文件名可以为 docker-compose 或者 compose, 拓展名可以为 yaml 或者 yml.

利用 docker 编译并导出 docker build --output=. --target=server .

Docker build

buildx 是新一代 build, 既然有就顺便安上了. 最好设置一下这个环境变量

export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1

通过 Dockerfile 来构建自己的镜像, 配合 GitHub action 可以实现自动构建.

记一下自己踩过的坑:

  • RUN 关键词实际上是等同 (ba)sh -c, 在批量创建文件夹时遇到的坑.
  • 以 alpine 为例, 编译环境和使用环境的包不同, 一个要用 xx-dev 一个是 xx 本身.

常用命令

删除废弃无名镜像

docker image rm $(docker image ls -q --filter "dangling=true")

直接上传本地镜像至其他设备

docker save docker.io/sickcodes/docker-osx:auto | ssh kazusa@10.0.0.1 "docker load"

先导出再导入

docker save -o ./img.tar image:tag
docker load -i ./img.tar

搭建私有 registry

由于各种原因, 可能会需要使用私有仓库.

搭建过程很简单, 可以选择 官方镜像 或者 nexus3 然后再配置反代, 这里选择 nexus3.

docker pull sonatype/nexus3:latest

然后创建 compose 文件, 内容如下:

version: '3'

services:
  nexus3:
    image: sonatype/nexus3:latest
    container_name: registry
    ports:
      - 8081:8081
      - 5000:5000
    restart: always
    volumes:
      - nexus-data:/nexus-data

volumes:
  nexus-data:

第一次初始化需要半分钟左右, 可以查看日志等待初始化完成后获取初始密码.

docker compose up -d
docker run registry cat /nexus-data/admin.password

拿到密码后访问 8081 端口登录管理面板, 在 IP:port/#admin/repository/repositories 创建 docker (hosted)
compose 已经映射了 5000 端口这里直接在 HTTP 填上就好, 其他的自行调整.

这里也可以选择 HTTPS 但我习惯服务不配置证书统一反代.
在 nginx 中新增一个 server 块 (由于我使用泛域名证书所以写到外面全局配置了):

ssl_certificate cert/kazusa.cc.fullchain.cer;
ssl_certificate_key cert/kazusa.cc.key;

ssl_session_tickets on;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:10m;
ssl_stapling on;
ssl_protocols SSLv2 SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:!aNULL:!MD5;
add_header Strict-Transport-Security "max-age=31536000";

server {
    listen 443 ssl;
    server_name registry.kazusa.cc;

    location / {
        proxy_set_header Via "nginx";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_pass http://127.0.0.1:5000;

        client_max_body_size 40960M;
    }
}

重载反代服务器并登录 docker 开始第一次 push, 账号密码和面板一样.

sudo nginx -s reload
docker login https://<your domain>

$ docker push registry.kazusa.cc/nginx:alpine                                                                                                                                          
The push refers to repository [registry.kazusa.cc/nginx]
f0392c660f3e: Pushed
e313a0517f2a: Pushed
2f2246c72ea3: Pushed
392d6e8abdf2: Pushed
48a7a583ea2a: Pushed
bd7ccbfbb1d0: Pushed
56c78c952066: Pushed
5967fbd27f44: Pushed
186ce2d777be: Pushed
9fe9a137fd00: Pushed
alpine: digest: sha256:cf222eb6175ea47d19784adb88fc6b787411fed8eba536a8b8e80426e1f6bdcd size: 2417

接下来使用上是一样的, 如果设置了非匿名也需要登录才能拉取镜像.

关于 Podman

简单来说将 docker 命令开头的 docker 替换为 podman 就可以直接平替使用, 但就使用体验而言个人觉得还是 docker 更方便和干净一些 (指 /etc 下目录, 子卷其实脏的很)
它们只是底层实现逻辑不同, 但在用户的使用层面上可以说完全相同.