Docker Compose 如何一键部署前后端 + PostgreSQL + Redis?从 ShiyuAdmin 拆解完整部署方案

在这里插入图片描述

对于一个完整的后台管理系统来说,真正麻烦的往往不是“代码写完”,而是:

怎么把它跑起来?

比如一个典型的前后端分离项目,至少可能包含:

React 前端
Go 后端
PostgreSQL
Redis

如果全部手动部署,你可能要分别处理:

Node.js
npm
Go
Nginx
PostgreSQL
Redis
环境变量
数据库连接
端口
进程守护
服务启动顺序
日志
数据持久化

对于开发者来说还能接受。

但是如果这是一个开源项目,用户下载以后还需要看几十分钟文档才能启动,使用门槛就会非常高。

所以我在 ShiyuAdmin 里专门做了一套 Docker Compose 部署方案。

项目地址:

https://github.com/Rodert/ShiyuAdmin

ShiyuAdmin 是一个使用:

Go
Gin
GORM
React
Umi Max
Ant Design Pro
PostgreSQL
Redis
Docker Compose

搭建的通用后台管理系统。

现在本地启动完整系统,核心就是一条命令:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up -d --build

执行完成之后:

PostgreSQL
Redis
Go Backend
React Frontend
Nginx

全部运行起来。

这篇文章就从 ShiyuAdmin 的实际部署方式出发,完整讲一下:

一个前后端分离项目,如何使用 Docker Compose 做成真正的一键部署。


一、先看 ShiyuAdmin 的整体部署架构

如果完全按照“一个程序一个容器”的思路,我们可能会设计成:

Browser
   │
   ▼
┌─────────────┐
│ React       │
│ Container   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ Go Backend  │
│ Container   │
└──────┬──────┘
       │
       ├──────────────┐
       ▼              ▼
┌─────────────┐ ┌─────────────┐
│ PostgreSQL  │ │ Redis       │
└─────────────┘ └─────────────┘

这样当然没问题。

但是 ShiyuAdmin 当前采用了另一种思路:

Browser
   │
   │ 18000
   ▼
┌──────────────────────────────┐
│         shiyu-app            │
│                              │
│  ┌────────────────────────┐  │
│  │ Nginx                  │  │
│  │ React 静态资源         │  │
│  │ API Reverse Proxy      │  │
│  └──────────┬─────────────┘  │
│             │ /api           │
│             ▼                │
│  ┌────────────────────────┐  │
│  │ Go Backend :8080       │  │
│  └────────────────────────┘  │
│                              │
│      Supervisor 管理进程      │
└─────────────┬────────────────┘
              │
        Docker Network
         ┌────┴─────┐
         ▼          ▼
┌─────────────┐ ┌─────────────┐
│ PostgreSQL  │ │ Redis       │
│ :5432       │ │ :6379       │
└─────────────┘ └─────────────┘

也就是说:

前端 + 后端
↓
一个应用镜像

PostgreSQL
↓
一个容器

Redis
↓
一个容器

最终只有三个核心 Service:

shiyu-app
shiyu-postgres
shiyu-redis

这种结构特别适合:

后台管理系统
中小型 SaaS
个人开源项目
内部管理系统
单机 Docker 部署

因为部署非常简单。


二、ShiyuAdmin 的项目结构

整个仓库核心结构可以简化为:

ShiyuAdmin
├── backend/
│   └── shiyu-admin-backend/
│       ├── cmd/
│       │   └── server/
│       ├── configs/
│       │   ├── config.yaml
│       │   └── config.docker.yaml
│       ├── internal/
│       ├── go.mod
│       └── go.sum
│
├── frontend/
│   └── shiyu-admin-web/
│       ├── src/
│       ├── package.json
│       └── ...
│
├── deploy/
│   ├── nginx.conf
│   └── supervisord.conf
│
├── Dockerfile
├── docker-compose.yml
├── docker-compose.local.yml
└── docker-compose.README.md

这里面几个文件分别负责:

文件 作用
Dockerfile 构建前端、后端和最终运行镜像
docker-compose.yml 定义 PostgreSQL、Redis、应用服务
docker-compose.local.yml 本地从源码构建 ShiyuAdmin
config.docker.yaml Docker 环境下的后端配置
nginx.conf 托管前端并代理 /api
supervisord.conf 同时管理 Nginx 和 Go 后端

看似文件不少,但真正执行的时候用户只需要:

docker compose up

这才是一键部署真正应该解决的问题。


三、Docker Compose 到底解决了什么?

如果不用 Docker Compose,我们首先启动 PostgreSQL:

docker run -d \
  --name shiyu-postgres \
  -e POSTGRES_DB=shiyu_admin_scaffold \
  -e POSTGRES_USER=shiyu \
  -e POSTGRES_PASSWORD=shiyu123 \
  -p 15432:5432 \
  postgres:15

然后 Redis:

docker run -d \
  --name shiyu-redis \
  -p 16379:6379 \
  redis:7

然后构建 ShiyuAdmin:

docker build \
  -t shiyu-admin:latest \
  .

然后启动:

docker run -d \
  --name shiyu-app \
  -p 18000:80 \
  shiyu-admin:latest

接下来还有一个问题:

这些容器怎么互相找到?

还得创建 Network:

docker network create shiyu-network

再指定:

--network shiyu-network

数据库数据还不能随着容器删除消失,所以又需要 Volume:

docker volume create shiyu-pg-data
docker volume create shiyu-redis-data

你会发现,最终命令越来越多。

Docker Compose 实际上就是把这些东西声明到:

docker-compose.yml

里面。

然后:

docker compose up -d

一次启动。

所以 Compose 的核心思想其实非常简单:

把一堆 Docker 命令,变成一份可版本管理、可重复执行的基础设施配置。


四、先从 PostgreSQL 开始

ShiyuAdmin 默认的 Compose 部署使用 PostgreSQL。

我们可以把 PostgreSQL Service 简化理解为:

services:

  shiyu-postgres:
    image: postgres:15

    container_name: shiyu-postgres

    restart: unless-stopped

    environment:
      POSTGRES_DB: shiyu_admin_scaffold
      POSTGRES_USER: shiyu
      POSTGRES_PASSWORD: shiyu123

    ports:
      - "15432:5432"

    volumes:
      - shiyu-pg-data:/var/lib/postgresql/data

    networks:
      - shiyu-network

逐个看。


五、image 是什么意思?

image: postgres:15

表示使用:

PostgreSQL 15

官方 Docker 镜像。

如果本地没有:

postgres:15

Docker 会自动拉取。

相当于:

docker pull postgres:15

这就是 Docker 最大的优势之一。

用户完全不需要自己安装 PostgreSQL。

不需要:

brew install postgresql

也不需要:

apt install postgresql

更不用考虑:

你的 PostgreSQL 是 14
我的 PostgreSQL 是 15
他的 PostgreSQL 是 16

统一:

postgres:15

六、environment 初始化数据库

这里:

environment:
  POSTGRES_DB: shiyu_admin_scaffold
  POSTGRES_USER: shiyu
  POSTGRES_PASSWORD: shiyu123

PostgreSQL 容器第一次启动的时候会创建:

数据库:
shiyu_admin_scaffold

用户名:
shiyu

密码:
shiyu123

因此 Go 后端连接数据库时也需要使用相同的信息。

例如:

database:
  driver: postgres

  host: shiyu-postgres

  port: 5432

  username: shiyu

  password: shiyu123

  database: shiyu_admin_scaffold

注意这里最重要的是:

host: shiyu-postgres

而不是:

host: localhost

为什么?

因为 Go 后端本身也运行在 Docker 容器中。


七、Docker 里面的 localhost 是谁?

这是 Docker 新手最容易犯的错误之一。

假设:

shiyu-app

容器里面配置:

database:
  host: localhost

那么:

localhost

代表的是:

shiyu-app 自己

不是 PostgreSQL。

因为每个容器都有自己的网络环境。

可以理解为:

Container A localhost
≠
Container B localhost

那么 ShiyuAdmin 后端怎么找到 PostgreSQL?

直接使用 Compose Service Name:

shiyu-postgres

所以:

database:
  host: shiyu-postgres
  port: 5432

Docker DNS 会自动完成:

shiyu-postgres
↓
PostgreSQL Container IP

Redis 同理:

redis:
  host: shiyu-redis
  port: 6379

这其实是 Docker Compose 非常重要的一项能力:

Service Name 本身就是内部 DNS 名称。


八、端口映射为什么是 15432:5432?

ShiyuAdmin 的 PostgreSQL 使用类似:

ports:
  - "15432:5432"

Docker 的端口规则是:

宿主机端口 : 容器端口

所以:

15432
↓
Host

5432
↓
PostgreSQL Container

如果你在 Mac 上使用数据库客户端:

Host:
localhost

Port:
15432

但是 ShiyuAdmin 容器访问数据库的时候:

Host:
shiyu-postgres

Port:
5432

这两个一定不要混。

可以记成:

宿主机访问容器
↓
localhost:15432

容器访问容器
↓
shiyu-postgres:5432

九、为什么一定需要 Volume?

如果只写:

shiyu-postgres:
  image: postgres:15

数据库数据默认会保存在容器内部。

假设删除容器:

docker compose down

再重新创建。

就可能涉及数据库数据生命周期的问题。

因此 ShiyuAdmin 使用:

volumes:
  - shiyu-pg-data:/var/lib/postgresql/data

然后在 Compose 最下面定义:

volumes:

  shiyu-pg-data:
    driver: local

现在数据路径变成:

PostgreSQL
        │
        ▼
/var/lib/postgresql/data
        │
        ▼
Docker Volume
        │
        ▼
shiyu-pg-data

于是:

docker compose down

删除容器以后,数据卷还在。

重新:

docker compose up -d

数据库数据还能回来。


十、什么时候数据库会真的被删?

