├── .gitignore
├── .editorconfig
├── scripts
├── install-docker.sh
├── pull-images.sh
└── install-docker-compose.sh
├── readme_files
├── swanlab-logo-single.svg
└── swanlab-logo-single-dark.svg
├── assets
└── swanlab.svg
├── LICENSE
├── README.md
├── docker
├── README.md
├── README_EN.md
├── docker-compose.yaml
├── upgrade.sh
├── install-dockerhub.sh
├── install.sh
└── install-nowsl.sh
└── README_EN.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Misc
2 | .idea
3 | .DS_Store
4 | .vscode
5 | .zed
6 |
7 | # Playground
8 | playground
9 |
10 | # Sensitive information
11 | .key
12 | data
13 | .env
14 | docker/swanlab
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = false
9 | max_line_length = 120
10 | tab_width = 2
11 |
--------------------------------------------------------------------------------
/scripts/install-docker.sh:
--------------------------------------------------------------------------------
1 | sudo apt update
2 | yes | sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
3 | curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
4 | echo \
5 | "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
6 | $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
7 | sudo apt update
8 | yes | sudo apt install docker-ce docker-ce-cli containerd.io
9 | # add the current user to the docker group
10 | sudo usermod -aG docker $USER
--------------------------------------------------------------------------------
/scripts/pull-images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 定义要下载的镜像列表
4 | images=(
5 | "ccr.ccs.tencentyun.com/self-hosted/traefik:v3.1"
6 | "ccr.ccs.tencentyun.com/self-hosted/postgres:16.1"
7 | "ccr.ccs.tencentyun.com/self-hosted/redis-stack-server:7.2.0-v15"
8 | "ccr.ccs.tencentyun.com/self-hosted/clickhouse:24.3"
9 | "ccr.ccs.tencentyun.com/self-hosted/logrotate:v1"
10 | "ccr.ccs.tencentyun.com/self-hosted/fluent-bit:3.1"
11 | "ccr.ccs.tencentyun.com/self-hosted/minio:RELEASE.2025-02-28T09-55-16Z"
12 | "ccr.ccs.tencentyun.com/self-hosted/minio-mc:RELEASE.2025-04-08T15-39-49Z"
13 | "ccr.ccs.tencentyun.com/self-hosted/swanlab-server:v2.5.0"
14 | "ccr.ccs.tencentyun.com/self-hosted/swanlab-house:v2.5.0"
15 | "ccr.ccs.tencentyun.com/self-hosted/swanlab-cloud:v2.5.0"
16 | "ccr.ccs.tencentyun.com/self-hosted/swanlab-next:v2.5.0"
17 | )
18 |
19 | # 下载镜像
20 | for image in "${images[@]}"; do
21 | docker pull "$image"
22 | done
23 |
24 | # 保存镜像到文件
25 | echo "正在打包所有镜像到 swanlab_images.tar..."
26 | docker save -o ./swanlab_images.tar "${images[@]}"
27 |
28 | echo "所有镜像都打包至 swanlab_images.tar,可直接上传该文件到目标服务器!"
--------------------------------------------------------------------------------
/scripts/install-docker-compose.sh:
--------------------------------------------------------------------------------
1 | if ! docker compose version &>/dev/null; then
2 | echo "🧐 ${yellow}Docker Compose not found, start installation...${reset}"
3 |
4 | if [[ "$(uname -s)" == "Linux" ]]; then
5 | if [ -f /etc/os-release ]; then
6 | source /etc/os-release
7 | case "$ID" in
8 | ubuntu)
9 | echo "🔧 ${yellow}Adding Docker repository with Aliyun mirror...${reset}"
10 | sudo mkdir -p /etc/apt/keyrings
11 | curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
12 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $VERSION_CODENAME stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
13 |
14 | echo "🔄 ${yellow}Updating package lists...${reset}"
15 | sudo apt-get update -qq || {
16 | echo "❌ ${red}Failed to update package lists${reset}";
17 | exit 1;
18 | }
19 |
20 | echo "📦 ${yellow}Installing docker-compose-plugin...${reset}"
21 | sudo apt-get install -qq -y docker-compose-plugin || {
22 | echo "❌ ${red}Installation failed, please check network connection${reset}";
23 | exit 1;
24 | }
25 | ;;
26 |
27 | centos)
28 | echo "🔧 ${yellow}Adding Docker repository with Aliyun mirror for CentOS...${reset}"
29 | sudo yum install -y yum-utils
30 | sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
31 |
32 | echo "🔄 ${yellow}Updating yum cache...${reset}"
33 | sudo yum makecache fast -q || {
34 | echo "❌ ${red}Failed to update yum cache${reset}";
35 | exit 1;
36 | }
37 |
38 | echo "📦 ${yellow}Installing docker-compose-plugin...${reset}"
39 | sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin || {
40 | echo "❌ ${red}Installation failed, please check network connection${reset}";
41 | exit 1;
42 | }
43 |
44 | echo "🚀 ${yellow}Starting Docker service...${reset}"
45 | sudo systemctl enable --now docker
46 | ;;
47 |
48 | *)
49 | echo "❌ ${red}Automatic installation only supported on Ubuntu/CentOS${reset}"
50 | exit 1
51 | ;;
52 | esac
53 |
54 | echo "✅ ${green}docker-compose-plugin installed successfully!${reset}"
55 | else
56 | echo "❌ ${red}Unsupported Linux distribution${reset}"
57 | exit 1
58 | fi
59 | else
60 | echo "❌ ${red}macOS/Windows detected: Docker Compose is included in Docker Desktop. Please install Docker Desktop${reset}"
61 | exit 1
62 | fi
63 | fi
64 |
--------------------------------------------------------------------------------
/readme_files/swanlab-logo-single.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/swanlab.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/readme_files/swanlab-logo-single-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | # Functional Source License, Version 1.1, Apache 2.0 Future License
2 |
3 | ## Abbreviation
4 |
5 | FSL-1.1-Apache-2.0
6 |
7 | ## Notice
8 |
9 | Copyright 2024-2025 Emotion Machine (Beijing) Technology Co., Ltd.
10 |
11 | ## Terms and Conditions
12 |
13 | ### Licensor ("We")
14 |
15 | The party offering the Software under these Terms and Conditions.
16 |
17 | ### The Software
18 |
19 | The "Software" is each version of the software that we make available under
20 | these Terms and Conditions, as indicated by our inclusion of these Terms and
21 | Conditions with the Software.
22 |
23 | ### License Grant
24 |
25 | Subject to your compliance with this License Grant and the Patents,
26 | Redistribution and Trademark clauses below, we hereby grant you the right to
27 | use, copy, modify, create derivative works, publicly perform, publicly display
28 | and redistribute the Software for any Permitted Purpose identified below.
29 |
30 | ### Permitted Purpose
31 |
32 | A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33 | means making the Software available to others in a commercial product or
34 | service that:
35 |
36 | 1. substitutes for the Software;
37 |
38 | 2. substitutes for any other product or service we offer using the Software
39 | that exists as of the date we make the Software available; or
40 |
41 | 3. offers the same or substantially similar functionality as the Software.
42 |
43 | Permitted Purposes specifically include using the Software:
44 |
45 | 1. for your internal use and access;
46 |
47 | 2. for non-commercial education;
48 |
49 | 3. for non-commercial research; and
50 |
51 | 4. in connection with professional services that you provide to a licensee
52 | using the Software in accordance with these Terms and Conditions.
53 |
54 | ### Patents
55 |
56 | To the extent your use for a Permitted Purpose would necessarily infringe our
57 | patents, the license grant above includes a license under our patents. If you
58 | make a claim against any party that the Software infringes or contributes to
59 | the infringement of any patent, then your patent license to the Software ends
60 | immediately.
61 |
62 | ### Redistribution
63 |
64 | The Terms and Conditions apply to all copies, modifications and derivatives of
65 | the Software.
66 |
67 | If you redistribute any copies, modifications or derivatives of the Software,
68 | you must include a copy of or a link to these Terms and Conditions and not
69 | remove any copyright notices provided in or with the Software.
70 |
71 | ### Disclaimer
72 |
73 | THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74 | IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75 | PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76 |
77 | IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78 | SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79 | EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80 |
81 | ### Trademarks
82 |
83 | Except for displaying the License Details and identifying us as the origin of
84 | the Software, you have no right under these Terms and Conditions to use our
85 | trademarks, trade names, service marks or product names.
86 |
87 | ## Grant of Future License
88 |
89 | We hereby irrevocably grant you an additional license to use the Software under
90 | the Apache License, Version 2.0 that is effective on the second anniversary of
91 | the date we make the Software available. On or after that date, you may use the
92 | Software under the Apache License, Version 2.0, in which case the following
93 | will apply:
94 |
95 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96 | this file except in compliance with the License.
97 |
98 | You may obtain a copy of the License at
99 |
100 | http://www.apache.org/licenses/LICENSE-2.0
101 |
102 | Unless required by applicable law or agreed to in writing, software distributed
103 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
105 | specific language governing permissions and limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Self-Hosted SwanLab
10 |
11 | SwanLab 私有化部署服务,支持Docker、云应用、纯离线环境部署方式
12 |
13 |
🔥SwanLab 在线版 ·
📃 文档 ·
报告问题 ·
DockerHub
14 |
15 | 中文 / [English](./README_EN.md)
16 |
17 |
18 |
19 | ## 📖 目录
20 |
21 | - [🌟 最近更新](#-最近更新)
22 | - [🚄 快速部署](#-快速部署)
23 | - [🔌 SDK版本兼容性](#-sdk版本兼容性)
24 | - [🚀 升级版本](#-升级版本)
25 | - [📚 资源](#-资源)
26 |
27 |
28 |
29 | ## 🌟 最近更新
30 |
31 | > 🤔**如何从旧版本升级**:同步项目仓库后,执行 `cd docker && ./upgrade.sh` 可升级至 `v2.5.0` 版本
32 |
33 | **v2.5.0 (2025.12.5)**
34 | - 为商业版开发了更全面的管理看板
35 |
36 | **v2.4 (2025.11.24)**
37 | - 不再需要开放 minio 的 9000 端口
38 | - 折线图的数据点携带时间戳信息
39 |
40 | **v2.3 (2025.11.17)**
41 | - 支持实验分组
42 |
43 | **v2.2 (2025.11.6)**
44 | - 支持折线图 x 轴自定义
45 |
46 | **v2.1 (2025.9.30)**
47 | - 上线图表视图全新UI
48 | - 同步到最新的公有云版功能
49 |
50 | **v2.0 (2025.9.1)**
51 | - 更新权限系统
52 |
53 | **v1.3 (2025.7.8)**
54 | - 同步到最新的公有云版功能
55 |
56 | **v1.2 (2025.5.30)**
57 | - Feature: 上线折线图创建和编辑功能,配置图表功能增加数据源选择功能,支持单张图表显示不同的指标
58 | - Feature: 支持在实验添加Tag标签
59 | - Feature: 支持折线图Log Scale;支持分组拖拽;增加swanlab.OpenApi开放接口
60 | - Feature: 新增「默认空间」和「默认可见性」配置,用于指定项目默认创建在对应的组织下
61 | - Optimize: 优化大量指标上传导致部分数据丢失的问题
62 | - Optimize: 大幅优化指标上传的性能问题
63 | - BugFix: 修复实验无法自动关闭的问题
64 |
65 | **v1.1 (2025.4.27)**
66 | swanlab相关镜像已更新至v1.1版本,初次使用的用户直接运行`install.sh` 即可享用v1.1版本,原v1版本用户可直接运行`docker/upgrade.sh`对`docker-compose.yaml`进行升级重启。
67 |
68 |
69 |
70 | ## 🚄 快速部署
71 |
72 | ### 1. 手动部署
73 |
74 | 克隆仓库:
75 |
76 | ```bash
77 | git clone https://github.com/swanhubx/self-hosted.git
78 | cd self-hosted/docker
79 | ```
80 |
81 | **方式一:** 使用 [DockerHub](https://hub.docker.com/search?q=swanlab) 镜像源部署:
82 |
83 | ```bash
84 | ./install-dockerhub.sh
85 | ```
86 |
87 | **方式二:** 中国地区快速部署:
88 |
89 | ```bash
90 | ./install.sh
91 | ```
92 |
93 | ### 2. 一键脚本部署
94 |
95 | **方式一:** 使用 [DockerHub](https://hub.docker.com/search?q=swanlab) 镜像源部署:
96 |
97 | ```bash
98 | curl -sO https://raw.githubusercontent.com/swanhubx/self-hosted/main/docker/install-dockerhub.sh && bash install.sh
99 | ```
100 |
101 | **方式二:** 中国地区快速部署:
102 |
103 | ```bash
104 | curl -sO https://raw.githubusercontent.com/swanhubx/self-hosted/main/docker/install.sh && bash install.sh
105 | ```
106 |
107 | 详细内容参考:[docker/README.md](./docker/README.md)
108 |
109 | 教程文档:[使用Docker进行部署](https://docs.swanlab.cn/guide_cloud/self_host/docker-deploy.html)
110 |
111 |
112 |
113 |
114 | ## 🚀 升级版本
115 |
116 | 克隆仓库同步最新的代码后,进入 `docker` 目录执行 `./upgrade.sh` 实现升级重启到最新版本。
117 |
118 | ```bash
119 | cd docker
120 | bash ./upgrade.sh
121 | ```
122 |
123 |
124 |
125 | ## 🔌 SDK版本兼容性
126 |
127 | 私有化部署版本与SDK(即[swanlab](https://github.com/SwanHubX/SwanLab) python库)的兼容性如下表:
128 |
129 | | 私有化版本 | 支持的 SDK 版本 |
130 | |-----------|------------------|
131 | | v2.5.0 | v0.6.0 ~ latest |
132 | | v2.4 | v0.6.0 ~ latest |
133 | | v2.3 | v0.6.0 ~ latest |
134 | | v2.2 | v0.6.0 ~ latest (除分组功能) |
135 | | v2.1 | v0.6.0 ~ latest (除分组功能) |
136 | | v2.0 | v0.6.0 ~ latest (除分组功能) |
137 | | v1.3 | v0.6.0 ~ latest (除分组功能) |
138 | | v1.2 | v0.6.0 ~ v0.6.4 |
139 | | v1.1 | v0.6.0 ~ v0.6.4 |
140 |
141 |
142 | [dockerhub-shield]: https://img.shields.io/docker/v/swanlab/swanlab-next?color=369eff&label=docker&labelColor=black&logoColor=white&style=flat-square
143 | [dockerhub-link]: https://hub.docker.com/r/swanlab/swanlab-next/tags
144 |
145 |
146 |
147 | ## 📚 资源
148 | - [纯离线环境部署](https://docs.swanlab.cn/guide_cloud/self_host/offline-deployment.html)
149 | - [腾讯云云应用部署](https://docs.swanlab.cn/guide_cloud/self_host/tencentcloud-app.html)
150 | - [阿里云计算巢部署](https://docs.swanlab.cn/guide_cloud/self_host/alibabacloud-computenest.html)
151 | - [常见问题](https://docs.swanlab.cn/guide_cloud/self_host/faq.html)
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 | ## 通过 Docker 部署
2 |
3 | [English](./README_EN.md)
4 |
5 | > 首先需要确保你的服务器上安装有 [docker](https://docs.docker.com/engine/install/)。如果未安装,可以参考[文档](https://yeasy.gitbook.io/docker_practice/install),或者使用我们提供的安装脚本 [scripts/install-docker.sh](../scripts/install-docker.sh)。
6 | > 若你的服务器上未安装Docker Compose插件,可以参考[官方地址](https://github.com/docker/compose/)进行下载安装,或者使用我们提供的安装脚本 [scripts/install-docker-compose.sh](../scripts/install-docker-compose.sh)。
7 |
8 | ### 在线部署
9 |
10 | 服务器可以联网时,直接执行 `./install.sh` 脚本即可开始部署。部署成功后会看到下面的 **SwanLab** 标志。
11 |
12 | ```bash
13 | $ ./install.sh
14 |
15 | ...
16 | _____ _ _
17 | / ____| | | | |
18 | | (_____ ____ _ _ __ | | __ _| |__
19 | \___ \ \ /\ / / _` | '_ \| | / _` | '_ \
20 | ____) \ V V / (_| | | | | |___| (_| | |_) |
21 | |_____/ \_/\_/ \__,_|_| |_|______\__,_|_.__/
22 |
23 | Self-Hosted Docker v2.4 - @SwanLab
24 |
25 | 🎉 Wow, the installation is complete. Everything is perfect.
26 | 🥰 Congratulations, self-hosted SwanLab can be accessed using {IP}:8000
27 | ```
28 |
29 | > `install.sh` 使用国内镜像源,如果是需要使用 [DockerHub](https://hub.docker.com/explore) 源,则可以使用 `install-dockerhub.sh` 脚本部署
30 | > 若使用 Windows 系统进行部署,在安装完成 Docker Desktop 后使用 `install-windows.sh` 脚本进行安装。
31 |
32 | ### 离线部署
33 |
34 | 1. 在联网机器上下载镜像,运行脚本 [scripts/pull-images.sh](../scripts/pull-images.sh),该脚本运行结束后会在当前下生成`swanlab_images.tar`文件,该文件包含所有镜像的压缩包。**请确保下载的机器上含有Docker运行环境。**
35 | 2. 将 `swanlab_images.tar` 文件上传到目标机器上。(可配合`sftp`工具)
36 | 3. 在目标服务器上运行 `docker load -i swanlab_images.tar` 加载镜像,等待加载成功后可以通过 `docker images` 命令查看镜像列表,将会显示所有镜像。
37 | 4. 然后跟上述在线部署一样执行 `./install.sh` 即可部署安装。
38 |
39 | ### 端口说明
40 |
41 | 如果你部署在服务器上,并希望远程访问与实验记录,那么确保开放以下两个端口:
42 |
43 | | 端口号 | 是否可配置 | 用途说明 |
44 | | ------ | ---------- | ------------------------------------------------------------- |
45 | | 8000 | 是 | 网关服务端口,可用于接收外部请求,建议在公网环境中设置为 `80` |
46 |
47 | ### 可配置项
48 |
49 | 脚本执行过程中会提示两个可配置项:
50 |
51 | 1. 数据保存路径,默认为 `./data`,建议选择一个固定的路径用于长期保存,例如 `/data`。
52 | 2. 服务暴露端口,默认为 `8000`,如果是在公网服务器上可以设置为 `80`。
53 |
54 | 如果不需要交互式配置,脚本还提供了三个命令行选项:
55 |
56 | - `-d`:用于指定数据保存路径
57 | - `-p`:服务暴露的端口
58 | - `-s`:用于跳过交互式配置。如果不希望交互式配置,则比如添加 `-s`
59 |
60 | 例如指定保存路径为 `/data`,同时暴露的端口为 `80`:
61 |
62 | ```bash
63 | $ ./install.sh -d /data -p 80 -s
64 | ```
65 |
66 | ### 执行结果
67 |
68 | 脚本执行成功后,将会创建一个 `swanlab/` 目录,并在目录下生成两个文件:
69 |
70 | - `docker-compose.yaml`:用于 Docker Compose 的配置文件
71 | - `.env`:对应的密钥文件,保存数据库对应的初始化密码
72 |
73 | 在 `swanlab` 目录下执行 `docker compose ps -a` 可以查看所有容器的运行状态:
74 |
75 | ```bash
76 | $ docker compose ps -a (base)
77 | NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
78 | swanlab-clickhouse ccr.ccs.tencentyun.com/self-hosted/clickhouse:24.3 "/entrypoint.sh" clickhouse 22 minutes ago Up 22 minutes (healthy) 8123/tcp, 9000/tcp, 9009/tcp
79 | swanlab-cloud ccr.ccs.tencentyun.com/self-hosted/swanlab-cloud:v1 "/docker-entrypoint.…" swanlab-cloud 22 minutes ago Up 21 minutes 80/tcp
80 | swanlab-fluentbit ccr.ccs.tencentyun.com/self-hosted/fluent-bit:3.0 "/fluent-bit/bin/flu…" fluent-bit 22 minutes ago Up 22 minutes 2020/tcp
81 | swanlab-house ccr.ccs.tencentyun.com/self-hosted/swanlab-house:v1 "./app" swanlab-house 22 minutes ago Up 21 minutes (healthy) 3000/tcp
82 | swanlab-logrotate ccr.ccs.tencentyun.com/self-hosted/logrotate:v1 "/sbin/tini -- /usr/…" logrotate 22 minutes ago Up 22 minutes
83 | swanlab-minio ccr.ccs.tencentyun.com/self-hosted/minio:RELEASE.2025-02-28T09-55-16Z "/usr/bin/docker-ent…" minio 22 minutes ago Up 22 minutes (healthy) 9000/tcp
84 | swanlab-next ccr.ccs.tencentyun.com/self-hosted/swanlab-next:v1 "docker-entrypoint.s…" swanlab-next 22 minutes ago Up 21 minutes 3000/tcp
85 | swanlab-postgres ccr.ccs.tencentyun.com/self-hosted/postgres:16.1 "docker-entrypoint.s…" postgres 22 minutes ago Up 22 minutes (healthy) 5432/tcp
86 | swanlab-redis ccr.ccs.tencentyun.com/self-hosted/redis-stack-server:7.2.0-v15 "/entrypoint.sh" redis 22 minutes ago Up 22 minutes (healthy) 6379/tcp
87 | swanlab-server ccr.ccs.tencentyun.com/self-hosted/swanlab-server:v1 "docker-entrypoint.s…" swanlab-server 22 minutes ago Up 21 minutes (healthy) 3000/tcp
88 | swanlab-traefik ccr.ccs.tencentyun.com/self-hosted/traefik:v3.0 "/entrypoint.sh trae…" traefik 22 minutes ago Up 22 minutes (healthy) 0.0.0.0:8000->80/tcp, [::]:8000->80/tcp
89 | ```
90 |
91 | 通过执行 `docker compose logs ` 可以查看每个容器的日志。
92 |
93 | ### 升级
94 |
95 | 执行 `./upgrade.sh` 可以进行无缝升级。可使用`./upgrade.sh file_path`来进行升级,`file_path`为`docker-compose.yaml`文件路径。,默认为`swanlab/docker-compose.yaml`
96 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Self-Hosted SwanLab
10 |
11 | Self-hosted SwanLab service supports Docker, cloud app, and fully offline deployment
12 |
13 |
🔥 Online SwanLab ·
📃 Documentation ·
Report Issues ·
DockerHub
14 |
15 | [中文](./README.md) / English
16 |
17 |
18 |
19 | ## 📖 Table of Contents
20 |
21 | - [🌟 Recent Updates](#-recent-updates)
22 | - [🚄 Quick Deployment](#-quick-deployment)
23 | - [🔌 SDK Version Compatibility](#-sdk-version-compatibility)
24 | - [🚀 Upgrade Version](#-upgrade-version)
25 | - [📚 Resources](#-resources)
26 |
27 |
28 |
29 | ## 🌟 Recent Updates
30 |
31 | > 🤔 **How to upgrade from an old version**:After syncing the project repository, run `cd docker && ./upgrade.sh` to upgrade to version `v2.5.0`
32 |
33 | **v2.5.0 (2025.12.5)**
34 | - Developed a more comprehensive management dashboard for enterprise edition
35 |
36 | **v2.4 (2025.11.24)**
37 | - No longer need to open port 9000 for minio
38 | - Data points in line charts carry timestamp information
39 |
40 | **v2.3 (2025.11.17)**
41 | - Support experiment grouping
42 |
43 | **v2.2 (2025.11.6)**
44 | - Support custom x-axis for line charts
45 |
46 | **v2.1 (2025.9.30)**
47 | - Launch new chart view UI
48 | - Sync to the latest public cloud version
49 |
50 | **v2.0 (2025.9.1)**
51 | - Update permission system
52 |
53 | **v1.3 (2025.7.8)**
54 | - Feature: Sync to the latest public cloud version
55 |
56 | **v1.2 (2025.5.30)**
57 | - Feature: Line chart creation and editing features launched; configure charts with data source selection, supporting multiple metrics in the same chart
58 | - Feature: Add Tag feature for experiments
59 | - Feature: Support Log Scale for line graphs; support drag-and-drop grouping; add swanlab.OpenApi open interfaces
60 | - Feature: Add 「Default Space」 and 「Default Visibility」 configuration, used to specify that projects are created under the corresponding organization by default
61 | - Optimize: Fix data loss when uploading large amounts of metrics
62 | - Optimize: Greatly improve the performance when uploading metrics
63 | - BugFix: Fix the issue where some experiments cannot be automatically closed
64 |
65 | **v1.1 (2025.4.27)**
66 | SwanLab images have been updated to v1.1. Users who are deploying for the first time can directly run `install.sh` to access v1.1, and users of the original v1 version can directly run `docker/upgrade.sh` to update and restart the `docker-compose.yaml`.
67 |
68 |
69 |
70 | ## 🚄 Quick Deployment
71 |
72 | ### 1. Manual Deployment
73 |
74 | Clone the repository:
75 |
76 | ```bash
77 | git clone https://github.com/swanhubx/self-hosted.git
78 | cd self-hosted/docker
79 | ```
80 |
81 | **Method 1:** Deploy using [DockerHub](https://hub.docker.com/search?q=swanlab) image source:
82 |
83 | ```bash
84 | ./install-dockerhub.sh
85 | ```
86 |
87 | **Method 2:** Quick deployment for users in China:
88 |
89 | ```bash
90 | ./install.sh
91 | ```
92 |
93 | ### 2. One-click Script Deployment
94 |
95 | **Method 1:** Deploy using [DockerHub](https://hub.docker.com/search?q=swanlab) image source:
96 |
97 | ```bash
98 | curl -sO https://raw.githubusercontent.com/swanhubx/self-hosted/main/docker/install-dockerhub.sh && bash install.sh
99 | ```
100 |
101 | **Method 2:** Quick deployment for users in China:
102 |
103 | ```bash
104 | curl -sO https://raw.githubusercontent.com/swanhubx/self-hosted/main/docker/install.sh && bash install.sh
105 | ```
106 |
107 | See also: [docker/README.md](./docker/README.md) for more details
108 |
109 | Tutorial documentation: [Deploy with Docker](https://docs.swanlab.cn/guide_cloud/self_host/docker-deploy.html)
110 |
111 |
112 |
113 | ## 🚀 Upgrade Version
114 |
115 | After cloning the repository and syncing the latest code, run `./upgrade.sh` under the `docker` directory to upgrade and restart to the latest version.
116 |
117 | ```bash
118 | cd docker
119 | bash ./upgrade.sh
120 | ```
121 |
122 |
123 |
124 | ## 🔌 SDK Version Compatibility
125 |
126 | The compatibility of the self-hosted version with the SDK (i.e., [swanlab](https://github.com/SwanHubX/SwanLab) Python library) is shown in the following table:
127 |
128 | | Self-hosted Version | Supported SDK Version |
129 | |------------------|-----------------------|
130 | | v2.5.0 | v0.6.0 ~ latest |
131 | | v2.4 | v0.6.0 ~ latest |
132 | | v2.3 | v0.6.0 ~ latest |
133 | | v2.2 | v0.6.0 ~ latest |
134 | | v2.1 | v0.6.0 ~ latest |
135 | | v2.0 | v0.6.0 ~ latest |
136 | | v1.3 | v0.6.0 ~ latest |
137 | | v1.2 | v0.6.0 ~ v0.6.4 |
138 | | v1.1 | v0.6.0 ~ v0.6.4 |
139 |
140 | [dockerhub-shield]: https://img.shields.io/docker/v/swanlab/swanlab-next?color=369eff&label=docker&labelColor=black&logoColor=white&style=flat-square
141 | [dockerhub-link]: https://hub.docker.com/r/swanlab/swanlab-next/tags
142 |
143 |
144 |
145 | ## 📚 Resources
146 | - [Offline deployment for pure offline environments](https://docs.swanlab.cn/guide_cloud/self_host/offline-deployment.html)
147 | - [Deployment on Tencent Cloud App](https://docs.swanlab.cn/guide_cloud/self_host/tencentcloud-app.html)
148 | - [Deployment on Alibaba Cloud Compute Nest](https://docs.swanlab.cn/guide_cloud/self_host/alibabacloud-computenest.html)
149 | - [FAQ](https://docs.swanlab.cn/guide_cloud/self_host/faq.html)
--------------------------------------------------------------------------------
/docker/README_EN.md:
--------------------------------------------------------------------------------
1 | ## Deployment via Docker
2 |
3 | [中文](./README.md)
4 |
5 | > First, make sure you have [docker](https://docs.docker.com/engine/install/) installed on your server. If not installed, you can refer to the [documentation](https://docs.docker.com/engine/install/), or use our installation script [scripts/install-docker.sh](../scripts/install-docker.sh).
6 | > If your server does not have Docker Compose plugin, you can refer to the [documentation](https://github.com/docker/compose/) for downloading and installing. Alternatively, you can use our provided installation script [scripts/install-docker-compose.sh](../scripts/install-docker-compose.sh).
7 |
8 | ### Online Deployment
9 |
10 | When the server can be connected to the network, simply execute the `./install-dockerhub.sh` script to start deployment. After successful deployment, you will see the **SwanLab** logo below.
11 |
12 | ```bash
13 | $ ./install.sh
14 |
15 | ...
16 | _____ _ _
17 | / ____| | | | |
18 | | (_____ ____ _ _ __ | | __ _| |__
19 | \___ \ \ /\ / / _` | '_ \| | / _` | '_ \
20 | ____) \ V V / (_| | | | | |___| (_| | |_) |
21 | |_____/ \_/\_/ \__,_|_| |_|______\__,_|_.__/
22 |
23 | Self-Hosted Docker v2.4 - @SwanLab
24 |
25 | 🎉 Wow, the installation is complete. Everything is perfect.
26 | 🥰 Congratulations, self-hosted SwanLab can be accessed using {IP}:8000
27 | ```
28 | > If deploying on Windows system, use the `install-windows.sh` script after completing Docker Desktop installation.
29 |
30 | ### Offline Deployment
31 |
32 | 1. **Download Images on a Networked Machine:** On a machine with internet access and a Docker environment, download the necessary images by running the script located at `scripts/pull-images.sh`. After the script completes, it will generate a file named `swanlab_images.tar` in the current directory. This file contains the compressed archive of all the required images. **Please ensure that the machine used for downloading has Docker properly installed and running.**
33 | 2. **Transfer the Archive to the Target Machine:** Upload the `swanlab_images.tar` file to your target machine. (You can use tools like `sftp` for this purpose).
34 | 3. **Load Images on the Target Machine:** On the target server, run the command `docker load -i swanlab_images.tar` to load the images from the archive. After the loading process is complete, you can verify the image list using the `docker images` command. It should display all the images contained in the archive.
35 | 4. **Install SwanLab:** Proceed with the installation by executing `./install.sh`, following the same steps as the online deployment method.
36 |
37 | ### Port Configuration
38 |
39 | If you deploy on a server and want to access and record experiments remotely, make sure to open the following two ports:
40 |
41 | | Port | Configurable | Description |
42 | | ---- | ------------ | ----------------------------------------------------------------------------------------------------- |
43 | | 8000 | Yes | Gateway service port. Handles external requests. For public deployments, consider setting it to `80`. |
44 |
45 | ### Configurable Items
46 |
47 | The script will prompt for two configurable items during execution:
48 |
49 | 1. Data storage path, default is `./data`. It's recommended to choose a fixed path for long-term storage, such as `/data`.
50 | 2. Service exposure port, default is `8000`. If deployed on a public server, it can be set to `80`.
51 |
52 | If interactive configuration is not needed, the script also provides three command line options:
53 |
54 | - `-d`: Specify the data storage path
55 | - `-p`: Service exposure port
56 | - `-s`: Skip interactive configuration. If you don't want interactive configuration, you must add `-s`
57 |
58 | For example, to specify the storage path as `/data` and expose port `80`:
59 |
60 | ```bash
61 | $ ./install.sh -d /data -p 80 -s
62 | ```
63 |
64 | ### Execution Results
65 |
66 | After successful script execution, a `swanlab/` directory will be created, and two files will be generated in the current directory:
67 |
68 | - `docker-compose.yaml`: Configuration file for Docker Compose
69 | - `.env`: Corresponding key file, storing the initialization password for the database
70 |
71 | Execute `docker compose ps -a` in the current directory to check the running status of all containers:
72 |
73 | ```bash
74 | $ docker compose ps -a (base)
75 | NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
76 | swanlab-clickhouse ccr.ccs.tencentyun.com/self-hosted/clickhouse:24.3 "/entrypoint.sh" clickhouse 22 minutes ago Up 22 minutes (healthy) 8123/tcp, 9000/tcp, 9009/tcp
77 | swanlab-cloud ccr.ccs.tencentyun.com/self-hosted/swanlab-cloud:v1 "/docker-entrypoint.…" swanlab-cloud 22 minutes ago Up 21 minutes 80/tcp
78 | swanlab-fluentbit ccr.ccs.tencentyun.com/self-hosted/fluent-bit:3.0 "/fluent-bit/bin/flu…" fluent-bit 22 minutes ago Up 22 minutes 2020/tcp
79 | swanlab-house ccr.ccs.tencentyun.com/self-hosted/swanlab-house:v1 "./app" swanlab-house 22 minutes ago Up 21 minutes (healthy) 3000/tcp
80 | swanlab-logrotate ccr.ccs.tencentyun.com/self-hosted/logrotate:v1 "/sbin/tini -- /usr/…" logrotate 22 minutes ago Up 22 minutes
81 | swanlab-minio ccr.ccs.tencentyun.com/self-hosted/minio:RELEASE.2025-02-28T09-55-16Z "/usr/bin/docker-ent…" minio 22 minutes ago Up 22 minutes (healthy) 9000/tcp
82 | swanlab-next ccr.ccs.tencentyun.com/self-hosted/swanlab-next:v1 "docker-entrypoint.s…" swanlab-next 22 minutes ago Up 21 minutes 3000/tcp
83 | swanlab-postgres ccr.ccs.tencentyun.com/self-hosted/postgres:16.1 "docker-entrypoint.s…" postgres 22 minutes ago Up 22 minutes (healthy) 5432/tcp
84 | swanlab-redis ccr.ccs.tencentyun.com/self-hosted/redis-stack-server:7.2.0-v15 "/entrypoint.sh" redis 22 minutes ago Up 22 minutes (healthy) 6379/tcp
85 | swanlab-server ccr.ccs.tencentyun.com/self-hosted/swanlab-server:v1 "docker-entrypoint.s…" swanlab-server 22 minutes ago Up 21 minutes (healthy) 3000/tcp
86 | swanlab-traefik ccr.ccs.tencentyun.com/self-hosted/traefik:v3.0 "/entrypoint.sh trae…" traefik 22 minutes ago Up 22 minutes (healthy) 0.0.0.0:8000->80/tcp, [::]:8000->80/tcp
87 | ```
88 |
89 | You can view the logs of each container by executing `docker compose logs `.
90 |
91 | ### Upgrade
92 |
93 | Execute `./upgrade.sh` to upgrade seamlessly.
94 |
--------------------------------------------------------------------------------
/docker/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | networks:
2 | swanlab-net:
3 | name: swanlab-net
4 |
5 | volumes:
6 | swanlab-house:
7 | name: swanlab-house
8 | fluent-bit:
9 | name: fluent-bit
10 |
11 | x-common: &common
12 | networks:
13 | - swanlab-net
14 | restart: unless-stopped
15 | logging:
16 | options:
17 | max-size: 50m
18 | max-file: "3"
19 |
20 | services:
21 | # Gateway
22 | traefik:
23 | <<: *common
24 | image: ccr.ccs.tencentyun.com/self-hosted/traefik:v3.1
25 | container_name: swanlab-traefik
26 | ports:
27 | - "8000:80"
28 | volumes:
29 | - /var/run/docker.sock:/var/run/docker.sock
30 | healthcheck:
31 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:8080/ping"]
32 | interval: 10s
33 | timeout: 5s
34 | retries: 3
35 | # Databases
36 | postgres:
37 | <<: *common
38 | image: ccr.ccs.tencentyun.com/self-hosted/postgres:16.1
39 | container_name: swanlab-postgres
40 | environment:
41 | TZ: UTC
42 | POSTGRES_USER: swanlab
43 | POSTGRES_PASSWORD: swanlab-postgres
44 | POSTGRES_DB: app
45 | volumes:
46 | - ./data/postgres/data:/var/lib/postgresql/data
47 | healthcheck:
48 | test: ["CMD-SHELL", "pg_isready"]
49 | interval: 10s
50 | timeout: 5s
51 | retries: 5
52 | labels:
53 | - "traefik.enable=false"
54 | redis:
55 | <<: *common
56 | image: ccr.ccs.tencentyun.com/self-hosted/redis-stack-server:7.2.0-v15
57 | container_name: swanlab-redis
58 | volumes:
59 | - ./data/redis:/data
60 | healthcheck:
61 | test: ["CMD", "redis-cli", "ping"]
62 | interval: 10s
63 | timeout: 5s
64 | retries: 3
65 | labels:
66 | - "traefik.enable=false"
67 | clickhouse:
68 | <<: *common
69 | image: ccr.ccs.tencentyun.com/self-hosted/clickhouse:24.3
70 | container_name: swanlab-clickhouse
71 | volumes:
72 | - ./data/clickhouse:/var/lib/clickhouse/
73 | environment:
74 | TZ: UTC
75 | CLICKHOUSE_USER: swanlab
76 | CLICKHOUSE_PASSWORD: swanlab-clickhouse
77 | CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
78 | healthcheck:
79 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:8123/ping"]
80 | interval: 10s
81 | timeout: 5s
82 | retries: 3
83 | labels:
84 | - "traefik.enable=false"
85 | logrotate:
86 | <<: *common
87 | image: ccr.ccs.tencentyun.com/self-hosted/logrotate:v1
88 | container_name: swanlab-logrotate
89 | volumes:
90 | - swanlab-house:/data
91 | environment:
92 | LOGS_DIRECTORIES: "/data"
93 | LOGROTATE_COPIES: 25
94 | LOGROTATE_SIZE: "25M"
95 | LOGROTATE_CRONSCHEDULE: "*/20 * * * * *"
96 | LOGROTATE_INTERVAL: daily
97 | labels:
98 | - "traefik.enable=false"
99 | fluent-bit:
100 | <<: *common
101 | image: ccr.ccs.tencentyun.com/self-hosted/fluent-bit:3.1
102 | container_name: swanlab-fluentbit
103 | command: ["fluent-bit/bin/fluent-bit", "-c", "/conf/fluent-bit.conf"]
104 | volumes:
105 | - fluent-bit:/data
106 | - swanlab-house:/metrics
107 | environment:
108 | LOG_PATH: /metrics/*.log
109 | CLICKHOUSE_HOST: clickhouse
110 | CLICKHOUSE_PORT: 8123
111 | CLICKHOUSE_USER: swanlab
112 | CLICKHOUSE_PASS: swanlab-clickhouse
113 | labels:
114 | - "traefik.enable=false"
115 | minio:
116 | <<: *common
117 | image: ccr.ccs.tencentyun.com/self-hosted/minio:RELEASE.2025-02-28T09-55-16Z
118 | container_name: swanlab-minio
119 | volumes:
120 | - ./data/minio:/data
121 | environment:
122 | MINIO_ROOT_USER: swanlab
123 | MINIO_ROOT_PASSWORD: swanlab-minio
124 | labels:
125 | - "traefik.http.services.minio.loadbalancer.server.port=9000"
126 | - "traefik.http.routers.minio1.rule=PathPrefix(`/swanlab-public`)"
127 | - "traefik.http.routers.minio1.middlewares=minio-host@file"
128 | - "traefik.http.routers.minio2.rule=PathPrefix(`/swanlab-private/exports`)"
129 | - "traefik.http.routers.minio2.middlewares=minio-host@file"
130 | - "traefik.http.routers.minio3.rule=PathPrefix(`/swanlab-private`)"
131 | command: server /data --console-address ":9001"
132 | healthcheck:
133 | test: ["CMD", "mc", "ready", "local"]
134 | interval: 10s
135 | timeout: 5s
136 | retries: 3
137 | create-buckets:
138 | image: ccr.ccs.tencentyun.com/self-hosted/minio-mc:RELEASE.2025-04-08T15-39-49Z
139 | container_name: swanlab-minio-mc
140 | networks:
141 | - swanlab-net
142 | depends_on:
143 | minio:
144 | condition: service_healthy
145 | entrypoint: >
146 | /bin/sh -c "
147 | mc alias set myminio http://minio:9000 swanlab swanlab-minio
148 | # private bucket
149 | mc mb --ignore-existing myminio/swanlab-private
150 | mc anonymous set private myminio/swanlab-private
151 | # public bucket
152 | mc mb --ignore-existing myminio/swanlab-public
153 | mc anonymous set public myminio/swanlab-public
154 | "
155 | labels:
156 | - "traefik.enable=false"
157 | # swanlab services
158 | swanlab-server:
159 | <<: *common
160 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-server:v2.5.0
161 | container_name: swanlab-server
162 | depends_on:
163 | postgres:
164 | condition: service_healthy
165 | redis:
166 | condition: service_healthy
167 | environment:
168 | - DATABASE_URL=postgresql://swanlab:swanlab-postgres@postgres:5432/app?schema=public
169 | - DATABASE_URL_REPLICA=postgresql://swanlab:swanlab-postgres@postgres:5432/app?schema=public
170 | - REDIS_URL=redis://default@redis:6379
171 | - SERVER_PREFIX=/api
172 | - ACCESS_KEY=swanlab
173 | - SECRET_KEY=swanlab-minio
174 | - VERSION=2.5.0
175 | labels:
176 | - "traefik.http.services.swanlab-server.loadbalancer.server.port=3000"
177 | - "traefik.http.routers.swanlab-server.rule=PathPrefix(`/api`)"
178 | - "traefik.http.routers.swanlab-server.middlewares=preprocess@file"
179 | command: bash -c "npx prisma migrate deploy && node migrate.js && pm2-runtime app.js"
180 | healthcheck:
181 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/"]
182 | interval: 10s
183 | timeout: 5s
184 | retries: 3
185 | swanlab-house:
186 | <<: *common
187 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-house:v2.5.0
188 | container_name: swanlab-house
189 | depends_on:
190 | clickhouse:
191 | condition: service_healthy
192 | redis:
193 | condition: service_healthy
194 | environment:
195 | - SH_API_PREFIX=/api/house
196 | - SH_LOG_PATH=/data/metrics.log
197 | - SH_DATABASE_URL=tcp://swanlab:swanlab-clickhouse@clickhouse:9000/app
198 | - SH_SERVER_URL=http://swanlab-server:3000/api
199 | - SH_MINIO_SECRET_ID=swanlab
200 | - SH_MINIO_SECRET_KEY=swanlab-minio
201 | - SH_DISTRIBUTED_ENABLE=true
202 | - SH_REDIS_URL=redis://default@redis:6379
203 | labels:
204 | - "traefik.http.services.swanlab-house.loadbalancer.server.port=3000"
205 | - "traefik.http.routers.swanlab-house.rule=PathPrefix(`/api/house`) || PathPrefix(`/api/internal`)"
206 | - "traefik.http.routers.swanlab-house.middlewares=preprocess@file"
207 | volumes:
208 | - swanlab-house:/data
209 | healthcheck:
210 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/house/health"]
211 | interval: 10s
212 | timeout: 5s
213 | retries: 3
214 | swanlab-cloud:
215 | <<: *common
216 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-cloud:v2.5.0
217 | container_name: swanlab-cloud
218 | depends_on:
219 | swanlab-server:
220 | condition: service_healthy
221 | labels:
222 | - "traefik.enable=false"
223 | healthcheck:
224 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:80"]
225 | interval: 5s
226 | timeout: 3s
227 | retries: 5
228 | start_period: 5s
229 | swanlab-next:
230 | <<: *common
231 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-next:v2.5.0
232 | container_name: swanlab-next
233 | depends_on:
234 | swanlab-server:
235 | condition: service_healthy
236 | environment:
237 | - NEXT_CLOUD_URL=http://swanlab-cloud:80
238 | labels:
239 | - "traefik.http.services.swanlab-next.loadbalancer.server.port=3000"
240 | - "traefik.http.routers.swanlab-next.rule=PathPrefix(`/`)"
241 | healthcheck:
242 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000"]
243 | interval: 5s
244 | timeout: 3s
245 | retries: 5
246 | start_period: 5s
247 |
--------------------------------------------------------------------------------
/docker/upgrade.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # update docker-compose.yaml swanlab.* image version
4 |
5 | COMPOSE_FILE="${1:-swanlab/docker-compose.yaml}"
6 | # update swanlab-server replica database config
7 | add_replica_env() {
8 | sed -i.bak -E '
9 | /^[[:space:]]*swanlab-server:/,/^$/ {
10 | /environment:/,/^[[:space:]]*- / {
11 | /^[[:space:]]*- DATABASE_URL=/ {
12 | p
13 | s/DATABASE_URL=/DATABASE_URL_REPLICA=/
14 | }
15 | }
16 | }
17 | ' "$COMPOSE_FILE"
18 | }
19 |
20 | # 为指定 service 添加 traefik 端口 label
21 | add_traefik_port_label() {
22 | local service=$1
23 | local port=$2
24 | add_new_var "$service" "labels" "- \"traefik.http.services.${service}.loadbalancer.server.port=${port}\""
25 | }
26 |
27 | # 向某一个服务下新增一个子块加一条值,例如向clickhouse添加
28 | # labels:
29 | # - "traefik.enable=false"
30 | add_new_key_with_value() {
31 | local service=$1
32 | local service_name="$1"
33 | local label_key="$2"
34 | local new_val="$3"
35 | local file_path="$COMPOSE_FILE"
36 |
37 | # mktemp
38 | local tmp_file
39 | tmp_file=$(mktemp) || {
40 | echo "can not make temp file" >&2
41 | return 1
42 | }
43 | awk -v service="$service_name" \
44 | -v label="$label_key" \
45 | -v val="$new_val" '
46 | BEGIN { in_service = 0; indent = "" }
47 | $0 ~ "^ " service ":$" { in_service = 1; print; next }
48 | in_service && $0 ~ "^ image:" {
49 | print
50 | match($0, /^ */); indent = substr($0, 1, RLENGTH)
51 | print indent label ":"
52 | print indent " " val
53 | next
54 | }
55 | /^ [a-zA-Z-]+:/ && !/^ 'service':$/ { in_service = 0 }
56 | { print }
57 | ' "$file_path" > "$tmp_file"
58 |
59 | # replace file
60 | if ! mv "$tmp_file" "$file_path"; then
61 | echo "replace fail, update docker-compose.yaml locate on ${tmp_file} " >&2
62 | return 2
63 | fi
64 | }
65 |
66 | # 为服务添加健康检查
67 | add_health_check(){
68 | local service=$1
69 | local port=$2
70 |
71 | add_new_key_with_value "$service" "healthcheck" "start_period: 5s"
72 | add_new_var "$service" "healthcheck" "retries: 5"
73 | add_new_var "$service" "healthcheck" "timeout: 3s"
74 | add_new_var "$service" "healthcheck" "interval: 5s"
75 | add_new_var "$service" "healthcheck" "test: [\"CMD\", \"wget\", \"--spider\", \"-q\", \"0.0.0.0:${port}\"]"
76 | }
77 |
78 | # add new variable for containers config
79 | add_new_var() {
80 | # Arguments:
81 | # : service name
82 | # : label key
83 | # : new variable
84 | local service_name="$1"
85 | local label_key="$2"
86 | local new_val="$3"
87 | local file_path="$COMPOSE_FILE"
88 | # check arguments
89 | if [[ -z "$service_name" || -z "$new_val" ]]; then
90 | echo "error: must input service name and new environment variable" >&2
91 | return 1
92 | fi
93 |
94 | # mktemp
95 | local tmp_file
96 | tmp_file=$(mktemp) || {
97 | echo "can not make temp file" >&2
98 | return 2
99 | }
100 | # do awk operation
101 | awk -v service="$service_name" \
102 | -v label="$label_key" \
103 | -v val="$new_val" '
104 | BEGIN { in_service=0; inserted=0 }
105 | $0 ~ "^ " service ":$" { in_service=1 }
106 | /^ [a-zA-Z-]+:/ && !( $0 ~ "^ " service ":$") { in_service=0 }
107 | in_service && $0 ~ "^ " label ":" {
108 | print $0
109 | print " " val
110 | inserted=1
111 | next
112 | }
113 | { print }
114 | ' "$file_path" > "$tmp_file"
115 |
116 | # replace file
117 | if ! mv "$tmp_file" "$file_path"; then
118 | echo "replace fail, update docker-compose.yaml locate on ${tmp_file} " >&2
119 | return 3
120 | fi
121 | }
122 |
123 | # update swanlab-server command config
124 | update_server_command() {
125 | sed -i.bak '
126 | /^[[:space:]]*command: bash -c "npx prisma migrate deploy && pm2-runtime app.js"/ {
127 | s/&& pm2-runtime app.js"/\&\& node migrate.js \&\& pm2-runtime app.js"/
128 | }
129 | ' "$COMPOSE_FILE"
130 | }
131 |
132 | # change version
133 | update_version() {
134 | local version="$1"
135 |
136 | if [ -z "$version" ]; then
137 | echo "Error: Version number is required."
138 | return 1
139 | fi
140 |
141 | sed -i.bak -E "
142 | /^[[:space:]]+image: .*swanlab-.*:v[^:]+$/ {
143 | s/(:v)[^:]+$/\1${version}/
144 | }
145 | " "$COMPOSE_FILE"
146 | }
147 |
148 | # update specific service version
149 | update_service_version() {
150 | local service="$1"
151 | local version="$2"
152 |
153 | if [ -z "$service" ] || [ -z "$version" ]; then
154 | echo "Error: Service name and version number are required."
155 | return 1
156 | fi
157 |
158 | sed -i.bak -E "
159 | /^[[:space:]]+image: .*${service}:[^:]+$/ {
160 | s/(:v?)[^:]+$/\1${version}/
161 | }
162 | " "$COMPOSE_FILE"
163 | }
164 |
165 | # update self-hosted version
166 | update_self_hosted_version() {
167 | local full_version="$1"
168 | sed -i.bak -E "
169 | /^[[:space:]]*swanlab-server:/,/^$/ {
170 | /^[[:space:]]*environment:/,/^$/ {
171 | /^[[:space:]]*- VERSION=[0-9]+([.][0-9]+)*[.][0-9]+/ {
172 | s/(VERSION=)[0-9]+([.][0-9]+)*[.][0-9]+/\\1${full_version}/
173 | }
174 | }
175 | }
176 | " "$COMPOSE_FILE"
177 | }
178 |
179 | # check docker-compose.yaml exists
180 | if [ ! -f "$COMPOSE_FILE" ]; then
181 | echo "docker-compose.yaml not found, please run install.sh directly"
182 | exit 1
183 | fi
184 |
185 | # # confirm information
186 | read -p "Updating the container version will restart docker compose. Do you agree? [y/N] " confirm
187 |
188 | # # check y or Y
189 | if [[ "$confirm" == [yY] || "$confirm" == [yY][eE][sS] ]]; then
190 | echo "begin update"
191 | # 更新设置页面版本号
192 | update_self_hosted_version "2.5.0"
193 | # update all containers version
194 | update_version "2.5.0"
195 | update_service_version "fluent-bit" "3.1"
196 | update_service_version "traefik" "3.1"
197 |
198 | # update DATABASE_URL_REPLICA
199 | if ! grep -q "DATABASE_URL_REPLICA" "$COMPOSE_FILE"; then
200 | add_replica_env
201 | fi
202 |
203 | # update traefik port label
204 | if ! grep -q "traefik.http.services.swanlab-server.loadbalancer.server.port=3000" "$COMPOSE_FILE"; then
205 | add_traefik_port_label "swanlab-server" 3000
206 | add_traefik_port_label "swanlab-house" 3000
207 | add_traefik_port_label "swanlab-next" 3000
208 |
209 | # 为指定 service 添加 traefik.enable=false
210 | add_new_key_with_value "postgres" "labels" "- \"traefik.enable=false\""
211 | add_new_key_with_value "redis" "labels" "- \"traefik.enable=false\""
212 | add_new_key_with_value "clickhouse" "labels" "- \"traefik.enable=false\""
213 | add_new_key_with_value "fluent-bit" "labels" "- \"traefik.enable=false\""
214 | add_new_key_with_value "create-buckets" "labels" "- \"traefik.enable=false\""
215 | add_new_key_with_value "swanlab-cloud" "labels" "- \"traefik.enable=false\""
216 | fi
217 |
218 | # update swanlab-server command
219 | if ! grep -q "node migrate.js" "$COMPOSE_FILE"; then
220 | update_server_command
221 | fi
222 |
223 | # delete backup
224 | rm -f "${COMPOSE_FILE}.bak"
225 |
226 | # add new variable for containers config, it only can be added once and can not be update existing
227 | # add swanlab-house environment variable
228 | if ! grep -q "SH_DISTRIBUTED_ENABLE" "$COMPOSE_FILE"; then
229 | add_new_var "swanlab-house" "environment" "- SH_DISTRIBUTED_ENABLE=true"
230 | fi
231 | if ! grep -q "SH_REDIS_URL" "$COMPOSE_FILE"; then
232 | add_new_var "swanlab-house" "environment" "- SH_REDIS_URL=redis://default@redis:6379"
233 | fi
234 | # add swanlab-server environment variable
235 | if ! grep -q "VERSION" "$COMPOSE_FILE"; then
236 | add_new_var "swanlab-server" "environment" "- VERSION=2.4.0"
237 | fi
238 |
239 | # add missing minio middleware if needed
240 | if ! grep -q 'traefik.http.routers.minio3.rule=PathPrefix(`/swanlab-private`)' "$COMPOSE_FILE"; then
241 | add_new_var "minio" "labels" "- \"traefik.http.routers.minio3.rule=PathPrefix(\`/swanlab-private\`)\""
242 | fi
243 | if ! grep -q "traefik.http.routers.minio2.middlewares=minio-host@file" "$COMPOSE_FILE"; then
244 | add_new_var "minio" "labels" "- \"traefik.http.routers.minio2.middlewares=minio-host@file\""
245 | fi
246 | if ! grep -q 'traefik.http.routers.minio2.rule=PathPrefix(`/swanlab-private/exports`)' "$COMPOSE_FILE"; then
247 | add_new_var "minio" "labels" "- \"traefik.http.routers.minio2.rule=PathPrefix(\`/swanlab-private/exports\`)\""
248 | fi
249 | # delete old minio labels if exists
250 | if grep -q 'traefik.http.routers.minio2.rule=PathPrefix(`/swanlab-private`)' "$COMPOSE_FILE"; then
251 | sed -i.bak '\|traefik\.http\.routers\.minio2\.rule=PathPrefix(`/swanlab-private`)|d' "$COMPOSE_FILE"
252 | fi
253 | # delete minio ports mapping
254 | # 删除以 'ports:' 开头,并且下一行包含 9000:9000 的两行
255 | sed -i.bak '/^[[:space:]]*ports:[[:space:]]*$/{
256 | N
257 | /"9000:9000"/d
258 | }' "$COMPOSE_FILE"
259 | # add healthcheck for swanlab-cloud
260 | if ! grep -A 10 'swanlab-cloud:' "$COMPOSE_FILE" | grep -q '^ healthcheck:'; then
261 | add_health_check "swanlab-cloud" 80
262 | fi
263 | # add healthcheck for swanlab-next
264 | if ! grep -A 10 'swanlab-next:' "$COMPOSE_FILE" | grep -q '^ healthcheck:'; then
265 | add_health_check "swanlab-next" 3000
266 | fi
267 | # restart docker-compose
268 | docker compose -f "$COMPOSE_FILE" up -d
269 |
270 | echo "⏳ Waiting for services to become healthy..."
271 |
272 | # Define services to check (based on docker-compose.yml)
273 | SERVICES=(
274 | swanlab-server
275 | swanlab-house
276 | swanlab-cloud
277 | swanlab-next
278 | swanlab-postgres
279 | swanlab-clickhouse
280 | swanlab-redis
281 | swanlab-minio
282 | swanlab-traefik
283 | )
284 |
285 | NOT_HEALTHY_SERVICES=()
286 |
287 | # Wait for each service to become healthy (timeout = 30s)
288 | for SERVICE in "${SERVICES[@]}"; do
289 | echo -n "🔍 Checking $SERVICE..."
290 | for i in {1..30}; do
291 | STATUS=$(docker inspect --format='{{.State.Health.Status}}' $SERVICE 2>/dev/null || echo "starting")
292 | if [ "$STATUS" == "healthy" ]; then
293 | echo " ✅ healthy"
294 | break
295 | fi
296 | sleep 1
297 | done
298 | if [ "$STATUS" != "healthy" ]; then
299 | echo " ❌ $SERVICE is not healthy after timeout."
300 | NOT_HEALTHY_SERVICES+=("$SERVICE")
301 | fi
302 | done
303 |
304 | if [ ${#NOT_HEALTHY_SERVICES[@]} -ne 0 ]; then
305 | echo -e "\n\033[0;31m❌ Oops! The following services failed to start properly:\033[0m"
306 | for SERVICE in "${NOT_HEALTHY_SERVICES[@]}"; do
307 | echo " - $SERVICE"
308 | done
309 | echo -e "\n🔧 You can check logs using: docker logs "
310 | echo "💡 Or inspect health details: docker inspect "
311 | exit 1
312 | else
313 | echo "finish update"
314 | fi
315 | else
316 | echo "update canceled"
317 | exit 1
318 | fi
--------------------------------------------------------------------------------
/docker/install-dockerhub.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # some fancy scripts
4 | red=$(tput setaf 1)
5 | green=$(tput setaf 2)
6 | yellow=$(tput setaf 3)
7 | bold=$(tput bold)
8 | reset=$(tput sgr0)
9 |
10 | # -----------------------------------------------
11 | # The following are all the parameters that can be set.
12 | # the path to store data
13 | # shell flag: -d, such as: -d /path/to/data
14 | DATA_PATH="./data"
15 | # the port to expose, and port 8000 is the default network port for web servers using HTTP.
16 | # shell flag: -p, such as: -p 8080
17 | EXPOSE_PORT=8000
18 | # skip input, default is 0
19 | # shell flag: -s, if -s is added, no manual input is required
20 | SKIP_INPUT=0
21 |
22 | while getopts ":d:p:" opt; do
23 | case ${opt} in
24 | d )
25 | DATA_PATH="$OPTARG"
26 | ;;
27 | p )
28 | EXPOSE_PORT="$OPTARG"
29 | ;;
30 | s )
31 | SKIP_INPUT=1
32 | ;;
33 | \? )
34 | echo "🧐 Invalid option: -$OPTARG" >&2
35 | exit 1
36 | ;;
37 | : )
38 | echo "🧐 Option -$OPTARG need a parameter." >&2
39 | exit 1
40 | ;;
41 | esac
42 | done
43 |
44 | # -----------------------------------------------
45 |
46 | # check if docker is installed
47 | if ! command -v docker &>/dev/null; then
48 | echo "😰 ${red}Docker is not installed.${reset}"
49 | echo "😁 ${bold}Please use the install script (${green}install-docker.sh${reset})${bold} located in the scripts directory.${reset}"
50 | echo
51 | exit 1
52 | fi
53 |
54 | echo "🤩 ${bold}Docker is installed, so let's get started.${reset}"
55 |
56 | # check if docker daemon is running
57 | echo "🧐 Checking if Docker is running..."
58 | docker_not_running=0
59 |
60 | # Different platform methods
61 | if [[ "$(uname -s)" == "Linux" ]]; then
62 | # Linux use systemctl to check
63 | if ! systemctl is-active docker >/dev/null 2>&1; then
64 | docker_not_running=1
65 | fi
66 | else
67 | # macOs/Windows use docker info to check
68 | if ! docker info >/dev/null 2>&1; then
69 | docker_not_running=1
70 | fi
71 | fi
72 |
73 | if [[ $docker_not_running -eq 1 ]]; then
74 | echo "😰 ${red}Docker daemon is not running.${reset}"
75 | if [[ "$(uname -s)" == "Linux" ]]; then
76 | # Linux systems provide options for automatic startup
77 | read -p "😁 ${bold}Would you like to start Docker now? (y/n): " START_DOCKER
78 | if [[ "$START_DOCKER" =~ ^[Yy]$ ]]; then
79 | if ! systemctl start docker; then
80 | echo "❌ ${red}Failed to start Docker. You may need to run with sudo.${reset}"
81 | exit 1
82 | fi
83 | echo "🚀 Starting Docker service..."
84 | # waiting Docker startup
85 | max_attempts=5
86 | attempt=1
87 | while [ $attempt -le $max_attempts ]; do
88 | if systemctl is-active --quiet docker; then
89 | echo "✅ ${green}Docker started successfully!${reset}"
90 | break
91 | fi
92 | sleep 1
93 | ((attempt++))
94 | done
95 |
96 | # final check
97 | if ! systemctl is-active --quiet docker; then
98 | echo "❌ ${red}Docker failed to start after $max_attempts second attempts${reset}"
99 | exit 1
100 | fi
101 | else
102 | echo "👋 ${yellow}Operation canceled. Docker must be running to continue.${reset}"
103 | exit 1
104 | fi
105 | else
106 | # macOS/Windows systems don't provide automatic startup options
107 | echo "💡 ${yellow}Please start Docker Desktop manually and ensure it's running:"
108 | echo "1. Open Docker Desktop application"
109 | echo "2. Wait until the whale icon shows \"Docker Desktop is running\""
110 | echo "3. Rerun this installation script${reset}"
111 | exit 1
112 | fi
113 | fi
114 |
115 | mkdir -p swanlab && cd swanlab
116 |
117 | # Select whether to use this configuration
118 | if [ ! -f .env ] && [ "$SKIP_INPUT" -eq 0 ]; then
119 | # ---- DATA_PATH
120 | echo
121 | read -p "1. Use the default path (${bold}$DATA_PATH${reset})? (y/n): " USE_DEFAULT
122 |
123 | # Process the user's choice
124 | if [[ -n "$USE_DEFAULT" && ! "$USE_DEFAULT" =~ ^[Yy]$ ]]; then
125 | read -p " Enter a custom path: " DATA_PATH
126 | fi
127 | # Trim the end of `/`
128 | DATA_PATH=$(echo "$DATA_PATH" | sed 's:/*$::')
129 | echo " The selected path is: ${green}$DATA_PATH${reset}"
130 |
131 | # ---- EXPOSE_PORT
132 | read -p "2. Use the default port (${bold}$EXPOSE_PORT${reset})? (y/n): " USE_DEFAULT1
133 |
134 | # Process the user's choice
135 | if [[ -n "$USE_DEFAULT1" && ! "$USE_DEFAULT1" =~ ^[Yy]$ ]]; then
136 | read -p " Enter a custom port: " EXPOSE_PORT
137 | fi
138 | echo " The exposed port is: ${green}$EXPOSE_PORT${reset}
139 | "
140 | fi
141 |
142 | # random 12-digit password include numbers, uppercase and lowercase letters
143 | random_password() {
144 | openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | cut -c1-10
145 | }
146 |
147 | POSTGRES_PASSWORD=$(random_password)
148 | CLICKHOUSE_PASSWORD=$(random_password)
149 | MINIO_ROOT_PASSWORD=$(random_password)
150 |
151 | if [ ! -f ".env" ]; then
152 | echo "🤐 Create .env file in current directory to save keys"
153 | echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" > .env
154 | echo "CLICKHOUSE_PASSWORD=$CLICKHOUSE_PASSWORD" >> .env
155 | echo "MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD" >> .env
156 | echo "DATA_PATH=$DATA_PATH" >> .env
157 | echo "EXPOSE_PORT=$EXPOSE_PORT" >> .env
158 | fi
159 |
160 | export $(grep -v '^#' .env | xargs)
161 |
162 | echo "🥳 Everything's ready for execution."
163 |
164 | # create docker-compose.yaml
165 | [ -f "docker-compose.yaml" ] && rm docker-compose.yaml
166 | cat > docker-compose.yaml <
314 | /bin/sh -c "
315 | mc alias set myminio http://minio:9000 swanlab ${MINIO_ROOT_PASSWORD}
316 | # private bucket
317 | mc mb --ignore-existing myminio/swanlab-private
318 | mc anonymous set private myminio/swanlab-private
319 | # public bucket
320 | mc mb --ignore-existing myminio/swanlab-public
321 | mc anonymous set public myminio/swanlab-public
322 | "
323 | # swanlab services
324 | swanlab-server:
325 | <<: *common
326 | image: swanlab/swanlab-server:v2.5.0
327 | container_name: swanlab-server
328 | depends_on:
329 | postgres:
330 | condition: service_healthy
331 | redis:
332 | condition: service_healthy
333 | environment:
334 | - DATABASE_URL=postgresql://swanlab:${POSTGRES_PASSWORD}@postgres:5432/app?schema=public
335 | - DATABASE_URL_REPLICA=postgresql://swanlab:${POSTGRES_PASSWORD}@postgres:5432/app?schema=public
336 | - REDIS_URL=redis://default@redis:6379
337 | - SERVER_PREFIX=/api
338 | - ACCESS_KEY=swanlab
339 | - SECRET_KEY=${MINIO_ROOT_PASSWORD}
340 | - VERSION=2.5.0
341 | labels:
342 | - "traefik.http.services.swanlab-server.loadbalancer.server.port=3000"
343 | - "traefik.http.routers.swanlab-server.rule=PathPrefix(\`/api\`)"
344 | - "traefik.http.routers.swanlab-server.middlewares=preprocess@file"
345 | command: bash -c "npx prisma migrate deploy && node migrate.js && pm2-runtime app.js"
346 | healthcheck:
347 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/"]
348 | interval: 10s
349 | timeout: 5s
350 | retries: 3
351 | swanlab-house:
352 | <<: *common
353 | image: swanlab/swanlab-house:v2.5.0
354 | container_name: swanlab-house
355 | depends_on:
356 | clickhouse:
357 | condition: service_healthy
358 | redis:
359 | condition: service_healthy
360 | environment:
361 | - SH_API_PREFIX=/api/house
362 | - SH_LOG_PATH=/data/metrics.log
363 | - SH_DATABASE_URL=tcp://swanlab:${CLICKHOUSE_PASSWORD}@clickhouse:9000/app
364 | - SH_SERVER_URL=http://swanlab-server:3000/api
365 | - SH_MINIO_SECRET_ID=swanlab
366 | - SH_MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
367 | - SH_DISTRIBUTED_ENABLE=true
368 | - SH_REDIS_URL=redis://default@redis:6379
369 | labels:
370 | - "traefik.http.services.swanlab-house.loadbalancer.server.port=3000"
371 | - "traefik.http.routers.swanlab-house.rule=PathPrefix(\`/api/house\`) || PathPrefix(\`/api/internal\`)"
372 | - "traefik.http.routers.swanlab-house.middlewares=preprocess@file"
373 | volumes:
374 | - swanlab-house:/data
375 | healthcheck:
376 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/house/health"]
377 | interval: 10s
378 | timeout: 5s
379 | retries: 3
380 | swanlab-cloud:
381 | <<: *common
382 | image: swanlab/swanlab-cloud:v2.5.0
383 | container_name: swanlab-cloud
384 | depends_on:
385 | swanlab-server:
386 | condition: service_healthy
387 | labels:
388 | - "traefik.enable=false"
389 | healthcheck:
390 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:80"]
391 | interval: 5s
392 | timeout: 3s
393 | retries: 5
394 | start_period: 5s
395 | swanlab-next:
396 | <<: *common
397 | image: swanlab/swanlab-next:v2.5.0
398 | container_name: swanlab-next
399 | depends_on:
400 | swanlab-server:
401 | condition: service_healthy
402 | environment:
403 | - NEXT_CLOUD_URL=http://swanlab-cloud:80
404 | labels:
405 | - "traefik.http.services.swanlab-next.loadbalancer.server.port=3000"
406 | - "traefik.http.routers.swanlab-next.rule=PathPrefix(\`/\`)"
407 | healthcheck:
408 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000"]
409 | interval: 5s
410 | timeout: 3s
411 | retries: 5
412 | start_period: 5s
413 | EOF
414 |
415 | # start docker services
416 | docker compose up -d
417 |
418 | echo "⏳ Waiting for services to become healthy..."
419 |
420 | SERVICES=(
421 | swanlab-server
422 | swanlab-house
423 | swanlab-cloud
424 | swanlab-next
425 | swanlab-postgres
426 | swanlab-clickhouse
427 | swanlab-redis
428 | swanlab-minio
429 | swanlab-traefik
430 | )
431 |
432 | NOT_HEALTHY_SERVICES=()
433 |
434 | # Wait for each service to become healthy (timeout = 30s)
435 | for SERVICE in "${SERVICES[@]}"; do
436 | echo -n "🔍 Checking $SERVICE..."
437 | for i in {1..30}; do
438 | STATUS=$(docker inspect --format='{{.State.Health.Status}}' $SERVICE 2>/dev/null || echo "starting")
439 | if [ "$STATUS" == "healthy" ]; then
440 | echo " ✅ healthy"
441 | break
442 | fi
443 | sleep 1
444 | done
445 | if [ "$STATUS" != "healthy" ]; then
446 | echo " ❌ $SERVICE is not healthy after timeout."
447 | NOT_HEALTHY_SERVICES+=("$SERVICE")
448 | fi
449 | done
450 |
451 | if [ ${#NOT_HEALTHY_SERVICES[@]} -ne 0 ]; then
452 | echo -e "\n\033[0;31m❌ Oops! The following services failed to start properly:\033[0m"
453 | for SERVICE in "${NOT_HEALTHY_SERVICES[@]}"; do
454 | echo " - $SERVICE"
455 | done
456 | echo -e "\n🔧 You can check logs using: docker logs "
457 | echo "💡 Or inspect health details: docker inspect "
458 | exit 1
459 | else
460 | echo -e "${green}${bold}"
461 | echo " _____ _ _ ";
462 | echo " / ____| | | | | ";
463 | echo " | (_____ ____ _ _ __ | | __ _| |__ ";
464 | echo " \___ \ \ /\ / / _\` | '_ \| | / _\` | '_ \ ";
465 | echo " ____) \ V V / (_| | | | | |___| (_| | |_) |";
466 | echo " |_____/ \_/\_/ \__,_|_| |_|______\__,_|_.__/ ";
467 | echo " ";
468 | echo " Self-Hosted Docker v2.5.0 - @SwanLab"
469 | echo -e "${reset}"
470 | echo "🎉 Wow, the installation is complete. Everything is perfect."
471 | echo "🥰 Congratulations, self-hosted SwanLab can be accessed using ${green}{IP}:${EXPOSE_PORT}${reset}"
472 | echo ""
473 | fi
--------------------------------------------------------------------------------
/docker/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # some fancy scripts
4 | red=$(tput setaf 1)
5 | green=$(tput setaf 2)
6 | yellow=$(tput setaf 3)
7 | bold=$(tput bold)
8 | reset=$(tput sgr0)
9 |
10 | # -----------------------------------------------
11 | # The following are all the parameters that can be set.
12 | # the path to store data
13 | # shell flag: -d, such as: -d /path/to/data
14 | DATA_PATH="./data"
15 | # the port to expose, and port 8000 is the default network port for web servers using HTTP.
16 | # shell flag: -p, such as: -p 8080
17 | EXPOSE_PORT=8000
18 | # skip input, default is 0
19 | # shell flag: -s, if -s is added, no manual input is required
20 | SKIP_INPUT=0
21 |
22 | while getopts ":d:p:" opt; do
23 | case ${opt} in
24 | d )
25 | DATA_PATH="$OPTARG"
26 | ;;
27 | p )
28 | EXPOSE_PORT="$OPTARG"
29 | ;;
30 | s )
31 | SKIP_INPUT=1
32 | ;;
33 | \? )
34 | echo "🧐 Invalid option: -$OPTARG" >&2
35 | exit 1
36 | ;;
37 | : )
38 | echo "🧐 Option -$OPTARG need a parameter." >&2
39 | exit 1
40 | ;;
41 | esac
42 | done
43 |
44 | # -----------------------------------------------
45 |
46 | # check if docker is installed
47 | if ! command -v docker &>/dev/null; then
48 | echo "😰 ${red}Docker is not installed.${reset}"
49 | echo "😁 ${bold}Please use the install script (${green}install-docker.sh${reset})${bold} located in the scripts directory.${reset}"
50 | echo
51 | exit 1
52 | fi
53 |
54 | echo "🤩 ${bold}Docker is installed, so let's get started.${reset}"
55 |
56 | # check if docker compose is installed
57 | if ! docker compose version &>/dev/null; then
58 | echo "😰 ${yellow}Docker Compose may not install ${reset}"
59 |
60 | if [[ "$(uname -s)" == "Linux" ]]; then
61 | echo "😁 ${bold}You can use the install script (${green}install-docker-compose.sh${reset})${bold} located in the scripts directory.${reset}"
62 | else
63 | echo "🧐 ${red}macOS/Windows detected: Docker Compose is included in Docker Desktop. Please install Docker Desktop${reset}"
64 | exit 1
65 | fi
66 | fi
67 |
68 |
69 | # check if docker daemon is running
70 | echo "🧐 Checking if Docker is running..."
71 | docker_not_running=1
72 |
73 | if docker info >/dev/null 2>&1; then
74 | docker_not_running=0
75 | fi
76 |
77 | if [[ $docker_not_running -eq 1 ]]; then
78 | echo "😰 ${red}Docker daemon is not running.${reset}"
79 | if [[ "$(uname -s)" == "Linux" ]]; then
80 | # Linux systems provide options for automatic startup
81 | read -p "😁 ${bold}Would you like to start Docker now? (y/n): " START_DOCKER
82 | if [[ "$START_DOCKER" =~ ^[Yy]$ ]]; then
83 | if ! systemctl start docker; then
84 | echo "❌ ${red}Failed to start Docker. You may need to run with sudo.${reset}"
85 | exit 1
86 | fi
87 | echo "🚀 Starting Docker service..."
88 | # waiting Docker startup
89 | max_attempts=5
90 | attempt=1
91 | while [ $attempt -le $max_attempts ]; do
92 | if systemctl is-active --quiet docker; then
93 | echo "✅ ${green}Docker started successfully!${reset}"
94 | break
95 | fi
96 | sleep 1
97 | ((attempt++))
98 | done
99 |
100 | # final check
101 | if ! systemctl is-active --quiet docker; then
102 | echo "❌ ${red}Docker failed to start after $max_attempts second attempts${reset}"
103 | exit 1
104 | fi
105 | else
106 | echo "👋 ${yellow}Operation canceled. Docker must be running to continue.${reset}"
107 | exit 1
108 | fi
109 | else
110 | # macOS/Windows systems don't provide automatic startup options
111 | echo "💡 ${yellow}Please start Docker Desktop manually and ensure it's running:"
112 | echo "1. Open Docker Desktop application"
113 | echo "2. Wait until the whale icon shows \"Docker Desktop is running\""
114 | echo "3. Rerun this installation script${reset}"
115 | exit 1
116 | fi
117 | fi
118 |
119 | mkdir -p swanlab && cd swanlab
120 |
121 | # Select whether to use this configuration
122 | if [ ! -f .env ] && [ "$SKIP_INPUT" -eq 0 ]; then
123 | # ---- DATA_PATH
124 | echo
125 | read -p "1. Use the default path (${bold}$DATA_PATH${reset})? (y/n): " USE_DEFAULT
126 |
127 | # Process the user's choice
128 | if [[ -n "$USE_DEFAULT" && ! "$USE_DEFAULT" =~ ^[Yy]$ ]]; then
129 | read -p " Enter a custom path: " DATA_PATH
130 | fi
131 | # Trim the end of `/`
132 | DATA_PATH=$(echo "$DATA_PATH" | sed 's:/*$::')
133 | echo " The selected path is: ${green}$DATA_PATH${reset}"
134 |
135 | # ---- EXPOSE_PORT
136 | read -p "2. Use the default port (${bold}$EXPOSE_PORT${reset})? (y/n): " USE_DEFAULT1
137 |
138 | # Process the user's choice
139 | if [[ -n "$USE_DEFAULT1" && ! "$USE_DEFAULT1" =~ ^[Yy]$ ]]; then
140 | read -p " Enter a custom port: " EXPOSE_PORT
141 | fi
142 | echo " The exposed port is: ${green}$EXPOSE_PORT${reset}
143 | "
144 | fi
145 |
146 | # random 12-digit password include numbers, uppercase and lowercase letters
147 | random_password() {
148 | openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | cut -c1-10
149 | }
150 |
151 | POSTGRES_PASSWORD=$(random_password)
152 | CLICKHOUSE_PASSWORD=$(random_password)
153 | MINIO_ROOT_PASSWORD=$(random_password)
154 |
155 | if [ ! -f ".env" ]; then
156 | echo "🤐 Create .env file in current directory to save keys"
157 | echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" > .env
158 | echo "CLICKHOUSE_PASSWORD=$CLICKHOUSE_PASSWORD" >> .env
159 | echo "MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD" >> .env
160 | echo "DATA_PATH=$DATA_PATH" >> .env
161 | echo "EXPOSE_PORT=$EXPOSE_PORT" >> .env
162 | fi
163 |
164 | export $(grep -v '^#' .env | xargs)
165 |
166 | echo "🥳 Everything's ready for execution."
167 |
168 | # create docker-compose.yaml
169 | [ -f "docker-compose.yaml" ] && rm docker-compose.yaml
170 | cat > docker-compose.yaml <
316 | /bin/sh -c "
317 | mc alias set myminio http://minio:9000 swanlab ${MINIO_ROOT_PASSWORD}
318 | # private bucket
319 | mc mb --ignore-existing myminio/swanlab-private
320 | mc anonymous set private myminio/swanlab-private
321 | # public bucket
322 | mc mb --ignore-existing myminio/swanlab-public
323 | mc anonymous set public myminio/swanlab-public
324 | "
325 | labels:
326 | - "traefik.enable=false"
327 | # swanlab services
328 | swanlab-server:
329 | <<: *common
330 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-server:v2.5.0
331 | container_name: swanlab-server
332 | depends_on:
333 | postgres:
334 | condition: service_healthy
335 | redis:
336 | condition: service_healthy
337 | environment:
338 | - DATABASE_URL=postgresql://swanlab:${POSTGRES_PASSWORD}@postgres:5432/app?schema=public
339 | - DATABASE_URL_REPLICA=postgresql://swanlab:${POSTGRES_PASSWORD}@postgres:5432/app?schema=public
340 | - REDIS_URL=redis://default@redis:6379
341 | - SERVER_PREFIX=/api
342 | - ACCESS_KEY=swanlab
343 | - SECRET_KEY=${MINIO_ROOT_PASSWORD}
344 | - VERSION=2.5.0
345 | labels:
346 | - "traefik.http.services.swanlab-server.loadbalancer.server.port=3000"
347 | - "traefik.http.routers.swanlab-server.rule=PathPrefix(\`/api\`)"
348 | - "traefik.http.routers.swanlab-server.middlewares=preprocess@file"
349 | command: bash -c "npx prisma migrate deploy && node migrate.js && pm2-runtime app.js"
350 | healthcheck:
351 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/"]
352 | interval: 10s
353 | timeout: 5s
354 | retries: 3
355 | swanlab-house:
356 | <<: *common
357 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-house:v2.5.0
358 | container_name: swanlab-house
359 | depends_on:
360 | clickhouse:
361 | condition: service_healthy
362 | redis:
363 | condition: service_healthy
364 | environment:
365 | - SH_API_PREFIX=/api/house
366 | - SH_LOG_PATH=/data/metrics.log
367 | - SH_DATABASE_URL=tcp://swanlab:${CLICKHOUSE_PASSWORD}@clickhouse:9000/app
368 | - SH_SERVER_URL=http://swanlab-server:3000/api
369 | - SH_MINIO_SECRET_ID=swanlab
370 | - SH_MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
371 | - SH_DISTRIBUTED_ENABLE=true
372 | - SH_REDIS_URL=redis://default@redis:6379
373 | labels:
374 | - "traefik.http.services.swanlab-house.loadbalancer.server.port=3000"
375 | - "traefik.http.routers.swanlab-house.rule=PathPrefix(\`/api/house\`) || PathPrefix(\`/api/internal\`)"
376 | - "traefik.http.routers.swanlab-house.middlewares=preprocess@file"
377 | volumes:
378 | - swanlab-house:/data
379 | healthcheck:
380 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/house/health"]
381 | interval: 10s
382 | timeout: 5s
383 | retries: 3
384 | swanlab-cloud:
385 | <<: *common
386 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-cloud:v2.5.0
387 | container_name: swanlab-cloud
388 | depends_on:
389 | swanlab-server:
390 | condition: service_healthy
391 | labels:
392 | - "traefik.enable=false"
393 | healthcheck:
394 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:80"]
395 | interval: 5s
396 | timeout: 3s
397 | retries: 5
398 | start_period: 5s
399 | swanlab-next:
400 | <<: *common
401 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-next:v2.5.0
402 | container_name: swanlab-next
403 | depends_on:
404 | swanlab-server:
405 | condition: service_healthy
406 | environment:
407 | - NEXT_CLOUD_URL=http://swanlab-cloud:80
408 | labels:
409 | - "traefik.http.services.swanlab-next.loadbalancer.server.port=3000"
410 | - "traefik.http.routers.swanlab-next.rule=PathPrefix(\`/\`)"
411 | healthcheck:
412 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000"]
413 | interval: 5s
414 | timeout: 3s
415 | retries: 5
416 | start_period: 5s
417 | EOF
418 |
419 | # start docker services
420 | docker compose up -d
421 |
422 | echo "⏳ Waiting for services to become healthy..."
423 |
424 | SERVICES=(
425 | swanlab-server
426 | swanlab-house
427 | swanlab-cloud
428 | swanlab-next
429 | swanlab-postgres
430 | swanlab-clickhouse
431 | swanlab-redis
432 | swanlab-minio
433 | swanlab-traefik
434 | )
435 |
436 | NOT_HEALTHY_SERVICES=()
437 |
438 | # Wait for each service to become healthy (timeout = 30s)
439 | for SERVICE in "${SERVICES[@]}"; do
440 | echo -n "🔍 Checking $SERVICE..."
441 | for i in {1..30}; do
442 | STATUS=$(docker inspect --format='{{.State.Health.Status}}' $SERVICE 2>/dev/null || echo "starting")
443 | if [ "$STATUS" == "healthy" ]; then
444 | echo " ✅ healthy"
445 | break
446 | fi
447 | sleep 1
448 | done
449 | if [ "$STATUS" != "healthy" ]; then
450 | echo " ❌ $SERVICE is not healthy after timeout."
451 | NOT_HEALTHY_SERVICES+=("$SERVICE")
452 | fi
453 | done
454 |
455 | if [ ${#NOT_HEALTHY_SERVICES[@]} -ne 0 ]; then
456 | echo -e "\n\033[0;31m❌ Oops! The following services failed to start properly:\033[0m"
457 | for SERVICE in "${NOT_HEALTHY_SERVICES[@]}"; do
458 | echo " - $SERVICE"
459 | done
460 | echo -e "\n🔧 You can check logs using: docker logs "
461 | echo "💡 Or inspect health details: docker inspect "
462 | exit 1
463 | else
464 | echo -e "${green}${bold}"
465 | echo " _____ _ _ ";
466 | echo " / ____| | | | | ";
467 | echo " | (_____ ____ _ _ __ | | __ _| |__ ";
468 | echo " \___ \ \ /\ / / _\` | '_ \| | / _\` | '_ \ ";
469 | echo " ____) \ V V / (_| | | | | |___| (_| | |_) |";
470 | echo " |_____/ \_/\_/ \__,_|_| |_|______\__,_|_.__/ ";
471 | echo " ";
472 | echo " Self-Hosted Docker v2.5.0 - @SwanLab"
473 | echo -e "${reset}"
474 | echo "🎉 Wow, the installation is complete. Everything is perfect."
475 | echo "🥰 Congratulations, self-hosted SwanLab can be accessed using ${green}{IP}:${EXPOSE_PORT}${reset}"
476 | echo ""
477 | fi
478 |
--------------------------------------------------------------------------------
/docker/install-nowsl.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # some fancy scripts
4 | red=$(tput setaf 1)
5 | green=$(tput setaf 2)
6 | yellow=$(tput setaf 3)
7 | bold=$(tput bold)
8 | reset=$(tput sgr0)
9 |
10 | # -----------------------------------------------
11 | # The following are all the parameters that can be set.
12 | # the path to store data
13 | # shell flag: -d, such as: -d /path/to/data
14 | DATA_PATH="./data"
15 | # the port to expose, and port 8000 is the default network port for web servers using HTTP.
16 | # shell flag: -p, such as: -p 8080
17 | EXPOSE_PORT=8000
18 | # skip input, default is 0
19 | # shell flag: -s, if -s is added, no manual input is required
20 | SKIP_INPUT=0
21 |
22 | while getopts ":d:p:" opt; do
23 | case ${opt} in
24 | d )
25 | DATA_PATH="$OPTARG"
26 | ;;
27 | p )
28 | EXPOSE_PORT="$OPTARG"
29 | ;;
30 | s )
31 | SKIP_INPUT=1
32 | ;;
33 | \? )
34 | echo "🧐 Invalid option: -$OPTARG" >&2
35 | exit 1
36 | ;;
37 | : )
38 | echo "🧐 Option -$OPTARG need a parameter." >&2
39 | exit 1
40 | ;;
41 | esac
42 | done
43 |
44 | # -----------------------------------------------
45 |
46 | # check if docker is installed
47 | if ! command -v docker &>/dev/null; then
48 | echo "😰 ${red}Docker is not installed.${reset}"
49 | echo "😁 ${bold}Please use the install script (${green}install-docker.sh${reset})${bold} located in the scripts directory.${reset}"
50 | echo
51 | exit 1
52 | fi
53 |
54 | echo "🤩 ${bold}Docker is installed, so let's get started.${reset}"
55 |
56 | # check if docker compose is installed
57 | if ! docker compose version &>/dev/null; then
58 | echo "😰 ${yellow}Docker Compose may not install ${reset}"
59 |
60 | if [[ "$(uname -s)" == "Linux" ]]; then
61 | echo "😁 ${bold}You can use the install script (${green}install-docker-compose.sh${reset})${bold} located in the scripts directory.${reset}"
62 | else
63 | echo "🧐 ${red}macOS/Windows detected: Docker Compose is included in Docker Desktop. Please install Docker Desktop${reset}"
64 | exit 1
65 | fi
66 | fi
67 |
68 |
69 | # check if docker daemon is running
70 | echo "🧐 Checking if Docker is running..."
71 | docker_not_running=1
72 |
73 | if docker info >/dev/null 2>&1; then
74 | docker_not_running=0
75 | fi
76 |
77 | if [[ $docker_not_running -eq 1 ]]; then
78 | echo "😰 ${red}Docker daemon is not running.${reset}"
79 | if [[ "$(uname -s)" == "Linux" ]]; then
80 | # Linux systems provide options for automatic startup
81 | read -p "😁 ${bold}Would you like to start Docker now? (y/n): " START_DOCKER
82 | if [[ "$START_DOCKER" =~ ^[Yy]$ ]]; then
83 | if ! systemctl start docker; then
84 | echo "❌ ${red}Failed to start Docker. You may need to run with sudo.${reset}"
85 | exit 1
86 | fi
87 | echo "🚀 Starting Docker service..."
88 | # waiting Docker startup
89 | max_attempts=5
90 | attempt=1
91 | while [ $attempt -le $max_attempts ]; do
92 | if systemctl is-active --quiet docker; then
93 | echo "✅ ${green}Docker started successfully!${reset}"
94 | break
95 | fi
96 | sleep 1
97 | ((attempt++))
98 | done
99 |
100 | # final check
101 | if ! systemctl is-active --quiet docker; then
102 | echo "❌ ${red}Docker failed to start after $max_attempts second attempts${reset}"
103 | exit 1
104 | fi
105 | else
106 | echo "👋 ${yellow}Operation canceled. Docker must be running to continue.${reset}"
107 | exit 1
108 | fi
109 | else
110 | # macOS/Windows systems don't provide automatic startup options
111 | echo "💡 ${yellow}Please start Docker Desktop manually and ensure it's running:"
112 | echo "1. Open Docker Desktop application"
113 | echo "2. Wait until the whale icon shows \"Docker Desktop is running\""
114 | echo "3. Rerun this installation script${reset}"
115 | exit 1
116 | fi
117 | fi
118 |
119 | mkdir -p swanlab && cd swanlab
120 |
121 | # Select whether to use this configuration
122 | if [ ! -f .env ] && [ "$SKIP_INPUT" -eq 0 ]; then
123 | # ---- DATA_PATH
124 | echo
125 | read -p "1. Use the default path (${bold}$DATA_PATH${reset})? (y/n): " USE_DEFAULT
126 |
127 | # Process the user's choice
128 | if [[ -n "$USE_DEFAULT" && ! "$USE_DEFAULT" =~ ^[Yy]$ ]]; then
129 | read -p " Enter a custom path: " DATA_PATH
130 | fi
131 | # Trim the end of `/`
132 | DATA_PATH=$(echo "$DATA_PATH" | sed 's:/*$::')
133 | echo " The selected path is: ${green}$DATA_PATH${reset}"
134 |
135 | # ---- EXPOSE_PORT
136 | read -p "2. Use the default port (${bold}$EXPOSE_PORT${reset})? (y/n): " USE_DEFAULT1
137 |
138 | # Process the user's choice
139 | if [[ -n "$USE_DEFAULT1" && ! "$USE_DEFAULT1" =~ ^[Yy]$ ]]; then
140 | read -p " Enter a custom port: " EXPOSE_PORT
141 | fi
142 | echo " The exposed port is: ${green}$EXPOSE_PORT${reset}
143 | "
144 | fi
145 |
146 | # random 12-digit password include numbers, uppercase and lowercase letters
147 | random_password() {
148 | openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | cut -c1-10
149 | }
150 |
151 | POSTGRES_PASSWORD=$(random_password)
152 | CLICKHOUSE_PASSWORD=$(random_password)
153 | MINIO_ROOT_PASSWORD=$(random_password)
154 |
155 | if [ ! -f ".env" ]; then
156 | echo "🤐 Create .env file in current directory to save keys"
157 | echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" > .env
158 | echo "CLICKHOUSE_PASSWORD=$CLICKHOUSE_PASSWORD" >> .env
159 | echo "MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD" >> .env
160 | echo "DATA_PATH=$DATA_PATH" >> .env
161 | echo "EXPOSE_PORT=$EXPOSE_PORT" >> .env
162 | fi
163 |
164 | export $(grep -v '^#' .env | xargs)
165 |
166 | echo "🥳 Everything's ready for execution."
167 |
168 | # create docker-compose.yaml
169 | [ -f "docker-compose.yaml" ] && rm docker-compose.yaml
170 | cat > docker-compose.yaml <
320 | /bin/sh -c "
321 | mc alias set myminio http://minio:9000 swanlab ${MINIO_ROOT_PASSWORD}
322 | # private bucket
323 | mc mb --ignore-existing myminio/swanlab-private
324 | mc anonymous set private myminio/swanlab-private
325 | # public bucket
326 | mc mb --ignore-existing myminio/swanlab-public
327 | mc anonymous set public myminio/swanlab-public
328 | "
329 | labels:
330 | - "traefik.enable=false"
331 | # swanlab services
332 | swanlab-server:
333 | <<: *common
334 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-server:v2.5.0
335 | container_name: swanlab-server
336 | depends_on:
337 | postgres:
338 | condition: service_healthy
339 | redis:
340 | condition: service_healthy
341 | environment:
342 | - DATABASE_URL=postgresql://swanlab:${POSTGRES_PASSWORD}@postgres:5432/app?schema=public
343 | - DATABASE_URL_REPLICA=postgresql://swanlab:${POSTGRES_PASSWORD}@postgres:5432/app?schema=public
344 | - REDIS_URL=redis://default@redis:6379
345 | - SERVER_PREFIX=/api
346 | - ACCESS_KEY=swanlab
347 | - SECRET_KEY=${MINIO_ROOT_PASSWORD}
348 | - VERSION=2.5.0
349 | labels:
350 | - "traefik.http.services.swanlab-server.loadbalancer.server.port=3000"
351 | - "traefik.http.routers.swanlab-server.rule=PathPrefix(\`/api\`)"
352 | - "traefik.http.routers.swanlab-server.middlewares=preprocess@file"
353 | command: bash -c "npx prisma migrate deploy && node migrate.js && pm2-runtime app.js"
354 | healthcheck:
355 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/"]
356 | interval: 10s
357 | timeout: 5s
358 | retries: 3
359 | swanlab-house:
360 | <<: *common
361 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-house:v2.5.0
362 | container_name: swanlab-house
363 | depends_on:
364 | clickhouse:
365 | condition: service_healthy
366 | redis:
367 | condition: service_healthy
368 | environment:
369 | - SH_API_PREFIX=/api/house
370 | - SH_LOG_PATH=/data/metrics.log
371 | - SH_DATABASE_URL=tcp://swanlab:${CLICKHOUSE_PASSWORD}@clickhouse:9000/app
372 | - SH_SERVER_URL=http://swanlab-server:3000/api
373 | - SH_MINIO_SECRET_ID=swanlab
374 | - SH_MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
375 | - SH_DISTRIBUTED_ENABLE=true
376 | - SH_REDIS_URL=redis://default@redis:6379
377 | labels:
378 | - "traefik.http.services.swanlab-house.loadbalancer.server.port=3000"
379 | - "traefik.http.routers.swanlab-house.rule=PathPrefix(\`/api/house\`) || PathPrefix(\`/api/internal\`)"
380 | - "traefik.http.routers.swanlab-house.middlewares=preprocess@file"
381 | volumes:
382 | - swanlab-house:/data
383 | healthcheck:
384 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000/api/house/health"]
385 | interval: 10s
386 | timeout: 5s
387 | retries: 3
388 | swanlab-cloud:
389 | <<: *common
390 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-cloud:v2.5.0
391 | container_name: swanlab-cloud
392 | depends_on:
393 | swanlab-server:
394 | condition: service_healthy
395 | labels:
396 | - "traefik.enable=false"
397 | healthcheck:
398 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:80"]
399 | interval: 5s
400 | timeout: 3s
401 | retries: 5
402 | start_period: 5s
403 | swanlab-next:
404 | <<: *common
405 | image: ccr.ccs.tencentyun.com/self-hosted/swanlab-next:v2.5.0
406 | container_name: swanlab-next
407 | depends_on:
408 | swanlab-server:
409 | condition: service_healthy
410 | environment:
411 | - NEXT_CLOUD_URL=http://swanlab-cloud:80
412 | labels:
413 | - "traefik.http.services.swanlab-next.loadbalancer.server.port=3000"
414 | - "traefik.http.routers.swanlab-next.rule=PathPrefix(\`/\`)"
415 | healthcheck:
416 | test: ["CMD", "wget", "--spider", "-q", "0.0.0.0:3000"]
417 | interval: 5s
418 | timeout: 3s
419 | retries: 5
420 | start_period: 5s
421 | EOF
422 |
423 | # start docker services
424 | docker compose up -d
425 |
426 | echo "⏳ Waiting for services to become healthy..."
427 |
428 | SERVICES=(
429 | swanlab-server
430 | swanlab-house
431 | swanlab-cloud
432 | swanlab-next
433 | swanlab-postgres
434 | swanlab-clickhouse
435 | swanlab-redis
436 | swanlab-minio
437 | swanlab-traefik
438 | )
439 |
440 | NOT_HEALTHY_SERVICES=()
441 |
442 | # Wait for each service to become healthy (timeout = 30s)
443 | for SERVICE in "${SERVICES[@]}"; do
444 | echo -n "🔍 Checking $SERVICE..."
445 | for i in {1..30}; do
446 | STATUS=$(docker inspect --format='{{.State.Health.Status}}' $SERVICE 2>/dev/null || echo "starting")
447 | if [ "$STATUS" == "healthy" ]; then
448 | echo " ✅ healthy"
449 | break
450 | fi
451 | sleep 1
452 | done
453 | if [ "$STATUS" != "healthy" ]; then
454 | echo " ❌ $SERVICE is not healthy after timeout."
455 | NOT_HEALTHY_SERVICES+=("$SERVICE")
456 | fi
457 | done
458 |
459 | if [ ${#NOT_HEALTHY_SERVICES[@]} -ne 0 ]; then
460 | echo -e "\n\033[0;31m❌ Oops! The following services failed to start properly:\033[0m"
461 | for SERVICE in "${NOT_HEALTHY_SERVICES[@]}"; do
462 | echo " - $SERVICE"
463 | done
464 | echo -e "\n🔧 You can check logs using: docker logs "
465 | echo "💡 Or inspect health details: docker inspect "
466 | exit 1
467 | else
468 | echo -e "${green}${bold}"
469 | echo " _____ _ _ ";
470 | echo " / ____| | | | | ";
471 | echo " | (_____ ____ _ _ __ | | __ _| |__ ";
472 | echo " \___ \ \ /\ / / _\` | '_ \| | / _\` | '_ \ ";
473 | echo " ____) \ V V / (_| | | | | |___| (_| | |_) |";
474 | echo " |_____/ \_/\_/ \__,_|_| |_|______\__,_|_.__/ ";
475 | echo " ";
476 | echo " Self-Hosted Docker v2.5.0 - @SwanLab"
477 | echo -e "${reset}"
478 | echo "🎉 Wow, the installation is complete. Everything is perfect."
479 | echo "🥰 Congratulations, self-hosted SwanLab can be accessed using ${green}{IP}:${EXPOSE_PORT}${reset}"
480 | echo -e "\n${bold}💾 Volume mount points:${reset}"
481 |
482 | # 获取 clickhouse-data 卷的宿主机路径
483 | CLICKHOUSE_VOLUME_PATH=$(docker volume inspect clickhouse-data --format '{{.Mountpoint}}' 2>/dev/null)
484 |
485 | if [ -n "$CLICKHOUSE_VOLUME_PATH" ]; then
486 | echo -e " - ${green}clickhouse-data location in wsl${reset}: $CLICKHOUSE_VOLUME_PATH"
487 | else
488 | echo -e " - ${red}clickhouse-data${reset}: Volume not found or Docker not running"
489 | fi
490 | echo ""
491 | fi
492 |
--------------------------------------------------------------------------------