From 2e940f7f3073f9b8e9ba10296c1c51ca857b5d59 Mon Sep 17 00:00:00 2001 From: biss Date: Sat, 25 Jul 2026 21:21:25 +0800 Subject: [PATCH] =?UTF-8?q?docker=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.docker.example | 31 ++++++++------ README.md | 101 ++++++++++++++++++++++++++++++++------------ compose.example.yml | 101 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 41 deletions(-) create mode 100644 compose.example.yml diff --git a/.env.docker.example b/.env.docker.example index 5563093..dca4dbc 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -1,13 +1,18 @@ -# 复制为 .env.docker,并通过 docker run --env-file .env.docker 使用。 -ASPNETCORE_ENVIRONMENT=Production -ASPNETCORE_HTTP_PORTS=8080 -Database__Provider=MySql -Database__ApplyMigrationsOnStartup=false -Database__CommandTimeoutSeconds=30 -ConnectionStrings__MySql=Server=db.example.edu.cn;Port=3306;Database=jiaowu;User=APP_USER;Password=REPLACE_WITH_A_STRONG_PASSWORD;SslMode=VerifyFull;SslCa=/run/secrets/mysql-ca.pem; -Jwt__Issuer=Jiaowu.Api -Jwt__Audience=Jiaowu.Web -Jwt__Key=REPLACE_WITH_AT_LEAST_32_RANDOM_BYTES -Jwt__ExpireMinutes=60 -AllowedHosts=jiaowu.example.edu.cn -Cors__Origins__0=https://jiaowu.example.edu.cn +# 复制为 .env.docker,供 compose.example.yml 在运行时读取。 +# 空值必须在启动前填写。建议使用十六进制或只包含字母数字的随机值。 +JIAOWU_IMAGE=jiaowu:local +JIAOWU_PORT=8080 + +MYSQL_DATABASE=jiaowu_demo +MYSQL_USER=jiaowu +MYSQL_PASSWORD= +MYSQL_ROOT_PASSWORD= + +JWT_KEY= +ALLOWED_HOSTS=localhost +CORS_ORIGIN=http://localhost:8080 + +# 仅首次创建演示管理员时使用;演示数据成功写入后删除下面三行。 +SEED_ADMIN_USERNAME=demo-admin +SEED_ADMIN_PASSWORD= +SEED_ADMIN_DISPLAY_NAME=演示环境管理员 diff --git a/README.md b/README.md index 55ce7fb..31f51f4 100644 --- a/README.md +++ b/README.md @@ -182,42 +182,87 @@ git.biss.click/biss/academic-affairs-system 会创建 Gitea Release;手动运行只保留工作流产物并推送 `manual-`、`sha-` 镜像标签。 -本地使用 Docker 时,先复制并编辑配置: +仓库提供跨 Windows、Linux 和 macOS Docker Desktop 使用的 +[`compose.example.yml`](compose.example.yml)。它包含 MySQL 8.4、一次性数据库迁移、 +Web 服务和一个默认关闭的演示数据工具服务。数据库数据保存在命名卷中,MySQL 端口不 +暴露到宿主机;容器日志默认轮转为 3 个 10 MB 文件。 + +先复制并编辑配置。PowerShell: ```powershell Copy-Item -LiteralPath '.env.docker.example' -Destination '.env.docker' ``` -先使用迁移账号配置文件执行迁移,再使用应用账号配置文件启动容器;MySQL CA 文件通过 -只读卷挂载,不会进入镜像: +Linux/macOS: -```powershell -$image = 'git.biss.click/biss/academic-affairs-system:1.0.0' - -$migrateArgs = @( - 'run', '--rm' - '--env-file', '.env.docker.migrate' - '--mount', 'type=bind,source=C:\certs\mysql-ca.pem,target=/run/secrets/mysql-ca.pem,readonly' - $image - '--migrate-only' -) -& docker @migrateArgs -if ($LASTEXITCODE -ne 0) { throw '数据库迁移失败。' } - -$runArgs = @( - 'run', '--detach' - '--name', 'jiaowu' - '--restart', 'unless-stopped' - '--env-file', '.env.docker' - '--publish', '8080:8080' - '--mount', 'type=bind,source=C:\certs\mysql-ca.pem,target=/run/secrets/mysql-ca.pem,readonly' - $image -) -& docker @runArgs -if ($LASTEXITCODE -ne 0) { throw '容器启动失败。' } +```bash +cp .env.docker.example .env.docker ``` -Docker 镜像不包含 `.env`、数据库密码或 JWT 密钥。容器以非 root 用户运行,监听 +把 `.env.docker` 中所有密码和 JWT 空值填成独立随机值;`JWT_KEY` 至少使用 64 个 +随机十六进制字符。该文件已被 +`.gitignore` 排除,不要提交;Compose 通过 `--env-file` 在运行时插值并把应用需要的 +配置传入容器,镜像构建过程不需要也不会得到这些值。 + +如需在当前机器构建镜像,保留 `JIAOWU_IMAGE=jiaowu:local` 并执行: + +```powershell +$composeArgs = @( + '--file', 'compose.example.yml' + '--env-file', '.env.docker' + 'build', 'app' +) +& docker compose @composeArgs +if ($LASTEXITCODE -ne 0) { throw 'Docker 镜像构建失败。' } +``` + +也可以把 `JIAOWU_IMAGE` 改成工作流发布的明确版本标签,例如 +`git.biss.click/biss/academic-affairs-system:1.0.0`。正常启动不会写入演示数据: + +```powershell +$composeArgs = @( + '--file', 'compose.example.yml' + '--env-file', '.env.docker' + 'up', '--detach', 'app' +) +& docker compose @composeArgs +if ($LASTEXITCODE -ne 0) { throw 'Compose 启动失败。' } +``` + +Compose 会先等待 MySQL 健康,再执行 `migrate`;只有迁移成功才启动 `app`。首次需要 +演示数据时,应在**尚未启动 app 的全新空库**上单独执行: + +```powershell +$composeArgs = @( + '--file', 'compose.example.yml' + '--env-file', '.env.docker' + 'run', '--rm', 'demo-data' +) +& docker compose @composeArgs +if ($LASTEXITCODE -ne 0) { throw '演示数据写入失败。' } +``` + +`demo-data` 使用 `tools` profile,因此普通 `docker compose up` 不会执行它;显式指定 +该服务时 Compose 会自动启用其 profile,并启动 MySQL、执行迁移后再写入数据。应用自身 +还会强制检查确认参数和空业务库,检测到已有业务数据会拒绝写入。成功后,从 +`.env.docker` 删除三个 `SEED_ADMIN_*` 配置,再启动 `app`。 + +Linux/macOS 的 Compose 参数完全相同,可直接运行: + +```bash +docker compose -f compose.example.yml --env-file .env.docker build app +docker compose -f compose.example.yml --env-file .env.docker run --rm demo-data +docker compose -f compose.example.yml --env-file .env.docker up -d app +docker compose -f compose.example.yml --env-file .env.docker logs --follow app +``` + +示例中应用与 MySQL 位于 Compose 私有网络,所以内部连接使用 +`SslMode=Disabled`,MySQL 未映射宿主机端口。这适合独立演示环境;正式生产连接外部 +MySQL 时仍应使用前文的 `SslMode=VerifyFull` 和 CA,并拆分具备 DDL 权限的迁移账号与 +仅具备业务 DML 权限的应用账号。官方 MySQL 镜像通过 `MYSQL_USER` 创建的账号会获得 +示例数据库的全部权限,因此这个一体化 Compose 不能替代生产环境的最小权限设计。 + +Docker 镜像不包含 `.env`、数据库密码或 JWT 密钥。应用容器以非 root 用户运行并监听 8080 端口;生产环境仍应由反向代理负责 HTTPS、访问日志和请求大小限制。 ## 验证 diff --git a/compose.example.yml b/compose.example.yml new file mode 100644 index 0000000..170a5bd --- /dev/null +++ b/compose.example.yml @@ -0,0 +1,101 @@ +name: jiaowu-demo + +x-jiaowu-image: &jiaowu-image + image: "${JIAOWU_IMAGE:-jiaowu:local}" + build: + context: . + dockerfile: Dockerfile + +x-jiaowu-environment: &jiaowu-environment + ASPNETCORE_ENVIRONMENT: Production + ASPNETCORE_HTTP_PORTS: "8080" + Database__Provider: MySql + Database__ApplyMigrationsOnStartup: "false" + Database__CommandTimeoutSeconds: "30" + ConnectionStrings__MySql: "Server=mysql;Port=3306;Database=${MYSQL_DATABASE:-jiaowu_demo};User=${MYSQL_USER:-jiaowu};Password=${MYSQL_PASSWORD:?请在 .env.docker 中设置 MYSQL_PASSWORD};SslMode=Disabled;" + Jwt__Issuer: Jiaowu.Api + Jwt__Audience: Jiaowu.Web + Jwt__Key: "${JWT_KEY:?请在 .env.docker 中设置 JWT_KEY}" + Jwt__ExpireMinutes: "60" + AllowedHosts: "${ALLOWED_HOSTS:-localhost}" + Cors__Origins__0: "${CORS_ORIGIN:-http://localhost:8080}" + SeedAdmin__UserName: "${SEED_ADMIN_USERNAME:-}" + SeedAdmin__Password: "${SEED_ADMIN_PASSWORD:-}" + SeedAdmin__DisplayName: "${SEED_ADMIN_DISPLAY_NAME:-}" + +x-json-logging: &json-logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + mysql: + image: mysql:8.4 + restart: unless-stopped + environment: + MYSQL_DATABASE: "${MYSQL_DATABASE:-jiaowu_demo}" + MYSQL_USER: "${MYSQL_USER:-jiaowu}" + MYSQL_PASSWORD: "${MYSQL_PASSWORD:?请在 .env.docker 中设置 MYSQL_PASSWORD}" + MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?请在 .env.docker 中设置 MYSQL_ROOT_PASSWORD}" + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_0900_ai_ci + healthcheck: + test: + - CMD-SHELL + - MYSQL_PWD="$${MYSQL_ROOT_PASSWORD}" mysqladmin ping --host=127.0.0.1 --user=root --silent + interval: 5s + timeout: 5s + retries: 20 + start_period: 30s + volumes: + - mysql-data:/var/lib/mysql + logging: *json-logging + + migrate: + <<: *jiaowu-image + command: + - --migrate-only + environment: *jiaowu-environment + depends_on: + mysql: + condition: service_healthy + restart: "no" + init: true + logging: *json-logging + + app: + <<: *jiaowu-image + environment: *jiaowu-environment + depends_on: + mysql: + condition: service_healthy + migrate: + condition: service_completed_successfully + ports: + - "${JIAOWU_PORT:-8080}:8080" + restart: unless-stopped + init: true + logging: *json-logging + + # 工具型一次性服务:普通 docker compose up 不会执行它。 + demo-data: + <<: *jiaowu-image + profiles: + - tools + command: + - --seed-demo-data + - --confirm-production-demo-data + environment: *jiaowu-environment + depends_on: + mysql: + condition: service_healthy + migrate: + condition: service_completed_successfully + restart: "no" + init: true + logging: *json-logging + +volumes: + mysql-data: