Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,13 @@ POSTGRESQL_PORT=5432
POSTGRESQL_DOCKER_VERSION=16.11

# ====================================
# OBJECT STORAGE (MinIO / S3)
# OBJECT STORAGE (SeaweedFS / S3)
# ====================================
# "minio" is the Docker Compose service name.
MINIO_HOST=minio
MINIO_PORT=9000
# "seaweedfs" is the Docker Compose service name. The S3 API listens on
# port 9000, the same port MinIO used. The MINIO_USER/MINIO_PASSWORD
# variable names are kept so existing .env files keep working.
MINIO_USER=sitespeedio
MINIO_DOCKER_VERSION=RELEASE.2025-09-07T16-13-09Z
MINIO_MC_DOCKER_VERSION=RELEASE.2025-08-13T08-35-41Z
SEAWEEDFS_DOCKER_VERSION=4.32

# ====================================
# DOMAIN VALIDATION
Expand Down Expand Up @@ -122,15 +121,15 @@ LOCATION_NAME="default"
SITESPEED_IO_CONTAINER=sitespeedio/sitespeed.io:41
# SITESPEED_IO_CONTAINER=sitespeedio/sitespeed.io:41.0.0

# The S3/MinIO endpoint as seen from inside the Docker network.
# In Docker Compose this is the minio service name.
SITESPEED_IO_S3_ENDPOINT="http://minio:9000"
# The S3 endpoint as seen from inside the Docker network.
# In Docker Compose this is the seaweedfs service name.
SITESPEED_IO_S3_ENDPOINT="http://seaweedfs:9000"

# ====================================
# RESULT STORAGE & ACCESS
# ====================================
# The public base URL where test results are accessible in a browser.
# For production, point this at your MinIO/S3 domain:
# For production, point this at your SeaweedFS/S3 domain:
# RESULT_BASE_URL="https://results.yourdomain.com/sitespeedio"
RESULT_BASE_URL="http://127.0.0.1:9000/sitespeedio"

Expand Down
16 changes: 8 additions & 8 deletions .env.example.local
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# ONLINETEST CONFIGURATION — LOCAL DEVELOPMENT
# ====================================
# This file is for running server and testrunner with Node.js on your host
# while dependencies (Redis, PostgreSQL, MinIO) run in Docker.
# while dependencies (Redis, PostgreSQL, SeaweedFS) run in Docker.
#
# Copy this file to .env and change all CHANGE_ME passwords before starting.
#
# 1. Start the dependencies (Redis, PostgreSQL, MinIO):
# 1. Start the dependencies (Redis, PostgreSQL, SeaweedFS):
# docker compose -f docker-compose.dependencies.yml -f standalone/docker-compose.dependencies.standalone.yml up -d
#
# 2. Install and start the server:
Expand Down Expand Up @@ -51,13 +51,13 @@ POSTGRESQL_PORT=5432
POSTGRESQL_DOCKER_VERSION=16.11

# ====================================
# OBJECT STORAGE (MinIO / S3)
# OBJECT STORAGE (SeaweedFS / S3)
# ====================================
MINIO_HOST=127.0.0.1
MINIO_PORT=9000
# The S3 API listens on port 9000, the same port MinIO used. The
# MINIO_USER/MINIO_PASSWORD variable names are kept so existing .env
# files keep working.
MINIO_USER=sitespeedio
MINIO_DOCKER_VERSION=RELEASE.2025-09-07T16-13-09Z
MINIO_MC_DOCKER_VERSION=RELEASE.2025-08-13T08-35-41Z
SEAWEEDFS_DOCKER_VERSION=4.32

# ====================================
# DOMAIN VALIDATION
Expand Down Expand Up @@ -85,7 +85,7 @@ LOCATION_NAME="default"

# The testrunner defaults (testrunner/config/testrunner.yaml) already point
# S3 and result URLs to 127.0.0.1:9000, so these only need to be set if you
# changed the MinIO port or use a remote S3 endpoint.
# changed the SeaweedFS port or use a remote S3 endpoint.
# SITESPEED_IO_S3_ENDPOINT="http://127.0.0.1:9000"

