Go-Zero进阶实战2: 使用 Docker Compose 编排与部署微服务

纲要

  • 引言:微服务容器化的挑战与 Docker Compose 的定位
  • 容器网络模式
    • none 模式
    • bridge 桥接模式
    • host 模式
  • 容器间通信的三种方式
    • 宿主机端口绑定
    • 同一桥接网络下的 IP 直连
    • --link 与容器名解析
  • 实战:手动部署 go-zero 微服务
    • 项目结构
    • 编写 Dockerfile
    • 启动 ETCD
    • 启动 APIRPC 服务
    • 常见问题与日志诊断
  • 使用 Docker Compose 统一编排
    • 安装 Docker Compose
    • 编写 docker-compose.yml
    • 自定义网络与固定 IP
    • 服务依赖与健康检查
    • 一键启动、停止与清理
  • 小结

引言

在微服务架构中,一个应用通常由多个独立服务组成,例如 API 网关、RPC 服务、配置中心 ETCD、缓存 Redis、数据库 MySQL 等。如果每个服务都手动通过 docker run 启动,并逐个管理网络、端口、依赖关系,不仅效率低下,而且极易出错。Docker Compose 正是为解决这一问题而生------它允许通过一个 YAML 文件定义所有服务、网络和卷,实现"一键编排"。

本文将以 go-zero 框架生成的微服务为例,从容器网络原理入手,逐步演示如何手动部署多个容器,并最终迁移到 Docker Compose 进行统一管理。文中的 go-zero 代码均基于 go-zero@latest 生成,确保与最新版本兼容。

容器网络模式

理解 Docker 网络是进行多容器通信的前提。Docker 提供三种基本网络模式:

  • none 模式

    容器拥有独立的网络栈,但 Docker 不会为其分配 IP、网卡等网络资源。此时容器完全隔离,无法与外界通信,适用于无需网络的特殊场景。

  • bridge 桥接模式 (默认)

    Docker 在宿主机上创建一个虚拟网桥(通常为 docker0),所有未指定网络的容器默认接入该网桥。每个容器获得一个私有 IP,宿主机通过 NAT 与容器通信。这是开发中最常用的模式。

    下图展示了桥接网络的工作原理:

#mermaid-svg-MVBpXRX4LAV2nsVH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MVBpXRX4LAV2nsVH .error-icon{fill:#552222;}#mermaid-svg-MVBpXRX4LAV2nsVH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MVBpXRX4LAV2nsVH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MVBpXRX4LAV2nsVH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MVBpXRX4LAV2nsVH .marker.cross{stroke:#333333;}#mermaid-svg-MVBpXRX4LAV2nsVH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MVBpXRX4LAV2nsVH p{margin:0;}#mermaid-svg-MVBpXRX4LAV2nsVH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH .cluster-label text{fill:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH .cluster-label span{color:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH .cluster-label span p{background-color:transparent;}#mermaid-svg-MVBpXRX4LAV2nsVH .label text,#mermaid-svg-MVBpXRX4LAV2nsVH span{fill:#333;color:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH .node rect,#mermaid-svg-MVBpXRX4LAV2nsVH .node circle,#mermaid-svg-MVBpXRX4LAV2nsVH .node ellipse,#mermaid-svg-MVBpXRX4LAV2nsVH .node polygon,#mermaid-svg-MVBpXRX4LAV2nsVH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MVBpXRX4LAV2nsVH .rough-node .label text,#mermaid-svg-MVBpXRX4LAV2nsVH .node .label text,#mermaid-svg-MVBpXRX4LAV2nsVH .image-shape .label,#mermaid-svg-MVBpXRX4LAV2nsVH .icon-shape .label{text-anchor:middle;}#mermaid-svg-MVBpXRX4LAV2nsVH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MVBpXRX4LAV2nsVH .rough-node .label,#mermaid-svg-MVBpXRX4LAV2nsVH .node .label,#mermaid-svg-MVBpXRX4LAV2nsVH .image-shape .label,#mermaid-svg-MVBpXRX4LAV2nsVH .icon-shape .label{text-align:center;}#mermaid-svg-MVBpXRX4LAV2nsVH .node.clickable{cursor:pointer;}#mermaid-svg-MVBpXRX4LAV2nsVH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MVBpXRX4LAV2nsVH .arrowheadPath{fill:#333333;}#mermaid-svg-MVBpXRX4LAV2nsVH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MVBpXRX4LAV2nsVH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MVBpXRX4LAV2nsVH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MVBpXRX4LAV2nsVH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MVBpXRX4LAV2nsVH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MVBpXRX4LAV2nsVH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MVBpXRX4LAV2nsVH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MVBpXRX4LAV2nsVH .cluster text{fill:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH .cluster span{color:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MVBpXRX4LAV2nsVH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MVBpXRX4LAV2nsVH rect.text{fill:none;stroke-width:0;}#mermaid-svg-MVBpXRX4LAV2nsVH .icon-shape,#mermaid-svg-MVBpXRX4LAV2nsVH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MVBpXRX4LAV2nsVH .icon-shape p,#mermaid-svg-MVBpXRX4LAV2nsVH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MVBpXRX4LAV2nsVH .icon-shape .label rect,#mermaid-svg-MVBpXRX4LAV2nsVH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MVBpXRX4LAV2nsVH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MVBpXRX4LAV2nsVH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MVBpXRX4LAV2nsVH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NAT
端口映射 8080:8080
端口映射 6379:6379
宿主机网卡 eth0
docker0 虚拟网桥
容器1: 172.17.0.2
容器2: 172.17.0.3

  • host 模式
    容器直接使用宿主机的网络栈,共享宿主机的 IP 和端口。这意味着容器内监听的端口会直接暴露在宿主机上,省略了端口映射的步骤。但该模式容易与宿主机其他程序发生端口冲突,不推荐在生产环境滥用。

容器间通信的三种方式

假设宿主机上运行了两个容器:go-zero-api(提供 HTTP 服务)和 redis(缓存),我们需要让它们互相访问。
#mermaid-svg-D8A8tIFQuktst1KE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D8A8tIFQuktst1KE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D8A8tIFQuktst1KE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D8A8tIFQuktst1KE .error-icon{fill:#552222;}#mermaid-svg-D8A8tIFQuktst1KE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D8A8tIFQuktst1KE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D8A8tIFQuktst1KE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D8A8tIFQuktst1KE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D8A8tIFQuktst1KE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D8A8tIFQuktst1KE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D8A8tIFQuktst1KE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D8A8tIFQuktst1KE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D8A8tIFQuktst1KE .marker.cross{stroke:#333333;}#mermaid-svg-D8A8tIFQuktst1KE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D8A8tIFQuktst1KE p{margin:0;}#mermaid-svg-D8A8tIFQuktst1KE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D8A8tIFQuktst1KE .cluster-label text{fill:#333;}#mermaid-svg-D8A8tIFQuktst1KE .cluster-label span{color:#333;}#mermaid-svg-D8A8tIFQuktst1KE .cluster-label span p{background-color:transparent;}#mermaid-svg-D8A8tIFQuktst1KE .label text,#mermaid-svg-D8A8tIFQuktst1KE span{fill:#333;color:#333;}#mermaid-svg-D8A8tIFQuktst1KE .node rect,#mermaid-svg-D8A8tIFQuktst1KE .node circle,#mermaid-svg-D8A8tIFQuktst1KE .node ellipse,#mermaid-svg-D8A8tIFQuktst1KE .node polygon,#mermaid-svg-D8A8tIFQuktst1KE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D8A8tIFQuktst1KE .rough-node .label text,#mermaid-svg-D8A8tIFQuktst1KE .node .label text,#mermaid-svg-D8A8tIFQuktst1KE .image-shape .label,#mermaid-svg-D8A8tIFQuktst1KE .icon-shape .label{text-anchor:middle;}#mermaid-svg-D8A8tIFQuktst1KE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D8A8tIFQuktst1KE .rough-node .label,#mermaid-svg-D8A8tIFQuktst1KE .node .label,#mermaid-svg-D8A8tIFQuktst1KE .image-shape .label,#mermaid-svg-D8A8tIFQuktst1KE .icon-shape .label{text-align:center;}#mermaid-svg-D8A8tIFQuktst1KE .node.clickable{cursor:pointer;}#mermaid-svg-D8A8tIFQuktst1KE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D8A8tIFQuktst1KE .arrowheadPath{fill:#333333;}#mermaid-svg-D8A8tIFQuktst1KE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D8A8tIFQuktst1KE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D8A8tIFQuktst1KE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D8A8tIFQuktst1KE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D8A8tIFQuktst1KE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D8A8tIFQuktst1KE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D8A8tIFQuktst1KE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D8A8tIFQuktst1KE .cluster text{fill:#333;}#mermaid-svg-D8A8tIFQuktst1KE .cluster span{color:#333;}#mermaid-svg-D8A8tIFQuktst1KE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D8A8tIFQuktst1KE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D8A8tIFQuktst1KE rect.text{fill:none;stroke-width:0;}#mermaid-svg-D8A8tIFQuktst1KE .icon-shape,#mermaid-svg-D8A8tIFQuktst1KE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D8A8tIFQuktst1KE .icon-shape p,#mermaid-svg-D8A8tIFQuktst1KE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D8A8tIFQuktst1KE .icon-shape .label rect,#mermaid-svg-D8A8tIFQuktst1KE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D8A8tIFQuktst1KE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D8A8tIFQuktst1KE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D8A8tIFQuktst1KE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Host
映射
宿主机端口 8080
API 容器
Redis 容器