如果执行:

docker compose down

默认不会删除 Volume。

但是如果执行:

docker compose down -v

这里的:

-v

代表:

同时删除 Volume

这意味着 ShiyuAdmin 的 PostgreSQL 数据也会被清理。

所以:

docker compose down -v

一定要谨慎。

特别是在生产环境。


十一、PostgreSQL Health Check

光启动 PostgreSQL 容器还不够。

例如:

10:00:00
PostgreSQL Container 启动

10:00:01
Go Backend 启动

10:00:02
Go Backend 连接 PostgreSQL

10:00:03
PostgreSQL 还没初始化完成

10:00:03
连接失败

所以 ShiyuAdmin 给 PostgreSQL 配置了健康检查。

大体类似:

healthcheck:
  test:
    [
      "CMD-SHELL",
      "pg_isready -U shiyu -d shiyu_admin_scaffold"
    ]

  interval: 10s

  timeout: 5s

  retries: 5

其中:

pg_isready

是 PostgreSQL 自带的检查工具。

它会判断:

数据库是否真的已经可以接受连接。

而不是简单判断:

Container 是否启动。

这两个概念完全不同。


十二、Container Running 不等于 Service Ready

Docker 显示:

Up

只能说明:

进程已经启动。

但是服务可能还在初始化。

例如:

PostgreSQL:
正在初始化数据库

Redis:
正在加载 AOF

Go:
正在连接数据库并初始化

Nginx:
配置文件还没加载完成

所以一个比较完整的 Compose 系统最好都加:

healthcheck

ShiyuAdmin 目前 PostgreSQL、Redis、应用容器都有自己的健康检查。

这其实就是一个成熟 Docker Compose 项目和普通 Demo 的区别之一。


十三、再来看 Redis

Redis 的配置可以简化为:

services:

  shiyu-redis:
    image: redis:7

    container_name: shiyu-redis

    restart: unless-stopped

    ports:
      - "16379:6379"

    volumes:
      - shiyu-redis-data:/data

    command:
      redis-server --appendonly yes

    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping

      interval: 10s

      timeout: 5s

      retries: 5

    networks:
      - shiyu-network

Redis 默认端口:

6379

宿主机映射:

16379

所以本机可以:

redis-cli \
  -h 127.0.0.1 \
  -p 16379

然后:

PING

正常应该:

PONG

十四、Redis 为什么开启 AOF?

ShiyuAdmin 使用:

redis-server --appendonly yes

也就是开启:

AOF
Append Only File

简单理解:

Redis 每执行一个写操作,就可以把操作记录下来。

例如:

SET token abc
HSET user:1 name javapub
LPUSH queue task1

这样 Redis 重启以后,可以根据 AOF 文件恢复数据。

同时 Compose 还挂载:

volumes:
  - shiyu-redis-data:/data

所以:

Redis
↓
/data
↓
shiyu-redis-data

Redis 数据也不会随着容器重新创建直接消失。


十五、Redis Health Check

Redis 健康检查更简单:

healthcheck:
  test:
    - CMD
    - redis-cli
    - ping

  interval: 10s

  timeout: 5s

  retries: 5

实际上执行的就是:

redis-cli ping

正常返回:

PONG

然后 Docker 就会把它标记为:

healthy

十六、最重要的 shiyu-app

接下来才是整个 ShiyuAdmin 部署最核心的部分:

shiyu-app

可以简化成:

services:

  shiyu-app:

    image:
      ${SHIYU_IMAGE:-ghcr.io/rodert/shiyuadmin:latest}

    container_name:
      shiyu-app

    restart:
      unless-stopped

    ports:
      - "18000:80"

    environment:
      - TZ=Asia/Shanghai
      - CONFIG_FILE=configs/config.docker.yaml

    depends_on:

      shiyu-postgres:
        condition: service_healthy

      shiyu-redis:
        condition: service_healthy

    networks:
      - shiyu-network

注意:

ShiyuAdmin 当前并没有:

shiyu-frontend
shiyu-backend

两个正式运行容器。

而是:

shiyu-app

一个容器同时包含:

React Build
Go Server
Nginx
Supervisor

这部分是整个方案最值得拆解的地方。


十七、为什么把前后端打进一个镜像?

传统前后端分离:

frontend container
backend container

没有错。

但是对于 ShiyuAdmin 这种项目:

后台管理系统
单体后端
SPA 前端
单机部署

其实可以进一步简化。

React 最终构建以后,并不是一个需要 Node.js 长期运行的服务。

React:

npm run build

最终得到:

dist/

里面主要就是:

index.html
*.js
*.css
images
fonts

这些都是静态文件。

因此完全可以:

React
↓
Build
↓
dist
↓
Nginx

后端:

Go
↓
Build
↓
server 二进制文件

最终:

Nginx
+
server

一起放进一个镜像。

这样最终部署只需要:

一个应用容器。

十八、ShiyuAdmin Dockerfile 的核心:多阶段构建