# ====================================
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Follow these steps to quickly set up and run the online version of sitespeed.io
cp .env.example .env
```

4. **Start the Docker containers (Redis/PostgreSQL/MinIO/sitespeed.io server and testrunner) on the same server:**
4. **Start the Docker containers (Redis/PostgreSQL/SeaweedFS/sitespeed.io server and testrunner) on the same server:**

```bash
docker compose -f docker-compose.dependencies.yml -f docker-compose.server.yml -f docker-compose.testrunner.yml up
Expand Down Expand Up @@ -131,7 +131,7 @@ Running on your own machine the result is served from localhost. If you deploy o

```RESULT_BASE_URL="http://127.0.0.1:9000/sitespeedio"```

By default the result is served by [MinIO](https://min.io) on port 9000. If you serve the result on the URL `https://sitespeed.domain.com` you change your result base to: ```RESULT_BASE_URL="https://sitespeed.domain.com/sitespeedio"```
By default the result is served by [SeaweedFS](https://github.com/seaweedfs/seaweedfs) on port 9000. If you serve the result on the URL `https://sitespeed.domain.com` you change your result base to: ```RESULT_BASE_URL="https://sitespeed.domain.com/sitespeedio"```

#### Update server and testrunner
You can configure which version of the server and the testrunner you want to use. You can either use latest stable version or specify a specific tag. In the *.env* file you configure which Docker tag to use.
Expand Down Expand Up @@ -224,7 +224,7 @@ The setup is split into three compose files ([docker-compose.dependencies.yml](h

* **A Database**: - [PostgreSQL](https://www.postgresql.org) - the open source database.

* **A Test Result Storage**: - Somewhere to store test results. The default setup uses [MinIO](https://min.io), an open source implementation of S3 but you can use all the result storages that work with sitespeed.io: S3, Google Cloud Storage or your own storage where you can scp the result.
* **A Test Result Storage**: - Somewhere to store test results. The default setup uses [SeaweedFS](https://github.com/seaweedfs/seaweedfs), an open source S3-compatible object store, but you can use all the result storages that work with sitespeed.io: S3, Google Cloud Storage or your own storage where you can scp the result.

Additionally, there's a server and one or multiple test runners that run the sitespeed.io tests.

Expand Down
2 changes: 1 addition & 1 deletion deploy/Caddyfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{$DOMAIN} {
handle /sitespeedio/* {
reverse_proxy minio:9000
reverse_proxy seaweedfs:9000
}
handle {
reverse_proxy sitespeed.io-server:3000
Expand Down
46 changes: 39 additions & 7 deletions deploy/PRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Both models use Docker Compose and the same `.env` configuration.

## Single-server deployment (with Caddy)

All services (Redis, PostgreSQL, MinIO, the sitespeed.io server, testrunner, and Caddy) run on the same machine. Only ports 80 and 443 are exposed — Caddy handles TLS termination and proxies traffic to the server and MinIO.
All services (Redis, PostgreSQL, SeaweedFS, the sitespeed.io server, testrunner, and Caddy) run on the same machine. Only ports 80 and 443 are exposed — Caddy handles TLS termination and proxies traffic to the server and SeaweedFS.

### Steps

Expand Down Expand Up @@ -56,7 +56,7 @@ Make sure your DNS points `yourdomain.com` to the server's IP. Caddy will automa
| Port | Service |
|------|---------|
| 80 | Caddy (HTTP → HTTPS redirect) |
| 443 | Caddy (HTTPS → server + MinIO) |
| 443 | Caddy (HTTPS → server + SeaweedFS) |

All other services communicate internally on the `skynet` Docker network.

Expand Down Expand Up @@ -84,19 +84,19 @@ This exposes:
| 3000 | Server |
| 5432 | PostgreSQL |
| 6379 | Redis |
| 9000 | MinIO |
| 9000 | SeaweedFS |

You will want to put a reverse proxy (Caddy, nginx, etc.) in front of port 3000 and 9000 for HTTPS — see [Reverse proxy examples](#reverse-proxy-examples) below.

3. **Lock down ports with iptables** — Redis, PostgreSQL, and MinIO should only be accessible from your testrunner machine(s), not the public internet. Replace `<testrunner-ip>` with the IP of each testrunner:
3. **Lock down ports with iptables** — Redis, PostgreSQL, and SeaweedFS should only be accessible from your testrunner machine(s), not the public internet. Replace `<testrunner-ip>` with the IP of each testrunner:

```bash
# Allow testrunner(s) to reach Redis
sudo iptables -A INPUT -p tcp --dport 6379 -s <testrunner-ip> -j ACCEPT
# Allow testrunner(s) to reach MinIO
# Allow testrunner(s) to reach SeaweedFS
sudo iptables -A INPUT -p tcp --dport 9000 -s <testrunner-ip> -j ACCEPT

# Block everyone else from Redis, PostgreSQL, and MinIO
# Block everyone else from Redis, PostgreSQL, and SeaweedFS
sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
sudo iptables -A INPUT -p tcp --dport 5432 -j DROP
sudo iptables -A INPUT -p tcp --dport 9000 -j DROP
Expand Down Expand Up @@ -149,7 +149,7 @@ You will want to put a reverse proxy (Caddy, nginx, etc.) in front of port 3000

Each testrunner machine follows the same recipe as the first — clone the repo, write the same `.env` (point at the same `REDIS_HOST`, share the same `REDIS_PASSWORD`/`MINIO_*` secrets), then `docker compose -f deploy/docker-compose.production-testrunner.yml up -d`. The only thing that **must** differ between machines is `LOCATION_NAME` (or, on Android farms, the `deviceId` in `testrunner/config/testrunner.yaml`).

Don't forget the iptables/firewall step: the server machine needs to accept Redis (6379), PostgreSQL (5432) and MinIO (9000) traffic from each new testrunner IP. Repeat the `ACCEPT` rules from the lock-down step for every additional runner.
Don't forget the iptables/firewall step: the server machine needs to accept Redis (6379), PostgreSQL (5432) and SeaweedFS (9000) traffic from each new testrunner IP. Repeat the `ACCEPT` rules from the lock-down step for every additional runner.

A few minutes after a runner boots it should appear under **Connected testrunners** on `/admin` with a green "fresh" badge. If it doesn't show up:

Expand Down Expand Up @@ -329,3 +329,35 @@ To move to a new major release of the server/testrunner, pass `--version`:
```

This rewrites `SITESPEED_IO_SERVER_VERSION` and `SITESPEED_IO_TESTRUNNER_VERSION` in `.env` to `3.4.0` before pulling.

### Upgrading from MinIO to SeaweedFS

Result storage moved from MinIO (whose community edition was archived
in 2026) to SeaweedFS. The S3 port (9000) and the
`MINIO_USER`/`MINIO_PASSWORD` variables are unchanged, so the upgrade
is two `.env` edits on the machine that runs storage:

1. Pull the new compose files (`git pull` or checkout the new tag).
2. In `.env`: add `SEAWEEDFS_DOCKER_VERSION=4.32` and change
`SITESPEED_IO_S3_ENDPOINT` from `http://minio:9000` to
`http://seaweedfs:9000`. The `MINIO_DOCKER_VERSION` and
`MINIO_MC_DOCKER_VERSION` lines are no longer used and can be
removed.
3. Run `./deploy/update.sh` with your usual mode. `--remove-orphans`
removes the old `minio` and `mc` containers; `seaweedfs-init`
creates the bucket and the 30 day expiry automatically.
4. Single-server only: `docker compose -f deploy/docker-compose.production.yml restart caddy` —
the Caddyfile now proxies results to `seaweedfs` and Caddy doesn't
re-read a changed config file on its own.

Multi-server testrunner boxes need no changes at all: their
`SITESPEED_IO_S3_ENDPOINT` points at the server machine's IP and port
9000, both of which still work, and the iptables rules stay the same.

Old results are **not** migrated: links to runs uploaded before the
switch stop working (they would have expired within 30 days anyway).
The test history in PostgreSQL is untouched. Once you're happy with
the new setup, reclaim the disk used by the old volume with
`docker volume rm <project>_minio-data`. If you run your own reverse
proxy in front of result storage (instead of the bundled Caddyfile),
repoint it from the MinIO container to `seaweedfs:9000`.
46 changes: 25 additions & 21 deletions deploy/docker-compose.production-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,44 @@ services:
interval: 5s
timeout: 5s
retries: 30
minio:
image: minio/minio:${MINIO_DOCKER_VERSION}
seaweedfs:
image: chrislusf/seaweedfs:${SEAWEEDFS_DOCKER_VERSION}
volumes:
- minio-data:/data
- seaweedfs-data:/data
ports:
- "9000:9000"
environment:
- MINIO_ROOT_USER=${MINIO_USER}
- MINIO_ROOT_PASSWORD=${MINIO_PASSWORD}
- MINIO_REGION=motown
command: server /data --console-address ":9001"
# SeaweedFS reads S3 credentials from a config file, not env vars, so the
# entrypoint writes it from .env values first. The anonymous identity gives
# public read access to the result bucket (what "mc anonymous set download"
# did on MinIO). The S3 API listens on 9000, the same port MinIO used.
entrypoint: >
/bin/sh -c "
printf '%s' '{\"identities\":[{\"name\":\"admin\",\"credentials\":[{\"accessKey\":\"${MINIO_USER}\",\"secretKey\":\"${MINIO_PASSWORD}\"}],\"actions\":[\"Admin\",\"Read\",\"List\",\"Tagging\",\"Write\"]},{\"name\":\"anonymous\",\"actions\":[\"Read:sitespeedio\"]}]}' > /etc/seaweedfs-s3.json &&
exec weed server -dir=/data -filer -s3 -s3.port=9000 -s3.config=/etc/seaweedfs-s3.json -volume.max=0 -master.volumeSizeLimitMB=1024"
networks:
- skynet
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9000/minio/health/ready > /dev/null"]
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:9000/healthz"]
interval: 5s
timeout: 3s
retries: 30
mc:
image: minio/mc:${MINIO_MC_DOCKER_VERSION}
seaweedfs-init:
image: chrislusf/seaweedfs:${SEAWEEDFS_DOCKER_VERSION}
depends_on:
minio:
seaweedfs:
condition: service_healthy
networks:
- skynet
restart: on-failure
# The 30 day expiry uses SeaweedFS native TTL (fs.configure) instead of an
# S3 lifecycle rule — lifecycle rules have a history of not being enforced.
# The TTL counts from write time and only applies to files written after it
# is set.
entrypoint: >
/bin/sh -c "
/usr/bin/mc alias set sitespeedio http://minio:9000 ${MINIO_USER} ${MINIO_PASSWORD} &&
(/usr/bin/mc mb --ignore-existing --region=motown sitespeedio/sitespeedio || echo 'Bucket already exists') &&
/usr/bin/mc ilm rule add --expire-days 30 sitespeedio/sitespeedio &&
/usr/bin/mc anonymous set download sitespeedio/sitespeedio &&
/usr/bin/mc admin config set sitespeedio compression extensions=\".html,.css,.js\" &&
echo 'MinIO and lifecycle policy setup complete.'"
(echo 's3.bucket.create -name sitespeedio' | weed shell -master=seaweedfs:9333 || echo 'Bucket already exists') &&
echo 'fs.configure -locationPrefix=/buckets/sitespeedio/ -ttl=30d -apply' | weed shell -master=seaweedfs:9333 &&
echo 'SeaweedFS bucket and TTL setup complete.'"
sitespeed.io-server:
image: sitespeedio/server:${SITESPEED_IO_SERVER_VERSION}
env_file:
Expand All @@ -86,9 +90,9 @@ services:
condition: service_healthy
postgresql:
condition: service_healthy
minio:
seaweedfs:
condition: service_healthy
mc:
seaweedfs-init:
condition: service_completed_successfully
healthcheck:
test: [
Expand All @@ -101,7 +105,7 @@ services:
start_period: 30s
volumes:
postgres-data:
minio-data:
seaweedfs-data:
redis_data:
driver: local
networks:
Expand Down
48 changes: 26 additions & 22 deletions deploy/docker-compose.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,42 @@ services:
interval: 5s
timeout: 5s
retries: 30
minio:
image: minio/minio:${MINIO_DOCKER_VERSION}
seaweedfs:
image: chrislusf/seaweedfs:${SEAWEEDFS_DOCKER_VERSION}
volumes:
- minio-data:/data
environment:
- MINIO_ROOT_USER=${MINIO_USER}
- MINIO_ROOT_PASSWORD=${MINIO_PASSWORD}
- MINIO_REGION=motown
command: server /data --console-address ":9001"
- seaweedfs-data:/data
# SeaweedFS reads S3 credentials from a config file, not env vars, so the
# entrypoint writes it from .env values first. The anonymous identity gives
# public read access to the result bucket (what "mc anonymous set download"
# did on MinIO). The S3 API listens on 9000, the same port MinIO used.
entrypoint: >
/bin/sh -c "
printf '%s' '{\"identities\":[{\"name\":\"admin\",\"credentials\":[{\"accessKey\":\"${MINIO_USER}\",\"secretKey\":\"${MINIO_PASSWORD}\"}],\"actions\":[\"Admin\",\"Read\",\"List\",\"Tagging\",\"Write\"]},{\"name\":\"anonymous\",\"actions\":[\"Read:sitespeedio\"]}]}' > /etc/seaweedfs-s3.json &&
exec weed server -dir=/data -filer -s3 -s3.port=9000 -s3.config=/etc/seaweedfs-s3.json -volume.max=0 -master.volumeSizeLimitMB=1024"
networks:
- skynet
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9000/minio/health/ready > /dev/null"]
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:9000/healthz"]
interval: 5s
timeout: 3s
retries: 30
mc:
image: minio/mc:${MINIO_MC_DOCKER_VERSION}
seaweedfs-init:
image: chrislusf/seaweedfs:${SEAWEEDFS_DOCKER_VERSION}
depends_on:
minio:
seaweedfs:
condition: service_healthy
networks:
- skynet
restart: on-failure
# The 30 day expiry uses SeaweedFS native TTL (fs.configure) instead of an
# S3 lifecycle rule — lifecycle rules have a history of not being enforced.
# The TTL counts from write time and only applies to files written after it
# is set.
entrypoint: >
/bin/sh -c "
/usr/bin/mc alias set sitespeedio http://minio:9000 ${MINIO_USER} ${MINIO_PASSWORD} &&
(/usr/bin/mc mb --ignore-existing --region=motown sitespeedio/sitespeedio || echo 'Bucket already exists') &&
/usr/bin/mc ilm rule add --expire-days 30 sitespeedio/sitespeedio &&
/usr/bin/mc anonymous set download sitespeedio/sitespeedio &&
/usr/bin/mc admin config set sitespeedio compression extensions=\".html,.css,.js\" &&
echo 'MinIO and lifecycle policy setup complete.'"
(echo 's3.bucket.create -name sitespeedio' | weed shell -master=seaweedfs:9333 || echo 'Bucket already exists') &&
echo 'fs.configure -locationPrefix=/buckets/sitespeedio/ -ttl=30d -apply' | weed shell -master=seaweedfs:9333 &&
echo 'SeaweedFS bucket and TTL setup complete.'"
sitespeed.io-server:
image: sitespeedio/server:${SITESPEED_IO_SERVER_VERSION}
env_file:
Expand All @@ -96,9 +100,9 @@ services:
condition: service_healthy
postgresql:
condition: service_healthy
minio:
seaweedfs:
condition: service_healthy
mc:
seaweedfs-init:
condition: service_completed_successfully
healthcheck:
test: [
Expand Down Expand Up @@ -134,15 +138,15 @@ services:
depends_on:
redis:
condition: service_healthy
minio:
seaweedfs:
condition: service_healthy
sitespeed.io-server:
condition: service_healthy
networks:
- skynet
volumes:
postgres-data:
minio-data:
seaweedfs-data:
redis_data:
driver: local
caddy_data:
Expand Down
Loading
Loading