跳到主要内容

7 篇博文 含有标签「docker」

查看所有标签

Docker 离线安装实战:从入门到精通的踩坑指南

· 阅读需 6 分钟
Gemini
Google Gemini
1adybug
子虚伊人

以下内容均为 Gemini 2.5 Pro Deep Research 生成

在物理隔离或网络受限的环境中部署 Docker,是许多系统管理员面临的共同挑战。这不仅仅是下载一个安装包那么简单,而是一个涉及依赖解析、环境差异和软件包冲突的系统工程。本文将通过一个完整的实战案例,带您走过在类 RHEL 系统(如 CentOS, RHEL, openEuler)上离线安装 Docker 的每一步,并详细记录我们遇到的每一个错误、分析其根本原因,最终给出精准的解决方案。这不仅是一份操作指南,更是一份宝贵的排错实录。

第一阶段:在线准备——收集所有必要"弹药"

我们的第一步是在一台可以访问互联网的"在线"计算机上,下载 Docker Engine 及其所有的依赖项。

第1步:添加 Docker 官方软件仓库

在全新的系统中,软件包管理器(如 dnf)并不知道从哪里可以找到 Docker 的官方社区版(docker-ce)。如果我们直接尝试下载,就会遇到第一个错误:

初始错误 #1: No package docker-ce available

# dnf download --resolve docker-ce...
Error: No package docker-ce available.

解决方案:我们需要手动将 Docker 的官方仓库地址添加到系统的配置中。这样,dnf 才能索引到相关的软件包。

# 在您的在线计算机上执行
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

第2步:安装 dnf 下载插件

在某些精简安装的系统中,dnf 的 download 功能可能不是内置的,而是通过插件提供。这时,您可能会遇到第二个报错:

初始错误 #2: No such command: download

# dnf download --resolve docker-ce...
No such command: download. Please use /usr/bin/dnf --help
It could be a DNF plugin command, try: "dnf install 'dnf-command(download)'"

解决方案:报错信息已经给出了明确的指引。我们需要安装提供这个命令的插件包,它通常是 dnf-plugins-core 1。

# 在您的在线计算机上执行
sudo dnf install 'dnf-command(download)'

第3步:下载 Docker 及其完整依赖树

准备工作就绪,现在我们可以正式下载所有需要的 .rpm 文件了。然而,即使仓库配置正确,我们仍然可能遇到由系统版本差异导致的下载失败。

初始错误 #3: Status code: 404 for... repomd.xml

Errors during downloading metadata for repository 'docker-ce-stable':
- Status code: 404 for https://download.docker.com/linux/centos/24.03LTS_SP1/x86_64/stable/repodata/repomd.xml
Error: Failed to download metadata for repo 'docker-ce-stable'

这个错误告诉我们,dnf 尝试访问的 URL 是无效的。它自动将您系统的特定版本号(如 24.03LTS_SP1)拼接到了 URL 中,但 Docker 的仓库是按主版本号(如 8 或 9)组织的 3。

解决方案:我们需要在命令中强制指定一个与 Docker 仓库结构兼容的发行版主版本号,使用 --releasever 标志即可 4。对于大多数现代系统,这个值是 9。

# 在您的在线计算机上执行
# 创建一个目录来存放所有软件包
mkdir docker-rpm-packages
cd docker-rpm-packages

# 执行最终的下载命令
sudo dnf download --releasever=9 --resolve docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

执行成功后,您的 docker-rpm-packages 目录里会装满所有离线安装所需的 .rpm 文件。至此,在线准备阶段圆满完成。

第二阶段:离线安装——直面冲突与挑战

现在,我们将准备好的 docker-rpm-packages 目录通过 U 盘等物理介质,完整地复制到目标离线服务器上。

第4步:执行安装

进入离线服务器上的软件包目录,我们开始执行安装。

关键错误 #4: 文件冲突

# sudo dnf install *.rpm --disablerepo=*
...
file /usr/bin/docker-proxy from install of docker-ce... conflicts with file from package libnetwork...

这是一个非常典型的冲突。错误信息表明,我们尝试安装的官方 docker-ce 包,与离线系统上一个已存在的 libnetwork 包(通常是操作系统自带的容器网络组件)发生了文件冲突 5。

第一次尝试:使用 --allowerasing

dnf 提供了一个强大的参数 --allowerasing,用于授权它在解决冲突时自动移除旧的、有冲突的软件包 4。这是解决此类问题的首选标准方法。

