# 2.7 Docker 快速体验 :::tip 概述 项目根目录已内置完整 Docker 化文件(`docker/` 目录 + 根目录 `.dockerignore`),一条命令拉起 后端 + MySQL + Redis 三容器全栈。生产部署细节见 [11.6 Docker 生产部署](../deploy/docker)。 ::: ## 文件布局 ```text EasyGoAdmin_GoFrame_EleVue/ ├── .dockerignore # 构建上下文排除清单(Docker 硬规则:必须在上下文根目录) └── docker/ ├── Dockerfile # 多阶段构建:golang:1.27-alpine 编译 → alpine 运行 ├── docker-compose.yml # 三服务编排:后端 + MySQL8 + Redis7 └── .env.example # 环境变量模板(复制为 .env 使用,含明文密钥不入库) ``` :::warning 为什么 `.dockerignore` 不在 docker/ 里 `.dockerignore` 由**构建上下文根目录**决定,与 Dockerfile 位置无关。放 `docker/` 里会完全失效,`ui/node_modules`(数百 MB)会被打进构建上下文,构建极慢。而 `Dockerfile` / `docker-compose.yml` 位置无所谓,构建/启动时用 `-f` 指定即可。 ::: ## 文件注释速览 **Dockerfile**(多阶段,关键设计): ```dockerfile # 阶段一:builder —— 编译静态二进制 # 纯 Go 项目(sqlite 驱动为 modernc 系,无 CGO),CGO_ENABLED=0 静态编译 # 先拷 go.mod/go.sum 再 go mod download:源码变更但依赖未变时不重复下载 # -trimpath 去除本地路径,-s -w 去除符号表与调试信息 FROM golang:1.27-alpine AS builder ENV GOPROXY=https://goproxy.cn,direct CGO_ENABLED=0 GOOS=linux WORKDIR /build COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -trimpath -ldflags "-s -w" -o /build/bin/admin-server main.go # 阶段二:runtime —— 最终业务镜像 # 运行时资产四件(缺一运行报错): # manifest/config 配置文件 # manifest/data ip2region.xdb 归属地库 # resource 字体/swagger 静态资源 # templates 代码生成器模板(tool 域运行时读取) # 先于二进制 COPY,业务代码变动不破坏资产层缓存 # 非 root 运行;tzdata 供 app.timezone=Asia/Shanghai 使用 FROM alpine:3.22 AS runtime ENV TZ=Asia/Shanghai RUN apk add --no-cache tzdata ca-certificates \ && addgroup -g 1000 app && adduser -u 1000 -G app -s /sbin/nologin -D app WORKDIR /opt/easygoadmin COPY --chown=app:app manifest/config ./manifest/config COPY --chown=app:app manifest/data ./manifest/data COPY --chown=app:app resource ./resource COPY --chown=app:app templates ./templates COPY --chown=app:app --from=builder /build/bin/admin-server ./bin/admin-server USER app # 健康检查:/api/health 为公开白名单路径,不带 /v1 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD wget -qO- "http://127.0.0.1:8000/api/health" || exit 1 ENTRYPOINT ["./bin/admin-server"] ``` **docker-compose.yml**(三服务编排,关键设计): ```yaml services: easygoadmin: build: context: .. # 上下文为项目根(compose 在 docker/ 目录) dockerfile: docker/Dockerfile # 敏感/环境相关配置一律 GF_ 前缀覆盖,不改镜像内 config.yaml # (键名 = 配置路径大写下划线,如 file.uploadDir → GF_FILE_UPLOADDIR) environment: # 连接串由 .env 参数拼装:主机/端口/账号/密码/库名均可覆盖(连外部库改 .env 即可) GF_DATABASE_DEFAULT_LINK: "mysql:${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DATABASE}?..." GF_REDIS_DEFAULT_ADDRESS: "${REDIS_HOST}:${REDIS_PORT}" GF_FILE_UPLOADDIR: "/data/upload" # 配置默认是 Windows 绝对路径,容器内必须覆盖 GF_CAPTCHA_BYPASSCODE: "" # 生产清空万能验证码 GF_APP_ERRORMSG: "false" # 生产关闭详细错误信息 volumes: - ../upload:/data/upload # 宿主路径相对 compose 文件目录,需 ../ 回到项目根 - ../log:/opt/easygoadmin/log depends_on: mysql: { condition: service_healthy } # 等 healthcheck 通过再启动 redis: { condition: service_healthy } mysql: image: mysql:8.0 volumes: - mysql-data:/var/lib/mysql # 首次初始化:导入定稿 SQL(仅空数据卷时执行一次) - ../document/mysql/easygoadmin.goframe.elevue.sql:/docker-entrypoint-initdb.d/init.sql:ro command: [--character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --lower_case_table_names=1] redis: image: redis:7-alpine command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:-123456}", "--appendonly", "yes"] ``` ## 三步启动 ```sh cd docker # 1. 首次:生成 .env 并按需修改密钥(JWT_SECRET / 数据库 / Redis 密码) copy .env.example .env # 2. 构建并启动全栈(首次 MySQL 自动导入定稿 SQL 建库) docker compose up -d --build # 3. 验证 curl http://127.0.0.1:8000/api/health ``` 默认账号 `admin / 123456`。`.env` 变量说明: | 变量 | 默认值 | 说明 | |------|--------|------| | APP_PORT | 8000 | 后端宿主端口 | | JWT_SECRET | please-change-... | 令牌签名密钥,**生产必改** | | MYSQL_HOST | mysql | **连接串内主机**:自带服务用服务名;连外部库填 IP/域名 | | MYSQL_PORT | 3306 | **数据库监听端口**(进连接串;连外部库填外部库端口) | | MYSQL_USER / MYSQL_PASSWORD | root / root | 数据库账号与密码 | | MYSQL_DATABASE | easygoadmin.goframe.elevue | 库名(与定稿 SQL 一致,勿随意改) | | REDIS_HOST | redis | 同 MYSQL_HOST 规则 | | REDIS_PORT / REDIS_PASSWORD | 6379 / 123456 | Redis 监听端口(进连接串)与密码 | > 自带 mysql/redis 服务的**宿主映射端口已在 compose 写死**(3306/6379,仅供宿主机客户端直连调试),不在 `.env` 中配置;占用冲突时直接改 compose 的 `ports`。 **连宿主机已有数据库/Redis**:`.env` 中把 `MYSQL_HOST` / `REDIS_HOST` 改为 `host.docker.internal`(Windows/Mac Docker Desktop;Linux 需 compose 加 `extra_hosts: host.docker.internal:host-gateway`),端口填宿主机上实际监听端口。 ::: warning 容器内 127.0.0.1 不是宿主机 bridge 网络下每个容器有独立网络栈,`MYSQL_HOST=127.0.0.1` / `REDIS_HOST=127.0.0.1` 指向**容器自身**,连接必失败(Redis 会静默降级内存缓存,JWT 黑名单、验证码共享随之失效)。连宿主机一律用 `host.docker.internal` 或宿主机实际 IP。 ::: ## 仅构建后端镜像(连宿主机已有 DB/Redis) 不启动 compose 里的 mysql/redis 服务时,在**项目根目录**执行: ```sh docker build -f docker/Dockerfile -t easygoadmin:latest . docker run -d --name easygoadmin -p 8000:8000 \ -e GF_DATABASE_DEFAULT_LINK="mysql:root:root@tcp(host.docker.internal:3306)/easygoadmin.goframe.elevue?charset=utf8mb4&parseTime=true&loc=Local" \ -e GF_REDIS_DEFAULT_ADDRESS="host.docker.internal:6379" \ -e GF_FILE_UPLOADDIR="/data/upload" \ -v ${PWD}/upload:/data/upload easygoadmin:latest ``` ## 常见问题 ::: warning 容器内注意 1. `file.uploadDir` 配置默认是 Windows 绝对路径,容器内**必须**用 `GF_FILE_UPLOADDIR` 覆盖并挂持久卷,否则重建丢文件。 2. 跨卷上传"复制后删源"逻辑依赖同一文件系统,upload 目录必须是独立卷。 3. 定稿 SQL 挂载到 `docker-entrypoint-initdb.d` **仅在 MySQL 数据卷为空时执行一次**;改表结构后需手动执行或 `docker volume rm` 清卷重建。 4. 代码生成器(tool 域)在容器内可用(`templates/` 已打入镜像),但生成产物写在容器文件系统里,**生产容器不建议使用**,开发期仍在宿主机跑 `go run ./cmd/generator`。 5. 连接串/Redis 地址在 bridge 网络下用**服务名**(`mysql`、`redis`)互访;连宿主机服务用 `host.docker.internal`;**容器内 127.0.0.1 指向容器自身**,填了连不上(详见上方变量表警示)。 ::: ## 其他运行形态 | 形态 | 说明 | |------|------| | 二进制 + systemd | `go build` 产物 + systemd unit(见 [11.4 后端部署](../deploy/backend)) | | 前端 Nginx 容器 | `ui/` 独立构建 dist 后由 Nginx 托管并反代 /api(见 [11.6](../deploy/docker)) | | k8s | 按拓扑拆分 Deployment/Service,上传目录用 PV |