跳到主要内容

Docker Compose 部署

本方案将 MySQL、Redis、Java 后端和 Nginx 放入同一个 Compose 项目,只对宿主机开放 Nginx 端口。数据库、Redis 和后端通过容器网络通信,不直接暴露到公网。

准备工作

系统要求

  • 建议至少 2 核 CPU、4 GB 内存和 20 GB 可用磁盘。
  • 已安装 Docker Engine 和 Docker Compose 插件。
  • 可以拉取 mysql:8.0redis:7-alpineeclipse-temurin:8-jrenginx:alpine

按照 Docker 官方文档安装 Compose 插件,使用以下命令验证:

docker version
docker compose version

Docker 官方将独立的 docker-compose 安装方式标记为仅用于向后兼容,新部署使用 docker compose

交付文件

  • surveyking-server.jar
  • surveyking-pro.sql
  • 包含 dist/publicdist/admin 的前端压缩包。
  • 版本配置说明和 License。

1. 创建部署目录

sudo mkdir -p /opt/surveyking-compose/{mysql/init,mysql/data,redis/data,app/data,logs/app,nginx/html}
sudo chown -R "$(id -u):$(id -g)" /opt/surveyking-compose
cd /opt/surveyking-compose

最终目录:

/opt/surveyking-compose/
├── compose.yaml
├── .env
├── mysql/
│ ├── init/surveyking-pro.sql
│ └── data/
├── redis/data/
├── app/
│ ├── surveyking-server.jar
│ └── data/
├── logs/app/
└── nginx/
├── default.conf
└── html/dist/
├── public/
└── admin/

复制并解压交付文件:

cp /path/to/surveyking-pro.sql mysql/init/
cp /path/to/surveyking-server.jar app/
unzip /path/to/surveyking-fe.zip -d nginx/html

test -f nginx/html/dist/public/index.html
test -f nginx/html/dist/admin/index.html

2. 配置环境变量

创建 /opt/surveyking-compose/.env。以下值必须全部替换,不要使用公开文档中的固定默认密码。

MYSQL_DATABASE=survey
MYSQL_USER=surveyking
MYSQL_PASSWORD=replace_with_random_password
MYSQL_ROOT_PASSWORD=replace_with_another_random_password

REDIS_PASSWORD=replace_with_random_password
JASYPT_PASSWORD=replace_with_delivery_value
DRUID_LOGIN_PASSWORD=replace_with_random_password

HTTP_PORT=18000
TZ=Asia/Shanghai

MySQL、Redis 和 Druid 密码可以使用 openssl rand -hex 24 分别生成。JASYPT_PASSWORD 如果随交付配置提供,必须使用交付值,否则可能无法解密已有配置。限制文件权限:

chmod 600 .env
注意
.env 包含敏感信息

不要提交到 Git、发送到群聊或粘贴到工单。备份时应加密保存,并限制读取权限。

3. 创建 Compose 配置

创建 /opt/surveyking-compose/compose.yaml

name: surveyking

services:
mysql:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
TZ: ${TZ}
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/init/surveyking-pro.sql:/docker-entrypoint-initdb.d/01-surveyking-pro.sql:ro
healthcheck:
test:
[
'CMD-SHELL',
'mysqladmin ping -h 127.0.0.1 -uroot -p"$$MYSQL_ROOT_PASSWORD" --silent',
]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s

redis:
image: redis:7-alpine
restart: unless-stopped
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
TZ: ${TZ}
command:
- sh
- -c
- exec redis-server --appendonly yes --requirepass "$$REDIS_PASSWORD"
volumes:
- ./redis/data:/data
healthcheck:
test:
[
'CMD-SHELL',
'redis-cli -a "$$REDIS_PASSWORD" ping 2>/dev/null | grep -q PONG',
]
interval: 10s
timeout: 5s
retries: 10

app:
image: eclipse-temurin:8-jre
restart: unless-stopped
working_dir: /data
environment:
SPRING_PROFILES_ACTIVE: prod
SERVER_PORT: 48080
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_NAME: ${MYSQL_DATABASE}
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://mysql:3306/${MYSQL_DATABASE}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: ${MYSQL_USER}
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: ${MYSQL_PASSWORD}
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
SPRING_REDIS_DATABASE: 0
SPRING_REDIS_PASSWORD: ${REDIS_PASSWORD}
JASYPT_PASSWORD: ${JASYPT_PASSWORD}
DRUID_LOGIN_PASSWORD: ${DRUID_LOGIN_PASSWORD}
SPRING_BOOT_ADMIN_URL: ''
TZ: ${TZ}
command:
- java
- -Xms512m
- -Xmx2g
- -Dfile.encoding=UTF-8
- -jar
- /opt/surveyking/surveyking-server.jar
volumes:
- ./app/surveyking-server.jar:/opt/surveyking/surveyking-server.jar:ro
- ./app/data:/data
- ./logs/app:/root/logs
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
stop_grace_period: 60s

nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- ${HTTP_PORT}:80
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/html:/usr/share/nginx/html:ro
depends_on:
- app