# 在您的离线服务器上执行
sudo dnf install *.rpm --disablerepo=* --allowerasing

然而,在某些复杂的系统环境中(如此次实战中的 openEuler),即使添加了这个参数,dnf 在最后的"事务测试"阶段仍然可能因为无法安全地自动解决冲突而报错退出。

关键错误 #5: 事务测试失败

# sudo dnf install *.rpm --disablerepo=* --allowerasing
...
Running transaction test
Error: Transaction test error:
file /usr/bin/docker-proxy from install of docker-ce... conflicts with file from package libnetwork...

最终解决方案:手动移除冲突根源

当自动化的解决方案失效时,我们需要进行更精确的手动干预。既然 dnf 无法安全地为我们移除 libnetwork,那我们就亲自动手。首先,手动卸载引发冲突的软件包。移除这个包是安全的,因为我们即将安装的 docker-ce 会提供功能完整且版本匹配的替代品 8。

# 在您的离线服务器上执行
sudo dnf remove libnetwork

dnf 可能会提示此操作会一并移除其他依赖包,确认即可。然后,再次执行安装命令。在清除了最主要的障碍后,我们再次运行带有 --allowerasing 的安装命令,以处理任何其他潜在的次要冲突。

# 在您的离线服务器上执行
sudo dnf install *.rpm --disablerepo=* --allowerasing

这一次,安装过程畅通无阻,成功完成。

第三阶段:启动与验证

安装成功后,我们只需启动并验证 Docker 服务。

启动并设置开机自启:

sudo systemctl enable --now docker

验证运行状态:

sudo systemctl status docker

运行 "Hello World":

由于是离线环境,您需要先将在在线计算机上用 docker save 打包好的镜像(例如 hello-world.tar)传输过来,然后用 docker load 导入。

# 加载镜像
sudo docker load -i /path/to/hello-world.tar

# 运行容器
sudo docker run hello-world

看到熟悉的欢迎信息,标志着本次高难度的离线 Docker 部署任务圆满成功。

总结

离线安装 Docker 的过程充满了挑战,但每一个错误都为我们揭示了其底层的运作机制。通过这次实战,我们不仅学会了如何准备离线包,更掌握了一套从分析错误到解决问题的系统性方法,特别是如何处理顽固的软件包冲突。希望这份详尽的踩坑指南,能为您在未来的工作中扫清障碍。

自托管 Next.js

· 阅读需 6 分钟
1adybug
子虚伊人

使用 Next.js 开发网站后,在自己的服务器上部署比较麻烦,有两种比较简单的解决方案:

第一种不用过多介绍,这里主要介绍第二种方案:

  1. next.config.ts 中添加 output: "standalone"

  2. 安装 Docker

  3. 在项目根目录下创建 Dockerfile 文件

    # syntax=docker.io/docker/dockerfile:1

    FROM node:22-alpine AS base

    # Install dependencies only when needed
    FROM base AS deps

    WORKDIR /app

    # Install dependencies based on the preferred package manager
    COPY package.json ./
    RUN npm install --registry=https://registry.npmmirror.com

    # Rebuild the source code only when needed
    FROM base AS builder
    WORKDIR /app
    COPY --from=deps /app/node_modules ./node_modules
    COPY . .

    # Next.js collects completely anonymous telemetry data about general usage.
    # Learn more here: https://nextjs.org/telemetry
    ENV NEXT_TELEMETRY_DISABLED=1

    RUN npx prisma generate
    RUN npm run build

    # Production image, copy all the files and run next
    FROM base AS runner
    WORKDIR /app

    ENV NODE_ENV=production
    # Uncomment the following line in case you want to disable telemetry during runtime.
    ENV NEXT_TELEMETRY_DISABLED=1

    COPY --from=builder /app/public ./public

    # Automatically leverage output traces to reduce image size
    # https://nextjs.org/docs/advanced-features/output-file-tracing
    COPY --from=builder /app/.next/standalone ./
    COPY --from=builder /app/.next/static ./.next/static

    EXPOSE 3000

    ENV PORT=3000

    # server.js is created by next build from the standalone output
    # https://nextjs.org/docs/pages/api-reference/config/next-config-js/output
    ENV HOSTNAME="0.0.0.0"

    CMD ["node", "server.js"]
  4. 在项目根目录下创建 .dockerignore 文件,主要内容应该和 .gitignore 类似,忽略一些不需要的文件,比如 node_modules.next 目录之类的

  5. package.json 中添加 "build:docker": "docker build -t your-app-name ."

Dockerfile 中可以看出,自托管的核心就是 standalone 模式,这个模式下,Next.js 会生成一个 server.js 文件,这个文件是 Next.js 的入口文件,会自动监听 3000 端口,并启动 Next.js 应用。

其实,本质上最重要的产物就是 .next/standalone 目录,这个目录就是最终的根目录。所以最终目录结构应该是这样的:

.next/standaloneapp

.next/staticapp/.next/static

publicapp/public

只要明白了这个原理,我们就可以再实现一个 Next.js 的“安装”程序:

在项目根目录下创建一个 scripts/createInstaller.ts 文件,内容如下:

import { readdir, rm, writeFile } from "fs/promises"
import { resolve } from "path"

import { spawnAsync, zip } from "soda-nodejs"

const reg = /^--target=(windows|linux)$/

const target = (process.argv.find(item => reg.test(item))?.match(reg)?.[1] ?? "windows") as "windows" | "linux"

await rm("scripts/install.ts", { force: true })

await spawnAsync("bunx prisma generate", { shell: true, stdio: "inherit" })

await spawnAsync("bun run build:standalone", { shell: true, stdio: "inherit" })

const input = await readdir(".next/standalone")

await zip({ input, output: "../standalone.zip", cwd: ".next/standalone" })

const input2 = await readdir(".next/static")

await zip({ input: input2, output: "../static.zip", cwd: ".next/static" })

const input3 = await readdir("public")

await zip({ input: input3, output: "../.next/public.zip", cwd: "public" })

const script = `import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises"
import { join, parse, resolve } from "path"
import { Readable } from "stream"
import { ReadableStream } from "stream/web"
import { styleText } from "util"
import { file } from "bun"
import { unzip } from "soda-nodejs"

import publicPath from "../.next/public.zip" with { type: "file" }
import standalonePath from "../.next/standalone.zip" with { type: "file" }
import staticPath from "../.next/static.zip" with { type: "file" }

${target === "windows" ? `import windowsPath from "../prisma/generated/query_engine-windows.dll.node" with { type: "file" }` : `import debianPath from "../prisma/generated/libquery_engine-debian-openssl-3.0.x.so.node" with { type: "file" }`}

const from = \`${resolve(".").replace(/\\/g, "\\\\")}\`.replace(/[\\\\/]$/, "")
const to = resolve(".").replace(/^[a-zA-Z]+/, m => m.toUpperCase()).replace(/[\\\\/]$/, "")
const from2 = encodeURIComponent(from)
const to2 = encodeURIComponent(to)
const from3 = encodeURIComponent(from + "/")
const to3 = encodeURIComponent(to + "/")
const from4 = encodeURIComponent(from + "\\\\")
const to4 = encodeURIComponent(to + "\\\\")

function escapeRegExp(str: string) {
return str.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&")
}

const reg = new RegExp(
\`\${escapeRegExp(from.replace(/[\\\\/]/g, "__PLACEHOLDER__")).replace(/__PLACEHOLDER__/g, "\\\\\\\\{0,3}[\\\\\\\\/]")}(\\\\\\\\{0,3}[\\\\\\\\/])?\`,
"gi",
)

async function replacePath(dir: string) {
const { name } = parse(dir)
if (name === "node_modules") return
const files = await readdir(dir)
for (const file of files) {
const path = join(dir, file)
const status = await stat(path)
if (status.isDirectory()) {
await replacePath(path)
} else {
if (/\\.([mc]?js|json)$/i.test(path)) {
const content = await readFile(path, "utf-8")
const newContent = content
.replace(reg, (m, p) => {
const prefix = m.match(/(\\\\{0,3})[\\\\/]/)?.[1] ?? ""
const split = \`\${prefix}\${m.includes("/") ? "/" : "\\\\"}\`
return \`\${to.replace(/[\\\\/]/g, split)}\${p ? split : ""}\`
})
.replaceAll(from2, to2)
.replaceAll(from3, to3)
.replaceAll(from4, to4)
await writeFile(path, newContent)
}
}
}
}

async function main() {
const publicStream = Readable.fromWeb(file(publicPath).stream() as ReadableStream)
const standaloneStream = Readable.fromWeb(file(standalonePath).stream() as ReadableStream)
const staticStream = Readable.fromWeb(file(staticPath).stream() as ReadableStream)
${target === "windows" ? `const windowsStream = Readable.fromWeb(file(windowsPath).stream() as ReadableStream)` : `const debianStream = Readable.fromWeb(file(debianPath).stream() as ReadableStream)`}

await rm(".temp", { recursive: true, force: true })
await mkdir(".temp", { recursive: true })

await writeFile(".temp/public.zip", publicStream)
await writeFile(".temp/standalone.zip", standaloneStream)
await writeFile(".temp/static.zip", staticStream)

await unzip({ input: ".temp/public.zip", output: ".temp/public" })
await unzip({ input: ".temp/standalone.zip", output: ".temp/standalone" })
await unzip({ input: ".temp/static.zip", output: ".temp/static" })

const dir = await readdir(".temp/standalone")

for (const item of dir) {
await rm(item, { recursive: true, force: true })
await rename(\`.temp/standalone/\${item}\`, item)
}

await rm("public", { recursive: true, force: true })
await rename(".temp/public", "public")
await rm(".next/static", { recursive: true, force: true })
await rename(".temp/static", ".next/static")

await replacePath(to)

await mkdir("prisma/generated", { recursive: true })
await ${target === "windows" ? `writeFile("prisma/generated/query_engine-windows.dll.node", windowsStream)` : `writeFile("prisma/generated/libquery_engine-debian-openssl-3.0.x.so.node", debianStream)`}

await rm(".temp", { recursive: true, force: true })

console.log(styleText("greenBright", "Task completed, the program will exit in 3 seconds..."))

setTimeout(() => 0, 3000)
}

main()
`