方式一:通过宿主机端口绑定访问

go-zero-api 的 8080 端口映射到宿主机,redis 容器通过访问宿主机 IP 加端口的方式间接调用 API。

示例:

bash 复制代码
# Redis 容器内执行 
curl http://192.168.1.100:8080/api/login

该方式简单直接,但调用方必须知道宿主机的 IP 和映射端口,耦合度较高。

方式二:基于同一桥接网络 IP 直连

当两个容器处于同一个 bridge 网络时,可以直接使用对方的私有 IP 进行通信。

查看网络信息:

bash 复制代码
docker network inspect bridge

输出会显示每个容器分配的 IP,例如 go-zero-api172.17.0.2redis172.17.0.3

redis 容器中直接访问:

bash 复制代码
curl http://172.17.0.2:8080/api/login

这种方式依赖固定 IP,但在容器重启后 IP 可能发生变化。

方式三:通过 --link 实现容器名解析

在启动容器时,使用 --link 参数可以将目标容器的名称或 ID 注入到当前容器的 /etc/hosts 中,从而实现"通过容器名访问"。

例如,先启动 redis,再启动 api 并链接到 redis

bash 复制代码
docker run -d --name redis redis:alpine
docker run -d -p 8080:8080 --name go-zero-api --link redis go-zero-api:latest

此时在 go-zero-api 容器内部,可以直接 ping redis 并解析到 redis 容器的 IP。注意 --link 是单向的,且官方已推荐使用自定义网络替代。

实战:手动部署 go-zero 微服务

假设我们已经用 go-zero@latest 生成了一个简单的微服务项目,包含 api 服务和 rpc 服务,它们都依赖 ETCD 作为服务注册中心。

项目结构概览

dir 复制代码
demo/
├── api/
│   ├── Dockerfile 
│   └── api.go 
├── rpc/
│   ├── Dockerfile 
│   └── rpc.go 
└── etcd/
    └── Dockerfile 

为了使部署简单化,我们只保留核心通信逻辑:api 通过 ETCD 发现 rpc 服务并调用它。为了纯容器化,暂时注释掉数据库等非必要依赖。

编写 API 服务的 Dockerfile

dockerfile 复制代码
FROM golang:1.21-alpine AS builder 
WORKDIR /app 
COPY . .
RUN go build -o api-server api.go 
 
FROM alpine:latest 
WORKDIR /root/
COPY --from=builder /app/api-server .
COPY etc/api.yaml .
EXPOSE 8080 
CMD ["./api-server"]

编写 RPC 服务的 Dockerfile(与 API 类似,仅服务名不同):

dockerfile 复制代码
FROM golang:1.21-alpine AS builder 
WORKDIR /app 
COPY . .
RUN go build -o rpc-server rpc.go 
 
FROM alpine:latest 
WORKDIR /root/
COPY --from=builder /app/rpc-server .
COPY etc/rpc.yaml .
EXPOSE 8081 
CMD ["./rpc-server"]

构建镜像

bash 复制代码
docker build -t go-zero-api -f api/Dockerfile .
docker build -t go-zero-rpc -f rpc/Dockerfile .

启动 ETCD 容器

首先确保 ETCD 正常运行,因为它是服务注册发现的基础。

bash 复制代码
docker build -t etcd -f etcd/Dockerfile .
docker run -d --name etcd \
  -p 2379:2379 -p 2380:2380 \
  --network mynet --ip 192.168.10.20 \
  etcd

这里我们事先创建了一个自定义网络 mynet

bash 复制代码
docker network create --subnet=192.168.10.0/24 mynet

启动 RPC 服务,并指定 ETCD 地址

bash 复制代码
docker run -d --name go-zero-rpc \
  --network mynet \
  -e ETCD_HOST=192.168.10.20:2379 \
  go-zero-rpc

启动 API 服务

bash 复制代码
docker run -d --name go-zero-api \
  -p 8080:8080 \
  --network mynet \
  -e ETCD_HOST=192.168.10.20:2379 \
  go-zero-api

如果容器启动后立即退出,或调用时报错,务必查看日志定位问题:

bash 复制代码
docker logs go-zero-api