该配置没有映射 MySQL、Redis 和后端端口。需要运维访问时使用 docker compose exec,不要为了调试长期开放端口。

4. 创建 Nginx 配置

创建 /opt/surveyking-compose/nginx/default.conf

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name _;
index index.html;
client_max_body_size 100m;

location = /pub {
return 301 /pub/;
}

location = /adm {
return 301 /adm/;
}

location = /pub/index.html {
root /usr/share/nginx/html/dist/public;
etag off;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

location = /adm/index.html {
root /usr/share/nginx/html/dist/admin;
etag off;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

location ^~ /pub/ {
alias /usr/share/nginx/html/dist/public/;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}

location ^~ /adm/ {
alias /usr/share/nginx/html/dist/admin/;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}

location ~ ^/(e|s|t|q|share|redeem)(/|$) {
root /usr/share/nginx/html/dist/public;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

location ~ ^/user/(login|register|binding)/?$ {
root /usr/share/nginx/html/dist/public;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

location ^~ /captcha {
proxy_pass http://app:48080;
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 X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1800s;
}

location ^~ /admin-api {
proxy_pass http://app:48080;
proxy_buffering off;
proxy_cache off;
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 X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
}

location = /health {
access_log off;
add_header Content-Type text/plain;
return 200 "healthy\n";
}

location / {
root /usr/share/nginx/html/dist/admin;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files $uri $uri/ /index.html;
}
}

公网生产环境可以在宿主机负载均衡器、WAF 或另一层 Nginx 终止 HTTPS,再转发到 Compose 的 HTTP_PORT。如直接在容器中配置证书,需要额外挂载证书并自行维护续期。

5. 启动并验证

先验证 Compose 文件,不输出展开后的敏感值:

docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=200 app

首次启动时,MySQL 只会在空数据目录中自动执行 mysql/init/surveyking-pro.sql。如果 mysql/data 已有数据,初始化脚本不会再次执行。

检查页面:

curl -I http://127.0.0.1:18000/pub/index.html
curl -I http://127.0.0.1:18000/adm/index.html
curl -I http://127.0.0.1:18000/e/route-check
curl -I http://127.0.0.1:18000/s/route-check
curl -I http://127.0.0.1:18000/t/route-check
curl -I http://127.0.0.1:18000/q/route-check
curl -I http://127.0.0.1:18000/share/route-check
curl -I http://127.0.0.1:18000/redeem/route-check
curl -I http://127.0.0.1:18000/user/login
curl http://127.0.0.1:18000/health

这些 route-check 路径用于确认 public history fallback 返回 HTTP 200,不要求存在对应业务数据。

如果修改了 HTTP_PORT,将命令中的 18000 替换为实际端口。

再使用普通账号完成登录、创建测试项目、提交答卷和上传文件。

6. 文件存储

应用工作目录 /data 已映射到宿主机 app/data,可用于单机本地文件持久化。进入 基础设置 → 文件管理 → 文件配置,将本地存储目录配置为 /data/files,并设置用户可访问的文件域名。

分布式或多实例部署不要依赖此本地目录,应改用对象存储或共享文件系统。

常用命令

# 状态和日志
docker compose ps
docker compose logs -f app
docker compose logs --tail=200 mysql redis nginx

# 重启单个服务
docker compose restart app

# 停止服务,但保留数据目录
docker compose down

# 重新启动
docker compose up -d

不要使用 docker compose down -v,该命令会删除 Compose 管理的数据卷。虽然本例使用宿主机目录持久化数据,生产运维仍应避免形成误用习惯。

备份与升级

数据库备份

mkdir -p backups
docker compose exec -T mysql sh -c \
'MYSQL_PWD="$MYSQL_PASSWORD" exec mysqldump -u "$MYSQL_USER" "$MYSQL_DATABASE"' \
> "backups/survey-$(date +%Y%m%d-%H%M%S).sql"

同时备份:

  • app/data 中的本地附件。
  • .envcompose.yamlnginx/default.conf
  • 当前 JAR、前端 dist 和 License。

升级时先在测试环境验证数据库变更,再停止 appnginx、替换 JAR 与前端文件并重新启动。不要对已有数据库重复导入初始化 SQL。

常见问题

MySQL 已启动,但没有业务表

mysql/data 不是空目录,因此 MySQL 跳过了初始化脚本。检查数据库现状,不要直接删除数据目录;确认没有正式数据后再重新初始化,或手工导入 SQL。

Redis 健康检查失败

检查 .env 中的 REDIS_PASSWORD 是否存在,以及 Compose 中的启动命令和健康检查是否引用同一个变量。NAS 部署还需要确认 redis/data 目录可写。

Nginx 返回 502

docker compose ps
docker compose logs --tail=200 app
docker compose exec nginx wget -qO- http://app:48080/ || true

重点检查后端启动日志、数据库初始化状态和容器内端口是否为 48080

升级后页面空白

确认 nginx/html/dist/publicnginx/html/dist/admin 都来自同一版本,并检查 index.html 未被浏览器、CDN 或上游代理缓存。