├── .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 | SwanLab 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 | SwanLab 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 | --------------------------------------------------------------------------------