Save products you love by clicking the heart icon.
Evidenzbasierte Testing-Praxis aus Produktions-Codebasen — property-basierte Invarianten, gezielte Fehlerinjektion, Contract-Tests, benchmark-verifizierte Performance und Qualitäts-Gates, die durchgesetzt werden, nicht nur versprochen.
Comprehensive DR testing and edge-specific patterns for k3s — failover validation, scheduled snapshots, multi-cluster recovery, and RPO/RTO planning.
SeaweedFS is a distributed object store written in Go that fits in a single static binary called weed. It is the anti-Ceph in the best possible way: where Ceph is a platform with monitors, managers, and a dashboard, SeaweedFS is a set of small daemons you can run on a Raspberry Pi — and scale to tens of petabytes.
Its superpower is the data path. A client asks the master where to write, gets a volume server address, and then talks to that volume server directly — the master is not in the data path, so throughput scales with the number of volume servers, not with a central coordinator.
The four moving parts:
-max=32 in the reference deployment).weed s3 implements the S3 API on top of the filer, which is what lets K8up, rclone, and every S3 tool in existence use the cluster.Where CephFS gives you a POSIX filesystem on RADOS, SeaweedFS gives you an S3 object store with an optional FUSE/WebDAV filesystem face. If your workload speaks S3 — backups, logs, artifacts, media — SeaweedFS is often a fraction of the operational weight.
Download the binary, run four commands, and you have a cluster:
# Master: manages volume servers (port 9333)
weed master -mdir=/data/master &
# Volume server: stores data (port 8080)
weed volume -dir=/data/volumes -max=32 -mserver=localhost:9333 &
# Filer: the namespace (port 8888)
weed filer -master=localhost:9333 &
# S3 gateway on the filer (port 8333)
weed s3 -filer=localhost:8888
Upload a file with plain HTTP — no SDK required:
# Assign a file ID from the master, then PUT to the volume server
weed upload -server=localhost:9333 ./photo.jpg
# → photo.jpg saved to http://localhost:8080/3,016bc0e4ac
# Or go through the filer for a real path
curl -F file=@photo.jpg http://localhost:8888/photos/
Two minutes in, you have already used the core design: the master handed you a volume server, and the data went there directly. That direct path is the whole architecture.
A volume server does not manage files; it manages logical volumes — fixed-size blobs (default 30 GB, set with -volumeSizeLimitMB=30000 in the reference cluster) written strictly append-only. Each file entry is 4 KB-aligned and addressed by a compact file ID: volume id, file key, and a cookie (the cookie is the write token that makes URLs hard to guess). Deletion marks space as free; a periodic compaction (weed shell → volume.gc / volume.compact) rewrites the volume, reclaiming the holes.
Why append-only? Because it turns random small writes into sequential big writes — the cheapest possible disk pattern — and makes replication trivially consistent: you replicate an append, not a random update.
Replication is per-volume, set at creation with a three-digit code:
| Code | Meaning |
|---|---|
000 | No replication (single copy) |
001 | One extra copy on another server |
010 | One extra copy on another rack |
100 | One extra copy on another data center |
110 / 200 | Combinations up to multi-DC quorum |
The reference cluster runs -defaultReplication=000 deliberately: the data it stores (Kubernetes backups) is already restic-encrypted, and the second copy lives on the off-cluster mirror (Part 5). Replication in SeaweedFS is a tool for availability, not the only copy you will ever have — design the chain, don't let the tool decide it for you.
For capacity efficiency, volumes can be erasure-coded (weed volume -dataCenter ... -ec): configurable data + parity shards (e.g. 4+2), stored across volume servers. EC trades CPU for raw capacity — roughly 1.5× efficiency at 2-failure tolerance, the same arithmetic Ceph users know from replicated vs. EC pools. The difference: with SeaweedFS you can EC specific old volumes while keeping hot volumes replicated.
Replication happens between volume servers, not between disks on one server. A single node with 000 and one disk is a single point of failure by definition — if that is your whole cluster, back it up elsewhere (that is exactly the mirror pattern in Part 5).
weed s3 turns the filer into an S3-compatible endpoint: buckets, multipart uploads, lifecycle rules, and the full S3 client ecosystem (aws cli, s3cmd, rclone, restic) work unchanged.
aws --endpoint-url http://s3.example.internal:8333 s3 mb s3://backups
aws --endpoint-url http://s3.example.internal:8333 s3 cp k8up-backups/ s3://backups/ --recursive
Beyond plain S3, three features matter in production:
weed mount — a FUSE mount gives you POSIX access to the same namespace, which makes the store usable as a shared filesystem for tools that do not speak S3 (or as a crash-consistent backup target).The reference deployment runs SeaweedFS 3.78 inside a K3s cluster as four small workloads in a dedicated backup namespace:
| Component | Workload | Ports | Data |
|---|---|---|---|
| Master | Deployment (1) | 9333 / 19333 (gRPC) | 10 GiB PVC (Ceph RBD) |
| Volume | StatefulSet (1) | 8080 | PVC (Ceph RBD) |
| Filer | Deployment (1) | 8888 / 18888 (gRPC) | PVC (Ceph RBD) |
| S3 | part of filer | 8333 (ClusterIP) | — |
The master is configured with -defaultReplication=000 and -volumeSizeLimitMB=30000; the volume server with -max=32 volumes. Every workload gets startup, liveness, and readiness probes against its HTTP endpoints (/cluster/status for the master, /status for volumes) — a volume server that cannot answer /status is restarted before it can serve bad data.
A NetworkPolicy restricts the namespace: only the backup operator, the mirror job, and the ingress can reach the S3 port; the master and volume ports are cluster-internal only.
The store exists to hold K8up backups — the Kubernetes backup operator that wraps restic. One Schedule CRD drives the whole lifecycle:
apiVersion: k8up.io/v1
kind: Schedule
metadata:
name: backup-schedule
spec:
backend:
repoPasswordSecretRef: { name: backup-repo, key: password }
s3:
endpoint: http://seaweedfs-s3.backup.svc.cluster.local:8333
bucket: k8up-backups
accessKeyIDSecretRef: { name: seaweedfs-s3-creds, key: accessKey }
secretAccessKeySecretRef: { name: seaweedfs-s3-creds, key: secretKey }
backup:
schedule: "42 0 * * *" # nightly, after quiet hours
check:
schedule: "0 4 * * 1" # weekly restic check
prune:
schedule: "30 4 * * 1" # weekly retention prune
PVCs opt in with the annotation k8up.io/backup: "true". Restic talks S3 to the filer gateway; the encrypted snapshots land as filer data striped across the volume server. The layout is deliberately layered: application data → PVC (Ceph RBD) → K8up/restic → SeaweedFS → off-cluster mirror — four independent copies in three places, none of them on the same failure domain as the app.
The pattern that makes backup boring: the operator (K8up) speaks S3 to a tiny object store (SeaweedFS) that lives in the cluster it backs up, and a nightly rclone job mirrors that store to a machine in another room. Local restores are instant; the mirror covers the "the whole cluster burned down" case.
A CronJob runs nightly at 01:00 — after the 00:42 backup — and syncs the bucket to a dedicated off-cluster mirror host:
rclone sync source:k8up-backups dest:k8up-backups \
--stats=1m --transfers=4 --checkers=8 \
--contimeout=60s --timeout=300s --retries=3
rclone sync makes the destination match the source: deletions propagate, so the mirror never accumulates stale snapshots beyond the retention the backup schedule already enforces. Four parallel transfers and eight checkers keep a multi-terabyte store moving without saturating the link.
The mirror endpoint is exposed over HTTPS with a self-signed certificate — and the first integration attempt failed with TLS errors. The investigation found four stacked problems, all of them instructive:
CN=localhost and no IP SAN for the mirror host. The mirror client (rclone → AWS SDK) rejects certificates whose hostname or IP is not in the SAN list. Classic.-master flag into the weed s3 command, which is why the S3 gateway crashed on startup in the first place.The fix was a hand-rolled Docker container that sidesteps the platform entirely:
docker run -d --name seaweedfs-s3 \
--network host \
--entrypoint /usr/bin/weed \ # skip the broken wrapper script
-v /etc/seaweedfs/ssl:/etc/seaweedfs/ssl:ro \
seaweedfs/seaweedfs:3.78 \
s3 -filer=<filer-address> -cert=/etc/seaweedfs/ssl/tls.crt -key=/etc/seaweedfs/ssl/tls.key
With the certificate regenerated to include the host's IP SAN, the container bound via host networking (no Docker port publishing involved), and the entrypoint bypassed:
https://127.0.0.1:30304/ — TLS works locallyhttps://<mirror-host>:30304/ — TLS works remotelyAccessDenied — proving the TLS handshake completed and only the (expected) S3 auth rejected the requestThe lessons generalize far beyond SeaweedFS:
curl — rejects a certificate whose SAN list does not contain the exact name or IP it connected to. CN=localhost is not a fix; it is a lie.openssl s_client but the mirror job's own client completing a handshake — AccessDenied beats TLS error any day.When a backup integration fails with TLS errors, do not reach for --no-check-certificate and move on — that is how a mirror silently stops being trustworthy. Fix the certificate (SAN), fix the binding (host network / IPv4), and prove it with the real client.
| Check | Command / signal | Why |
|---|---|---|
| Master heartbeats | weed shell → volume.list | A volume server that stopped heartbeating is invisible to new writes |
| Volume fill level | weed shell → volume.status | Full volumes stop accepting writes; watch -volumeSizeLimitMB headroom |
| Compaction debt | weed shell → volume.gc dry run | Holes accumulate with churn; schedule compaction like a backup |
| Filer health | HTTP probe on :8888 | The filer is the namespace — its loss means no new writes anywhere |
| Mirror freshness | rclone lsd on the destination | The mirror is the disaster-recovery copy; verify it, don't assume it |
| S3 latency | curl -w %{time_total} on a small GET | The data path is direct — if it is slow, it is the volume server or the network, not the master |
The whole system stays tiny: one master, one volume server, one filer — all of them smaller than a single Ceph MON. That is the point. SeaweedFS is the storage you run when the workload speaks S3 and you want the operational surface of one binary, not a platform.
| SeaweedFS | Ceph RGW | MinIO | |
|---|---|---|---|
| Footprint | One Go binary, MBs of RAM per daemon | Full RADOS cluster (MON/OSD/MGR) | One binary, but single-node by default |
| Data path | Direct client↔volume, master only assigns | Client↔OSD via CRUSH, no central node | Gateway-fronted |
| Namespace | Filer: dirs, FUSE, WebDAV, S3 | Buckets only (RGW) | Buckets only |
| Replication | Per-volume codes, EC per volume | CRUSH rules, EC pools | Per-bucket erasure sets |
| Best for | Backups, logs, media, S3 workloads at low weight | Unified block+file+object on one cluster | S3-compatible workloads wanting a familiar operator |
If you already run Ceph, RGW is free — see the CephFS article for the filesystem side. If you do not already run Ceph and your workload speaks S3, SeaweedFS delivers the same class of capability with a fraction of the machinery.
000 only with an external copy.check.SeaweedFS rewards the engineer who respects its two ideas: keep the master out of the data path, and keep the copies deliberate. Do that, and a store that fits in one binary quietly becomes the most reliable component in your stack.