常见错误包括 ETCD 地址配置错误、端口冲突或环境变量未正确传递。通过日志可以快速发现究竟是连接被拒绝还是配置项缺失。

使用 Docker Compose 统一编排

手动运行虽能工作,但每次修改配置都需要停止容器、删除、重新构建镜像、再启动,过程繁琐。Docker Compose 允许将上述所有步骤固化在一个 YAML 文件中。

安装 Docker Compose(以 Linux 为例):

bash 复制代码
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname  -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

docker-compose --version

编写 docker-compose.yml

在项目根目录创建该文件,内容如下(根据实际环境调整版本号和镜像名称):

yaml 复制代码
version: '3.8'
 
networks:
  go-zero-net:
    driver: bridge 
    ipam:
      config:
        - subnet: 192.168.10.0/24 
 
services:
  etcd:
    image: bitnami/etcd:latest 
    container_name: etcd 
    ports:
      - "2379:2379"
      - "2380:2380"
    networks:
      go-zero-net:
        ipv4_address: 192.168.10.20 
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes 
    volumes:
      - ./etcd/data:/bitnami/etcd 
 
  go-zero-rpc:
    build:
      context: ./rpc 
      dockerfile: Dockerfile 
    image: go-zero-rpc:latest 
    container_name: go-zero-rpc 
    networks:
      - go-zero-net 
    environment:
      - ETCD_HOST=192.168.10.20:2379 
    depends_on:
      - etcd 
 
  go-zero-api:
    build:
      context: ./api 
      dockerfile: Dockerfile 
    image: go-zero-api:latest 
    container_name: go-zero-api 
    ports:
      - "8080:8080"
    networks:
      - go-zero-net 
    environment:
      - ETCD_HOST=192.168.10.20:2379 
    depends_on:
      - etcd 
      - go-zero-rpc 

上述配置做了几件重要的事:

  • 自定义网络go-zero-net 使用固定的子网,并为 etcd 分配了静态 IP,rpcapi 会自动分配剩余地址。
  • 服务依赖depends_on 确保 etcd 先于其他服务启动。但对于需要"等待服务就绪"的场景,还需配合 healthcheck,这里简单起见未展开。
  • 构建策略 :若本地不存在对应镜像,docker-compose up 会自动根据 build 指令构建镜像,避免手动执行 docker build
  • 数据卷etcd 的数据目录映射到宿主机,防止数据丢失。

启动所有服务

bash 复制代码
docker-compose up -d

该命令会以后台方式启动所有定义的服务。使用 docker-compose ps 可以查看状态。

查看服务日志

bash 复制代码
docker-compose logs -f go-zero-api

停止并清理所有资源

bash 复制代码
docker-compose down

这条命令会停止容器、删除容器,同时移除自动创建的网络,彻底还原环境。

小结

本文从 Docker 网络的基础原理出发,介绍了容器通信的三种模式,并以 go-zero 微服务为例,演示了从手动部署到 Docker Compose 统一编排的演进过程。Docker Compose 通过声明式的配置文件,极大简化了多容器应用的管理,特别是对于服务依赖、网络划分、环境变量注入等高频操作,它提供了清晰且可复用的解决方案。

在实际开发中,建议将 docker-compose.yml 与源代码一同纳入版本控制,这样任何团队成员都能一键拉起完整的开发环境。后续还可以结合 Docker SwarmKubernetes 进行生产级编排,但 Docker Compose 依然是本地开发和小规模部署的首选工具。

相关推荐
潘正翔1 小时前
docker核心概念
linux·运维·服务器·docker·容器·centos·运维开发
一路向北North2 小时前
Spring Security OAuth2.0(23):分布式系统授权-转发明文给微服务
java·spring·微服务
Java小白笔记4 小时前
Docker 安装配置完全指南:MacOS 、Windows、Linux环境下的安装、配置与验证
linux·macos·docker
copyer_xyf9 小时前
Docker 入门与实战:容器化 React + NestJS 项目
docker
刘马想放假10 小时前
Docker 从入门到进阶:一篇写给工程师的系统化实战指南
运维·docker·容器
zjun302110 小时前
如何搭建TRAE IDE 连接到容器开发环境
docker·容器·ascend·trae
寒冰碧海11 小时前
大模型部署从0到1(一):通用环境全流程准备|NVIDIA驱动+Docker+NVIDIA Container Toolkit
运维·docker·容器
hdsoft_huge13 小时前
SpringBoot 系列 20:Jar 打包 + Docker 容器化 + Compose 一键部署|服务器上线 & 零停机平滑升级全教程
spring boot·docker·jar
你想考研啊13 小时前
七、Docker 容器 + Docker Compose单机部署理发店系统
docker·容器·composer