await writeFile("scripts/install.ts", script)

await spawnAsync(`bun build --compile --target=bun-${target}-x64 --minify --sourcemap --bytecode scripts/install.ts --outfile installer`, {
shell: true,
stdio: "inherit",
})

await rm("scripts/install.ts", { force: true })

await rm(".next/standalone.zip", { force: true })

await rm(".next/static.zip", { force: true })

await rm(".next/public.zip", { force: true })

package.json 中添加以下命令:

{
"scripts": {
"build:standalone": "npx cross-env NEXT_OUTPUT=standalone next build",
"build:windows": "bun scripts/createInstaller.ts --target=windows",
"build:linux": "bun scripts/createInstaller.ts --target=linux"
}
}

因为我的项目中涉及到 Prisma ,所以需要生成 Prisma 的客户端,所以需要先执行 prisma generate 命令,然后执行 build:standalone 命令,生成 standalone 模式下的 Next.js 应用。又因为最终的平台涉及 WindowsLinux ,所以需要生成两个版本的 .node 文件,需要在 schema.prisma 中添加如下内容:

generator client {
binaryTargets = ["windows", "debian-openssl-3.0.x"]
}

原理也很简单,就是将产物都使用 bun 打包,执行时再释放出来。

在 Docker 中使用 Clash

· 阅读需 6 分钟
1adybug
子虚伊人

在图形化的操作系统中,我们可以使用 Clash Verge 来使用代理,但是在服务器上,就没有这么方便了。这时候我们可以使用 Clash 的核心 mihomo 来实现代理。然而,mihomo 的安装也是比较麻烦,所以我们可以使用 Docker 来安装 mihomo