ShiyuAdmin 当前使用的是典型的:

Multi-stage Build

可以抽象成三个阶段:

# ===============================
# Stage 1:构建 React
# ===============================

FROM node:20-alpine AS frontend-builder

WORKDIR /app

COPY frontend/shiyu-admin-web/package.json ./

RUN npm install \
    --legacy-peer-deps \
    --no-audit

COPY frontend/shiyu-admin-web/ ./

RUN npm run build

这一阶段只负责:

React Source
↓
npm install
↓
npm run build
↓
dist

运行完以后,我们真正需要的其实只是:

/app/dist

Node.js 本身在生产镜像里并不需要。


十九、第二阶段:构建 Go 后端

接下来:

FROM golang:1.23-alpine AS backend-builder

WORKDIR /app

ENV GOPROXY= https://proxy.golang.org ,direct

RUN apk add \
    --no-cache \
    gcc \
    musl-dev \
    sqlite-dev

COPY \
    backend/shiyu-admin-backend/go.mod \
    backend/shiyu-admin-backend/go.sum \
    ./

RUN go mod download

COPY \
    backend/shiyu-admin-backend/ \
    ./

RUN CGO_ENABLED=1 \
    GOOS=linux \
    go build \
    -trimpath \
    -ldflags="-s -w" \
    -o /server \
    ./cmd/server

这一层做:

Go Source
↓
go mod download
↓
go build
↓
/server

最后真正需要的是:

/server

而不是:

Golang 编译器
Go Module Cache
Source Code
gcc

二十、第三阶段才是真正的生产镜像

最后:

FROM nginx:alpine

生产镜像以:

nginx:alpine

为基础。

再安装:

RUN apk add \
    --no-cache \
    ca-certificates \
    tzdata \
    supervisor \
    sqlite-libs

然后复制 React:

COPY \
    --from=frontend-builder \
    /app/dist \
    /usr/share/nginx/html

复制 Go:

COPY \
    --from=backend-builder \
    /server \
    /app/server

复制配置:

COPY \
    --from=backend-builder \
    /app/configs \
    /app/configs

再复制 Nginx:

COPY \
    deploy/nginx.conf \
    /etc/nginx/conf.d/default.conf

以及 Supervisor:

COPY \
    deploy/supervisord.conf \
    /etc/supervisord.conf

最后:

WORKDIR /app

EXPOSE 80

CMD [
  "/usr/bin/supervisord",
  "-c",
  "/etc/supervisord.conf"
]

整个镜像最终变成:

nginx:alpine
│
├── React dist
│
├── Go server
│
├── Nginx config
│
├── Supervisor
└── backend configs

二十一、为什么不把 Node 和 Go 都留在最终镜像?

假设不用多阶段构建。

可能需要:

Node.js
npm
Go SDK
gcc
源代码
node_modules
Go Module Cache
Nginx

全部塞进最终镜像。

结果就是:

镜像更大
攻击面更大
依赖更多
部署更慢
安全性更差

多阶段构建之后:

Node
只负责 Build

Go
只负责 Build

Nginx
负责 Runtime

最终镜像不需要:

Node.js
npm
Go Compiler

这才是多阶段 Docker Build 真正解决的问题。


二十二、Nginx 同时解决前端和 API

ShiyuAdmin 生产容器里 Nginx 监听:

server {

    listen 80;

    root /usr/share/nginx/html;

    index index.html;

}

所以访问:

http://localhost:18000

实际上:

18000
↓
Docker Port Mapping
↓
Container :80
↓
Nginx
↓
React

二十三、React Router 刷新 404 怎么解决?

SPA 前端有一个经典问题。

例如访问:

/system/user

前端路由可以正常进入。

但是刷新页面以后 Nginx 会去磁盘找:

/system/user

文件。

显然不存在。

于是:

404

因此 ShiyuAdmin 的 Nginx 使用类似:

location / {

    try_files \
        $uri \
        /index.html;

}

意思就是:

文件存在
↓
直接返回

不存在
↓
回到 index.html

再由 React Router 接管

这是部署 React/Vue SPA 时非常常见的配置。


二十四、API 为什么不需要跨域?

这是这个架构另一个很实用的地方。

ShiyuAdmin 的 Nginx 会把:

/api

代理给:

127.0.0.1:8080

大体类似:

location /api {

    proxy_pass \
        http://127.0.0.1:8080;

    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;
}

因此浏览器请求:

http://localhost:18000/api/v1/system/users

实际链路:

Browser
↓
Nginx :80
↓
/api
↓
127.0.0.1:8080
↓
Go Gin

前端页面:

http://localhost:18000

API:

http://localhost:18000/api

所以浏览器看到的是:

同域
同协议
同端口

这就大大减少了:

CORS
跨域
前端 API Host 配置
生产环境域名切换

这些问题。


二十五、为什么 Nginx 能访问 127.0.0.1:8080?

前面我说:

容器 A 的 localhost
≠
容器 B 的 localhost

但是这里:

proxy_pass http://127.0.0.1:8080;

