string.is Docker Compose 部署指南
string.is v1.38.0 的 Docker Compose 从零部署指南:官方镜像选型、编排文件逐项解说、部署与探活实测回显、环境变量表(含构建期统计域说明)、升级回滚与常见问题,Ubuntu 22.04 实测。
string.is Docker Compose 部署指南
目标:在 Ubuntu 22.04 服务器上以 Docker Compose 完成 string.is v1.38.0 的从零部署与验证,覆盖镜像选型、部署步骤、环境变量、升级与常见问题。全部命令与回显已在 Ubuntu 22.04(x86_64)实测通过,服务发布于 http://localhost:8128 。
一、环境信息
本文实测环境与部署目标:
- 操作系统:Ubuntu 22.04(x86_64)
- 软件版本:string.is v1.38.0(官方最新版,2025-04-15 发布)
- 部署方式:Docker Compose(单服务编排)
- 应用监听端口:
3000(服务内置,不可省略);宿主发布端口:8128(可自行更换,避开已占用端口即可) - 部署目录:
/opt/string-is(编排文件docker-compose.yml所在目录) - 运行身份:应用进程以非特权用户
nextjs(uid 1001)运行(官方内置,无需配置) - 架构支持:官方发布 linux/amd64 与 linux/arm64 双架构应用包,Docker 会按主机架构自动匹配
string.is 是无状态 Web 服务:无数据库、无服务端配置文件、无持久化数据,编排里不需要任何卷——整个部署的核心工作只有「拉应用包 → 起服务 → 发布端口」三步。
二、镜像选型说明
string.is 的官方分发渠道与版本选择建议如下:
| 渠道 | 地址 | 说明 |
|---|---|---|
| Docker Hub(官方推荐) | daveperrett/string-is | 官方 README 指定的发布渠道,v1.38.0 实测约 218 MB(228,413,743 字节),基于 node:20-alpine,Next.js standalone 运行 |
| 源码构建 | GitHub 仓库 recurser/string-is 的 Dockerfile | 三阶段构建(deps → builder → runner),适合需要自定义统计域等深度定制的场景 |
版本选择两条原则:
- 锁定具体 tag,不用
latest:latest会随官方发布漂移,部署与升级都应写死版本号(如v1.38.0),升级时显式改 tag; - tag 与 GitHub Release 一一对应:镜像
v1.38.0对应 2025-04-15 发布的 v1.38.0,可在 https://hub.docker.com/r/daveperrett/string-is/tags 核对可用版本。
选型结论:常规部署直接用 Docker Hub 官方包 + 具体版本 tag;只有当需要修改构建期统计域名(见 7.2)等构建参数时才从源码构建。
三、前置条件
部署前确认宿主已具备:
- Docker Engine 与 Docker Compose 插件(
docker compose version可执行); - 目标发布端口未被占用(本文用
8128,检查命令ss -ltnp | grep 8128无输出即空闲); - 能访问 Docker Hub(拉取失败时的代理与镜像加速处理见第十一章)。
验证环境就绪:
docker compose version
实测回显:
Docker Compose version v2.39.2
ss -ltnp | grep 8128
实测回显(端口空闲时无输出,命令直接返回):
(无输出)
四、部署步骤
4.1 编排文件
创建部署目录并写入编排文件 /opt/string-is/docker-compose.yml:
name: string-is-stack
services:
string-is:
image: daveperrett/string-is:v1.38.0
container_name: string-is-web
restart: unless-stopped
ports:
- "8128:3000"
networks:
default:
name: string-is-net
逐项说明:
image: daveperrett/string-is:v1.38.0:官方应用包 + 锁定版本 tag(选型原则见第二章);container_name:固定服务名,便于docker logs/docker inspect直接定位;不指定时 Compose 会按「目录名-服务名-序号」自动生成;restart: unless-stopped:宿主重启后自动拉起(手动 stop 的除外);
💡 技术拓展:
restart策略有四种——no(默认,不自动重启)、on-failure[:次数](仅进程异常退出时重启,适合一次性任务)、always(任何情况都重启,包括手动 stop 后 daemon 重启时)、unless-stopped(类似 always,但尊重手动 stop)。Web 常驻服务的标准选择是unless-stopped:既保证开机自启,又不会在人工停服维护后被 daemon 悄悄拉起。本文采用unless-stopped。
ports: "8128:3000":宿主8128发布到应用3000;3000是应用内置监听端口,等号右边不能改;networks:显式命名网络,便于多项目环境辨识;单项目部署也可以整个删掉,用 Compose 默认网络。
4.2 拉取应用包
docker pull daveperrett/string-is:v1.38.0
实测回显(输出节选,实测首尾):
v1.38.0: Pulling from daveperrett/string-is
Digest: sha256:dfe8775071d37ab6182fdefa8f20ed14beaa4aa5b9e9658904742e57b87d54d9
Status: Downloaded newer image for daveperrett/string-is:v1.38.0
docker.io/daveperrett/string-is:v1.38.0
再次执行时因本地已存在同版本包,回显为 Status: Image is up to date for daveperrett/string-is:v1.38.0,属正常现象。
4.3 启动服务
cd /opt/string-is && docker compose up -d
实测回显(逐行照抄):
Container string-is-web Creating
Container string-is-web Created
Container string-is-web Starting
Container string-is-web Started
4.4 验证服务
第一步:编排状态。
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
string-is-web daveperrett/string-is:v1.38.0 "docker-entrypoint.s…" string-is 14 seconds ago Up 13 seconds 0.0.0.0:8128->3000/tcp, [::]:8128->3000/tcp
STATUS 为 Up、端口映射 0.0.0.0:8128->3000/tcp 生效即启动成功。
第二步:服务日志。
docker logs string-is-web
实测回显:
Listening on port 3000
Next.js standalone 服务的启动日志只有这一行,出现即代表监听就绪;没有报错堆栈、没有配置告警——无状态服务的日志面就是这么小。
第三步:HTTP 探活。
curl -s -o /dev/null -w '首页 HTTP %{http_code}\n' http://localhost:8128/
实测回显:
首页 HTTP 200
curl -s http://localhost:8128/ | head -c 220
实测回显(输出节选):
<!DOCTYPE html><style data-cmpld="true">._t5xn1vki body{background-color:var(--_120p1lg)}@media only screen and (max-width:768px){._1irj1j28 body{background-color:transparent}}</style><html style="--_120p1lg:#FAFBFF" cla
返回 200 且响应体为 string.is 页面 HTML,浏览器访问 http://<服务器IP>:8128 即可使用全部 49 个转换器页面。运行身份与重启策略复核:
docker inspect string-is-web --format '运行用户: {{.Config.User}} | 状态: {{.State.Status}} | 重启策略: {{.HostConfig.RestartPolicy.Name}}'
实测回显:
运行用户: nextjs | 状态: running | 重启策略: unless-stopped
五、数据持久化说明
string.is 不需要任何持久化配置,编排里没有也不应有卷:
- 服务端无状态:转换逻辑全部在访问者浏览器执行,服务端不落盘、不写库,应用包本身只读;
- 用户偏好存浏览器:转换选项保存在访问者浏览器的 LocalStorage(官方隐私政策明示),换浏览器或清缓存后回到默认值;
- 升级零迁移:换版本只是替换应用包,不存在「数据目录」「配置兼容」「数据库 schema」等升级负担。
这一特性也决定了备份策略为空:没有需要备份的服务端数据;若要固化的是用户自己的转换选项,浏览器侧导出即可,与部署无关。
六、环境变量表
v1.38.0 运行时环境变量实测(docker exec string-is-web env 过滤关键项)与官方 Dockerfile 定义对照:
| 变量 | 实际值 | 作用 | 可否运行时修改 |
|---|---|---|---|
PORT | 3000 | 应用监听端口(Dockerfile ENV PORT 3000) | 否,固定值,编排里通过端口映射改对外发布端口 |
NODE_ENV | production | Node 运行模式 | 否,生产部署保持 production |
NEXT_TELEMETRY_DISABLED | 0 | Next.js 遥测开关(构建阶段为 1 关闭) | 否 |
NEXT_PUBLIC_ANALYTICS_DOMAIN | string.is | 匿名统计(Plausible)的归属域名,默认指向官网 | 否,见下方说明 |
关于 NEXT_PUBLIC_ANALYTICS_DOMAIN 需要特别注意:Next.js 的 NEXT_PUBLIC_* 变量是构建期内联进前端产物的,运行时用 environment: 覆盖不会生效。它的取值来自构建参数 ANALYTICS_DOMAIN(Dockerfile 中 ARG ANALYTICS_DOMAIN=string.is)。因此:
- 直接使用官方应用包:统计脚本以官网域名上报,自托管实例的匿名访问量会计入官方公开面板,建议在反代层拦截对
plausible.io的请求(或在公司网络出站过滤),即彻底关闭统计; - 需要归属自己域名:从源码构建并在构建时覆盖,
docker build --build-arg ANALYTICS_DOMAIN=stats.example.com .。
七、配置参数说明
7.1 服务端配置
string.is 没有服务端配置文件——没有 YAML/TOML/env 文件需要挂载,全部行为由应用包内置(端口、安全响应头、界面语言等)。「配置」维度上唯一可调整的就是第六章的环境变量与构建参数。
7.2 安全响应头(内置)
应用内置的安全响应头在部署后可直接验证(这是 string.is 的卖点之一,部署时值得确认一遍):
curl -sI http://localhost:8128/ | grep -iE 'content-security|x-frame'
实测回显(输出节选,实测首行):
Content-Security-Policy: base-uri 'none'; connect-src 'self' https://plausible.io; default-src 'none'; font-src 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; manifest-src 'self'; script-src 'self' 'unsafe-eval' https://plausible.io; script-src-elem 'self' 'unsafe-eval' https://plausible.io https://plausible.io; style-src 'self' 'unsafe-inline';
X-Frame-Options: DENY
default-src 'none' 白名单模型 + X-Frame-Options: DENY 禁嵌入,应用层安全基线开箱即得,反代层无需再补安全头。
7.3 反向代理接入(可选)
若需要域名 + TLS 统一入口,在宿主 Nginx 中新增 server 块(以 string.example.com 为例,证书由 certbot 签发):
server {
listen 443 ssl;
server_name string.example.com;
ssl_certificate /etc/letsencrypt/live/string.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/string.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8128;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
要点:proxy_pass 指向宿主发布端口 8128;string.is 自带头已完备,Nginx 不需要额外添加安全头;若按第六章方案关闭统计,可在此加 location / 内的 proxy_set_header 之外配合出站过滤,或在应用侧改用自建统计域(源码构建)。
重载前先做配置语法检查:
nginx -t
实测回显:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
语法通过后执行 systemctl reload nginx 生效,浏览器访问 https://string.example.com 验证页面正常打开。
八、配置文件挂载
结论先行:没有需要挂载的配置文件,编排里不要写 volumes。string.is 应用包自包含全部静态产物与运行时,官方 Dockerfile 也没有定义任何可挂载路径;强行挂载空目录到应用路径反而会导致页面 404。需要定制的场景(统计域、品牌文案、界面语言)都只能通过修改源码重新构建实现,不属于挂载范畴。
九、等价 docker run
不用 Compose 时,等价的单命令部署:
docker run -d --name string-is-run -p 8129:3000 daveperrett/string-is:v1.38.0
实测回显(容器 ID 样例):
28a648779df54c9f59d2da2276f687b4582b164d48f3eb8f27b9e1653d5e5f15
探活验证:
curl -s -o /dev/null -w 'docker run 实例 HTTP %{http_code}\n' http://localhost:8129/
实测回显:
docker run 实例 HTTP 200
验证后清理:
docker rm -f string-is-run
两种方式的取舍:单机长期服务用 Compose(编排文件即文档,升级/回滚/重建都是一条命令);临时试用或一次性验证用 docker run 更直接。生产建议 Compose,restart: unless-stopped 与编排可追溯性是 docker run 不具备的。
十、常用运维命令
在 /opt/string-is 目录下:
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
string-is-web daveperrett/string-is:v1.38.0 "docker-entrypoint.s…" string-is 14 seconds ago Up 13 seconds 0.0.0.0:8128->3000/tcp, [::]:8128->3000/tcp
日志跟踪(应用日志量极小,-f 长跟通常无新输出):
docker logs --tail 20 string-is-web
实测回显:
Listening on port 3000
重启服务(编排文件或宿主变更后):
docker compose restart
实测回显(逐行照抄):
Container string-is-web Restarting
Container string-is-web Started
停止并移除服务与网络(应用包保留在本地):
docker compose down
实测回显(逐行照抄):
Container string-is-web Removing
Container string-is-web Removed
Network string-is-net Removing
Network string-is-net Removed
升级流程(以未来升级到 v1.39.0 为例):
- 编辑
docker-compose.yml把image的 tag 从v1.38.0改为v1.39.0; - 拉取并滚动重建:
docker compose up -d
Compose 会自动完成「拉新包 → 停旧服务 → 以新包重建 → 启动」,全程数秒;因服务无状态,无任何数据迁移步骤。
- 探活确认:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8128/
预期输出 200。
回滚:把 tag 改回 v1.38.0 再执行一次 docker compose up -d 即可(旧版本应用包仍在本地时无需重新拉取)。
十一、常见问题
Q1:拉取应用包超时或失败?
国内网络直连 Docker Hub 不稳定时的处理链:优先配置镜像加速器(/etc/docker/daemon.json 的 registry-mirrors 后重启 docker 服务);仍失败可走 HTTP 代理拉取:
HTTPS_PROXY=http://192.168.1.62:7897 docker pull daveperrett/string-is:v1.38.0
实测环境曾全程走代理完成拉取,拉取成功判据同 4.2:回显 Digest: sha256:dfe877… 与 Status: Downloaded newer image(或已存在时 Image is up to date)。
Q2:8128 端口被占用,docker compose up 报错?
报错形如 Error starting userland proxy: listen tcp4 0.0.0.0:8128: bind: address already in use。改编排文件 ports 左侧宿主端口为空闲端口(如 "8129:3000")后重新 docker compose up -d;右侧 3000 保持不变。
Q3:升级后发现统计请求发往 plausible.io?
这是官方应用包的默认行为(构建参数 ANALYTICS_DOMAIN 默认 string.is,见第六章)。运行时改环境变量无效;关闭方案是在反代/出站层过滤 plausible.io,归属自有域则必须源码构建并传 --build-arg ANALYTICS_DOMAIN=<你的域>。
Q4:访问出现 502/连接拒绝?
按顺序排查:docker compose ps 看服务是否 Up;docker logs string-is-web 是否有 Listening on port 3000;curl -s -o /dev/null -w '%{http_code}' http://localhost:8128/ 在宿主本地探活。宿主本地 200 而外部不通时,检查防火墙(ufw status)或云安全组是否放行发布端口。
Q5:arm64 主机(如 Apple Silicon 虚拟机、树莓派)能否部署?
可以。官方发布 linux/amd64 与 linux/arm64 双架构应用包,docker pull 自动匹配主机架构,编排文件无需任何改动。
Q6:需要接入 LDAP/SSO 或限制访问?
string.is 自身无任何鉴权体系。公网暴露时在反代层加认证(如 Nginx auth_basic + htpasswd,或 OAuth2 Proxy);内网部署则依赖网络边界控制。服务不产生访问日志,审计需求同样要在反代层实现。
十二、参考链接
- Docker Hub 应用包:https://hub.docker.com/r/daveperrett/string-is
- 源码与官方 Dockerfile:https://github.com/recurser/string-is
- 官方 README(部署说明):https://github.com/recurser/string-is#running-with-docker
- 本系列文档:《string.is 介绍与选型指南》(选型与架构)、《string.is 使用与运维详解》(使用与运维)