安装教程

  1. 安装 Docker

  2. 创建 config.yaml 文件

    # url 里填写自己的订阅,名称不能重复
    proxy-providers:
    <你的订阅名称>:
    url: "<你的订阅链接>"
    type: http
    interval: 86400
    health-check: { enable: true, url: "https://www.gstatic.com/generate_204", interval: 300 }

    proxies:
    - name: "直连"
    type: direct
    udp: true

    mixed-port: 7890
    ipv6: true
    allow-lan: true
    unified-delay: false
    tcp-concurrent: true
    external-controller: 0.0.0.0:9090
    external-ui: ui
    external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
    # 如果你的端口开放了所有 IP 访问,建议取消注释 secret 并设置密码
    # secret: <你的密码>

    geodata-mode: true
    geox-url:
    geoip: "https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat"
    geosite: "https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
    mmdb: "https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb"
    asn: "https://mirror.ghproxy.com/https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb"

    find-process-mode: strict
    global-client-fingerprint: chrome

    profile:
    store-selected: true
    store-fake-ip: true

    sniffer:
    enable: true
    sniff:
    HTTP:
    ports: [80, 8080-8880]
    override-destination: true
    TLS:
    ports: [443, 8443]
    QUIC:
    ports: [443, 8443]
    skip-domain:
    - "Mijia Cloud"
    - "+.push.apple.com"

    tun:
    enable: true
    stack: mixed
    dns-hijack:
    - "any:53"
    - "tcp://any:53"
    auto-route: true
    auto-redirect: true
    auto-detect-interface: true

    dns:
    enable: true
    ipv6: true
    enhanced-mode: fake-ip
    fake-ip-filter:
    - "*"
    - "+.lan"
    - "+.local"
    - "+.market.xiaomi.com"
    default-nameserver:
    - tls://223.5.5.5
    - tls://223.6.6.6
    nameserver:
    - https://doh.pub/dns-query
    - https://dns.alidns.com/dns-query

    proxy-groups:
    - name: 默认
    type: select
    proxies: [自动选择, 直连, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点]

    - name: Google
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: Telegram
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: Twitter
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: 哔哩哔哩
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: 巴哈姆特
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: YouTube
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: NETFLIX
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: Spotify
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: Github
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    - name: 国内
    type: select
    proxies: [直连, 默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择]

    - name: 其他
    type: select
    proxies: [默认, 香港, 台湾, 日本, 新加坡, 美国, 其它地区, 全部节点, 自动选择, 直连]

    #分隔,下面是地区分组
    - name: 香港
    type: select
    include-all: true
    exclude-type: direct
    filter: "(?i)港|hk|hongkong|hong kong"

    - name: 台湾
    type: select
    include-all: true
    exclude-type: direct
    filter: "(?i)台|tw|taiwan"

    - name: 日本
    type: select
    include-all: true
    exclude-type: direct
    filter: "(?i)日|jp|japan"

    - name: 美国
    type: select
    include-all: true
    exclude-type: direct
    filter: "(?i)美|us|unitedstates|united states"

    - name: 新加坡
    type: select
    include-all: true
    exclude-type: direct
    filter: "(?i)(新|sg|singapore)"

    - name: 其它地区
    type: select
    include-all: true
    exclude-type: direct
    filter: "(?i)^(?!.*(?:🇭🇰|🇯🇵|🇺🇸|🇸🇬|🇨🇳|港|hk|hongkong|台|tw|taiwan|日|jp|japan|新|sg|singapore|美|us|unitedstates)).*"

    - name: 全部节点
    type: select
    include-all: true
    exclude-type: direct

    - name: 自动选择
    type: url-test
    include-all: true
    exclude-type: direct
    tolerance: 10

    rules:
    - GEOIP,lan,直连,no-resolve
    - GEOSITE,github,Github
    - GEOSITE,twitter,Twitter
    - GEOSITE,youtube,YouTube
    - GEOSITE,google,Google
    - GEOSITE,telegram,Telegram
    - GEOSITE,netflix,NETFLIX
    - GEOSITE,bilibili,哔哩哔哩
    - GEOSITE,bahamut,巴哈姆特
    - GEOSITE,spotify,Spotify
    - GEOSITE,CN,国内
    - GEOSITE,geolocation-!cn,其他

    - GEOIP,google,Google
    - GEOIP,netflix,NETFLIX
    - GEOIP,telegram,Telegram
    - GEOIP,twitter,Twitter
    - GEOIP,CN,国内
    - MATCH,其他
  3. 创建 docker-compose.yaml 文件

    version: "3"

    services:
    metacubexd:
    container_name: metacubexd
    image: ghcr.io/metacubex/metacubexd
    restart: always
    ports:
    - "9097:80"

    mihomo:
    container_name: mihomo
    image: docker.io/metacubex/mihomo:latest
    restart: always
    ports:
    # 尽量使用 127.0.0.1 而不是 0.0.0.0,否则可能让代理暴露在公网
    - "127.0.0.1:7890:7890"
    - "9090:9090"
    volumes:
    - <保存 config.yaml 文件的目录>:/root/.config/mihomo
  4. 启动容器 docker compose up -d

使用教程

在需要使用代理的地方,设置代理为 http://127.0.0.1:7890 即可。