却是正确的。

因为:

Nginx
和
Go Backend

就在:

同一个 shiyu-app 容器。

所以:

Nginx localhost
=
Go Backend localhost

架构就是:

shiyu-app
│
├── nginx :80
│
└── go :8080

所以:

127.0.0.1:8080

完全合理。


二十六、一个容器两个进程怎么管理?

通常 Docker 推荐:

一个容器一个主要进程

但是实际工程里并不是绝对不能跑多个进程。

ShiyuAdmin 这里为了让:

Nginx
Go Backend

统一放入一个应用镜像,引入了:

Supervisor

配置可以理解成:

[supervisord]

nodaemon=true


[program:backend]

command=/app/server

directory=/app

autostart=true

autorestart=true


[program:nginx]

command=/usr/sbin/nginx -g "daemon off;"

autostart=true

autorestart=true

Supervisor 负责:

启动 Nginx
启动 Go

监控 Nginx
监控 Go

异常退出
↓
自动重启

最终 Docker 只需要运行:

supervisord

Supervisor 再负责两个子进程。


二十七、Docker 专用后端配置

ShiyuAdmin 还单独提供:

config.docker.yaml

因为:

本地开发配置

和:

Docker 配置

不能完全一样。

例如 Docker 环境:

server:

  port: "8080"

  mode: "debug"


database:

  driver: "postgres"

  host: "shiyu-postgres"

  port: 5432

  username: "shiyu"

  password: "shiyu123"

  database: "shiyu_admin_scaffold"

  ssl_mode: "disable"


redis:

  host: "shiyu-redis"

  port: 6379

  password: ""

  db: 0

核心就是:

database.host
=
shiyu-postgres

redis.host
=
shiyu-redis

而本地开发则可能是:

database:
  host: localhost

redis:
  host: localhost

环境不同,连接地址就不同。


二十八、CONFIG_FILE 让程序主动选择配置

Compose 中:

environment:

  - CONFIG_FILE=configs/config.docker.yaml

后端启动后可以读取:

configFile :=
    os.Getenv("CONFIG_FILE")

如果没有:

if configFile == "" {

    configFile =
        "configs/config.yaml"
}

Docker 环境:

CONFIG_FILE
↓
configs/config.docker.yaml

本地开发:

默认
↓
configs/config.yaml

这样同一个 Go 程序可以运行在不同环境。


二十九、depends_on 不是简单的启动顺序

ShiyuAdmin 应用服务依赖:

PostgreSQL
Redis

因此 Compose 使用:

depends_on:

  shiyu-postgres:
    condition: service_healthy

  shiyu-redis:
    condition: service_healthy

意思不是:

先创建 PostgreSQL Container
然后创建 Redis Container
然后马上启动 Go

而是:

启动 PostgreSQL
↓
等待 PostgreSQL healthy

启动 Redis
↓
等待 Redis healthy

两者都正常
↓
启动 shiyu-app

这就比简单写:

depends_on:
  - shiyu-postgres
  - shiyu-redis

更加可靠。


三十、ShiyuAdmin 自己也有 Health Check

应用启动以后还需要判断:

Nginx 正常吗?

Go Backend 正常吗?

API 能请求吗?

因此可以通过:

/api/v1/system/health

检查。

Compose 中类似:

healthcheck:

  test:
    [
      "CMD-SHELL",
      "wget --quiet \
       --tries=1 \
       --spider \
       http://127.0.0.1/api/v1/system/health \
       || exit 1"
    ]

  interval: 30s

  timeout: 10s

  retries: 3

  start_period: 40s

请求链路:

wget
↓
Nginx :80
↓
/api/v1/system/health
↓
Go :8080
↓
Health API

所以这一个 Health Check 同时验证了:

Nginx
+
Nginx Reverse Proxy
+
Go Backend

整条链路。


三十一、Docker Network 把三个容器连起来

Compose 最后定义:

networks:

  shiyu-network:

    driver: bridge

三个服务都加入:

networks:
  - shiyu-network

最终:

        shiyu-network
             │
     ┌───────┼────────┐
     │       │        │
     ▼       ▼        ▼
   app    postgres   redis

于是:

shiyu-app

可以访问:

shiyu-postgres:5432

以及:

shiyu-redis:6379

但是 PostgreSQL 和 Redis 没必要直接暴露给公网。


三十二、把核心 Compose 串起来看

如果按照 ShiyuAdmin 的整体设计思想重新整理,一个完整版本大体可以理解成:

services:

  # ===========================
  # PostgreSQL
  # ===========================

  shiyu-postgres:

    image:
      postgres:15

    container_name:
      shiyu-postgres

    restart:
      unless-stopped

    environment:

      POSTGRES_DB:
        shiyu_admin_scaffold

      POSTGRES_USER:
        shiyu

      POSTGRES_PASSWORD:
        shiyu123

    ports:

      - "15432:5432"

    volumes:

      - shiyu-pg-data:/var/lib/postgresql/data

    healthcheck:

      test:
        [
          "CMD-SHELL",
          "pg_isready -U shiyu -d shiyu_admin_scaffold"
        ]

      interval:
        10s

      timeout:
        5s

      retries:
        5

    networks:

      - shiyu-network


  # ===========================
  # Redis
  # ===========================

  shiyu-redis:

    image:
      redis:7

    container_name:
      shiyu-redis

    restart:
      unless-stopped

    ports:

      - "16379:6379"

    volumes:

      - shiyu-redis-data:/data

    command:

      redis-server
      --appendonly yes

    healthcheck:

      test:
        [
          "CMD",
          "redis-cli",
          "ping"
        ]

      interval:
        10s

      timeout:
        5s

      retries:
        5

    networks:

      - shiyu-network


  # ===========================
  # ShiyuAdmin
  # ===========================

  shiyu-app:

    image:
      ${SHIYU_IMAGE:-ghcr.io/rodert/shiyuadmin:latest}

    container_name:
      shiyu-app

    restart:
      unless-stopped

    ports:

      - "18000:80"

    environment:

      TZ:
        Asia/Shanghai

      CONFIG_FILE:
        configs/config.docker.yaml

    depends_on:

      shiyu-postgres:

        condition:
          service_healthy

      shiyu-redis:

        condition:
          service_healthy

    healthcheck:

      test:
        [
          "CMD-SHELL",
          "wget --quiet --tries=1 --spider http://127.0.0.1/api/v1/system/health || exit 1"
        ]

      interval:
        30s

      timeout:
        10s

      retries:
        3

      start_period:
        40s

    networks:

      - shiyu-network


# ===========================
# Network
# ===========================

networks:

  shiyu-network:

    driver:
      bridge


# ===========================
# Volume
# ===========================

volumes:

  shiyu-pg-data:

    driver:
      local


  shiyu-redis-data:

    driver:
      local

把这份配置理解以后,Docker Compose 基本就已经入门了。


三十三、为什么还有 docker-compose.local.yml?

ShiyuAdmin 当前又提供了:

docker-compose.local.yml

它并不是重新定义整套系统。

而是:

覆盖 docker-compose.yml 中的一部分配置。

例如主配置:

shiyu-app:

  image:
    ghcr.io/rodert/shiyuadmin:latest

表示正常可以直接拉取已经构建好的镜像。

但是开发者修改了源码以后,希望:

从当前源码重新 build。

于是:

services:

  shiyu-app:

    build:

      context:
        .

      dockerfile:
        Dockerfile

    image:
      shiyu-admin:local

    volumes:

      - ./backend/shiyu-admin-backend/configs:/app/configs:ro

启动时同时指定:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up -d --build

Compose 会把两份配置合并。


三十四、为什么这种覆盖设计很好用?

如果只有:

docker-compose.yml

你可能需要在:

生产模式

和:

本地源码构建模式

之间来回修改:

image:

和:

build:

非常容易误提交。

现在拆成:

docker-compose.yml
↓
基础/正式配置

docker-compose.local.yml
↓
本地开发覆盖

部署就更加清晰。

生产:

docker compose up -d

本地构建:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up -d --build

这是我比较推荐的一种 Compose 组织方式。


三十五、真正启动 ShiyuAdmin

首先:

git clone \
  https://github.com/Rodert/ShiyuAdmin.git

进入目录:

cd ShiyuAdmin

然后:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up -d --build

这里:

-d

表示:

后台运行

而:

--build

表示:

启动之前重新 Build 镜像。

三十六、这条命令背后发生了什么?

虽然用户只执行:

docker compose up -d --build

但后面实际上发生:

读取 docker-compose.yml
↓
读取 docker-compose.local.yml
↓
合并配置
↓
创建 Docker Network
↓
创建 PostgreSQL Volume
↓
创建 Redis Volume
↓
拉取 PostgreSQL
↓
拉取 Redis
↓
构建 React
↓
构建 Go
↓
生成 shiyu-app 镜像
↓
启动 PostgreSQL
↓
等待 PostgreSQL Healthy
↓
启动 Redis
↓
等待 Redis Healthy
↓
启动 shiyu-app
↓
Supervisor 启动 Go
↓
Supervisor 启动 Nginx
↓
Nginx 提供 React
↓
Nginx 代理 Go API
↓
Health Check
↓
ShiyuAdmin Ready

这就是:

一条命令部署整个系统。

三十七、查看运行状态

执行:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  ps

理想状态类似:

NAME               STATUS
shiyu-app          Up (healthy)
shiyu-postgres     Up (healthy)
shiyu-redis        Up (healthy)

重点看:

healthy

而不只是:

Up

三十八、访问 ShiyuAdmin

启动以后访问:

http://localhost:18000

进入后台。

后端 API:

http://localhost:18000/api/v1

Health Check:

http://localhost:18000/api/v1/system/health

默认管理员账号:

用户名:
admin

密码:
Admin@123

第一次体验 ShiyuAdmin 的话,基本只需要:

clone
↓
docker compose
↓
浏览器打开

三十九、怎么查看日志?

查看全部:

docker compose logs -f

只看应用:

docker compose \
  logs \
  -f \
  shiyu-app

PostgreSQL:

docker compose \
  logs \
  -f \
  shiyu-postgres

Redis:

docker compose \
  logs \
  -f \
  shiyu-redis

-f 类似 Linux:

tail -f

会持续跟踪日志。


四十、进入 ShiyuAdmin 容器

如果需要排查:

docker exec \
  -it \
  shiyu-app \
  sh

然后可以:

ls -lah

你应该能够看到:

/app/server
/app/configs

检查进程:

ps

会看到类似:

supervisord
nginx
server

这也是排查部署问题非常有效的方法。


四十一、进入 PostgreSQL

执行:

docker exec \
  -it \
  shiyu-postgres \
  psql \
  -U shiyu \
  -d shiyu_admin_scaffold

进入以后:

\dt

查看表。

例如:

SELECT *
FROM sys_users
LIMIT 10;

退出:

\q

四十二、进入 Redis

执行:

docker exec \
  -it \
  shiyu-redis \
  redis-cli

检查:

PING

返回:

PONG

查看数据库:

INFO keyspace

切换:

SELECT 0

查看 Key:

SCAN 0

ShiyuAdmin 自己后台也有 Redis 缓存管理功能,因此 Docker Redis 正常启动以后,可以直接在后台查看 Redis 数据。


四十三、只想启动 PostgreSQL + Redis 怎么办?

开发后端的时候可能不希望:

Go
React

也运行在 Docker。

比如希望:

Go
↓
IDE 本地启动

React
↓
npm run start:dev

PostgreSQL
↓
Docker

Redis
↓
Docker

那就只启动:

docker compose \
  up \
  -d \
  shiyu-postgres \
  shiyu-redis

然后本地后端使用:

database:

  host:
    localhost

  port:
    15432


redis:

  host:
    localhost

  port:
    16379

注意:

Docker 容器访问数据库:

shiyu-postgres:5432

本机程序访问:

localhost:15432

这是两种不同的网络路径。


四十四、修改代码以后怎么重新构建?

如果修改:

frontend
backend
Dockerfile

可以:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  build \
  shiyu-app

再:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up \
  -d

或者直接:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up \
  -d \
  --build

四十五、只重启 Go 应用有没有用?

可以:

docker compose restart shiyu-app

但是如果修改的是源代码:

restart

并不会重新编译。

必须:

docker compose up -d --build

区别:

restart
=
重新启动旧镜像

--build
=
重新构建新镜像

这个也很容易搞错。


四十六、停止 ShiyuAdmin

执行:

docker compose down

会:

停止 Container
删除 Container
删除 Network

但:

Volume 还在。

所以数据库数据还在。


四十七、彻底恢复到全新状态

如果你正在测试初始化逻辑,希望数据库完全清空:

docker compose down -v

再:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up \
  -d \
  --build

相当于:

删除 PostgreSQL 数据
删除 Redis 数据
重新初始化系统

再次提醒:

生产环境不要随便执行 down -v。


四十八、生产环境不要直接使用默认密码

ShiyuAdmin 为了:

开箱即用

仓库里的 Docker 示例配置会提供开发环境默认值。

但是如果真正部署生产环境:

PostgreSQL Password
JWT Secret
管理员密码

必须修改。

比如不要长期使用:

shiyu123

不要长期使用示例:

JWT Secret

更合理的是通过:

.env

管理。

例如:

POSTGRES_DB=shiyu_admin_scaffold

POSTGRES_USER=shiyu

POSTGRES_PASSWORD=change-me

JWT_SECRET=replace-with-a-long-random-secret

然后 Compose:

environment:

  POSTGRES_DB:
    ${POSTGRES_DB}

  POSTGRES_USER:
    ${POSTGRES_USER}

  POSTGRES_PASSWORD:
    ${POSTGRES_PASSWORD}

四十九、生产环境甚至不应该暴露 PostgreSQL

本地开发为了调试方便可以:

ports:
  - "15432:5432"

生产服务器如果:

只有 shiyu-app 需要连接 PostgreSQL

完全可以删除:

ports:

变成:

shiyu-postgres:

  image:
    postgres:15

  networks:
    - shiyu-network

因为:

shiyu-app

通过 Docker Network 依然可以:

shiyu-postgres:5432

连接。

但是公网访问不了 PostgreSQL。

Redis 同理。

生产环境通常应该:

公网
↓
只暴露 80 / 443

PostgreSQL
↓
内部网络

Redis
↓
内部网络

五十、生产环境可以再加 HTTPS

例如服务器前面增加:

Cloudflare
↓
Nginx / Caddy
↓
ShiyuAdmin :18000

或者:

Internet
↓
443
↓
Reverse Proxy
↓
127.0.0.1:18000
↓
ShiyuAdmin

例如:

server {

    listen 443 ssl;

    server_name admin.example.com;

    ssl_certificate
        /etc/ssl/fullchain.pem;

    ssl_certificate_key
        /etc/ssl/privkey.pem;

    location / {

        proxy_pass
            http://127.0.0.1:18000;

        proxy_set_header
            Host
            $host;

        proxy_set_header
            X-Real-IP
            $remote_addr;

        proxy_set_header
            X-Forwarded-For
            $proxy_add_x_forwarded_for;

    }

}

最终:

https://admin.example.com

就可以访问 ShiyuAdmin。


五十一、为什么我认为 ShiyuAdmin 这套 Docker 方案比较适合开源项目?

一个开源后台系统,如果 README 写:

第一步安装 PostgreSQL
第二步安装 Redis
第三步创建数据库
第四步修改数据库用户
第五步安装 Go
第六步安装 Node.js
第七步 npm install
第八步 build
第九步安装 Nginx
第十步配置代理
……

很多人看到这里就不会继续用了。

而 ShiyuAdmin 希望做到:

git clone ...

cd ShiyuAdmin

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up -d --build

然后:

http://localhost:18000

直接访问。

这才符合一个:

开箱即用的后台脚手架

应该有的体验。


五十二、这套设计真正值得借鉴的几个技术点

如果把 ShiyuAdmin 的 Docker 方案拆开,我认为真正值得其他项目借鉴的是下面这些东西。

1. Multi-stage Build

Node Builder
+
Go Builder
+
Runtime Image

构建环境和运行环境分离。


2. 前后端合并为一个部署单元

React dist
+
Go Binary
+
Nginx

降低中小项目部署复杂度。


3. Nginx 统一入口

/
↓
React

/api
↓
Go

让前后端在生产环境保持同源。


4. Supervisor 管理多个进程

Supervisor
├── Go
└── Nginx

让单应用镜像仍然可以稳定管理两个进程。


5. Docker DNS

shiyu-postgres
shiyu-redis

直接作为内部 Host。

不写 IP。


6. Health Check

不是:

容器启动就算成功。

而是:

数据库真的 Ready
Redis 真的 Ready
应用真的 Ready

7. depends_on + service_healthy

PostgreSQL Ready
+
Redis Ready
↓
ShiyuAdmin Start

降低初始化时的连接失败。


8. Volume 数据持久化

Container
可以删

Data
不能跟着删

9. Local Override

docker-compose.yml
+
docker-compose.local.yml

生产配置与本地源码构建解耦。


五十三、最终部署链路

最后再把整个 ShiyuAdmin Docker 部署链路完整串一次:

Git Clone
        │
        ▼
docker compose up
        │
        ├────────────────────────────┐
        │                            │
        ▼                            ▼
PostgreSQL                      Redis
        │                            │
        ▼                            ▼
Volume                         Volume
        │                            │
        ▼                            ▼
Health Check                   Health Check
        │                            │
        └─────────────┬──────────────┘
                      │
                    Ready
                      │
                      ▼
                 shiyu-app
                      │
          ┌───────────┴────────────┐
          │                        │
          ▼                        ▼
     React Build               Go Build
          │                        │
          └──────────┬─────────────┘
                     ▼
                Final Image
                     │
                     ▼
                Supervisor
                 ┌───┴────┐
                 │        │
                 ▼        ▼
               Nginx      Go
                 │        │
          ┌──────┘        │
          │               │
          ▼               ▼
       React           /api
          │               │
          └───────┬───────┘
                  │
                  ▼
          localhost:18000

用户最终看到的其实只有:

http://localhost:18000

但是背后:

前端构建
后端构建
Nginx
Supervisor
PostgreSQL
Redis
Docker Network
Volume
Health Check
Reverse Proxy

全部已经被 Docker Compose 管起来了。


总结

Docker Compose 真正的价值,并不是:

少敲几条 Docker 命令。

而是把整个应用运行环境:

代码
数据库
缓存
网络
端口
数据卷
依赖关系
健康检查
启动顺序

全部变成:

代码化配置。

这样无论谁拿到 ShiyuAdmin:

开发者 A 的 Mac
开发者 B 的 Windows
Linux 服务器
CI 环境

理论上都可以按照同一套方式启动。

而 ShiyuAdmin 当前采用的:

React
+
Go
+
Nginx
+
Supervisor
↓
shiyu-app

PostgreSQL
↓
shiyu-postgres

Redis
↓
shiyu-redis

我认为是一个非常适合中小型后台项目和开源脚手架的部署结构。

复杂度没有 Kubernetes 那么高,却已经解决了绝大多数单机部署问题。

最后真正留给用户的,只剩一条命令:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.local.yml \
  up -d --build

这也是我认为一个开源后台项目应该尽量做到的事情:

不只是把代码开源,而是让别人能够真正把它跑起来。

ShiyuAdmin:

https://github.com/Rodert/ShiyuAdmin
posted @ 2026-09-01 10:10  JavaPub  阅读(14)  评论(0)    收藏  举报