比如为终端设置代理:

  1. 编辑 ~/.bashrc 文件,添加以下内容:

    export http_proxy="http://127.0.0.1:7890"
    export https_proxy="http://127.0.0.1:7890"
    export all_proxy="socks5://127.0.0.1:7890"
    export no_proxy="localhost,127.0.0.1,*.local"
  2. 保存并退出,然后执行 source ~/.bashrc 使配置生效。

  3. 使用 curl https://www.google.com 测试是否成功。

注意事项

  1. 尽量不要使用 MobaXterm 等终端创建或者编辑配置文件,可以会出现默认编码非 UTF-8 或者缺少字符集的问题,导致配置文件无法正常使用。
  2. config.yamlexternal-controller 的端口和 docker-compose.yamlmetacubexd 的端口都需要在防火墙中开放,建议只允许特定 IP 访问,如果全放开的话,建议取消注释 secret 并设置密码。
  3. docker-compose.yamlmihomovolumes 需要指向 config.yaml 文件在主机的目录而非文件。
  4. 如果 mihomo 的容器的日志中出现了下载文件的错误,请到 meta-rules-dat 仓库中手动下载文件并保存到 config.yaml 所在的目录

使用 wsl 版的 docker desktop

· 阅读需 1 分钟
1adybug
子虚伊人

如果在内网安装 docker desktop,那么 docker desktop 会强制要求升级 wsl,否则无法使用。只需要修改 docker desktop 的配置文件:

// C:\Users\用户名\AppData\Roaming\Docker\setting-store.json
{
// 添加这一行
"WslUpdateRequired": false
}

windows 版本的 docker desktop

· 阅读需 1 分钟
1adybug
子虚伊人

之前为了便于 wsl 中的系统直接使用 windows 的代理,我将 wsl 的网络模式都设置为了 mirrored,这样就导致了无法使用 ip 访问 docker 中的应用,所以必须将网络模式设置为 NAT,这样就可以通过 ip 访问 docker 中的应用了。

[wsl2]
# 注释掉或者改为 NAT
# networkingMode=mirrored
networkingMode=NAT

关闭 wsl,然后重启 docker desktop,这样就可以通过 ip 访问 docker 中的应用了。

wsl --shutdown

Docker 安装

· 阅读需 2 分钟
1adybug
子虚伊人

使用 apt 命令安装很有可能失败,参考官方教程 install-from-a-package

  1. 转至 https://download.docker.com/linux/ubuntu/dists/

  2. 在列表中选择 Ubuntu 版本

  3. 转到 pool/stable/ 并选择适用的架构(amd64armhfarm64s390x

  4. 下载 Docker EngineCLIcontainerdDocker Compose 软件包的以下 deb 文件:

    • containerd.io_<version>_<arch>.deb
    • docker-ce_<version>_<arch>.deb
    • docker-ce-cli_<version>_<arch>.deb
    • docker-buildx-plugin_<version>_<arch>.deb
    • docker-compose-plugin_<version>_<arch>.deb
  5. 安装 .deb 软件包。将以下示例中的路径更新为您下载 Docker 软件包的位置。

    sudo dpkg -i ./containerd.io_<version>_<arch>.deb \
    ./docker-ce_<version>_<arch>.deb \
    ./docker-ce-cli_<version>_<arch>.deb \
    ./docker-buildx-plugin_<version>_<arch>.deb \
    ./docker-compose-plugin_<version>_<arch>.deb

    Docker 守护进程自动启动。

  6. 通过运行 hello-world 镜像来验证安装是否成功:

    sudo service docker start
    sudo docker run hello-world

    此命令下载测试映像并在容器中运行。容器运行时,它会打印一条确认消息并退出。

现已成功安装并启动了 Docker Engine

修改 Docker 镜像

· 阅读需 1 分钟
1adybug
子虚伊人
  1. 创建目录 /etc/docker

    sudo mkdir -p /etc/docker
  2. 创建并编辑文件 /etc/docker/daemon.json

    sudo vim /etc/docker/daemon.json

    添加以下内容:

    {
    "registry-mirrors": ["https://docker.sunzishaokao.com", "https://hub.hxui.site", "https://docker.1ms.run"],
    "exec-opts": ["native.cgroupdriver=systemd"]
    }

    其中 registry-mirrors 为镜像地址,根据实际情况替换。

  3. 重启 Docker 服务:

    sudo systemctl daemon-reload
    sudo systemctl restart docker