Fail-closed, self-healing SeaweedFS S3 appliance for network shares and attached disks.
3.0K
S3 Storage Node turns a network share, attached filesystem, or dedicated disk into a guarded SeaweedFS S3 endpoint that fails closed and repairs itself.
It is designed for useful but imperfect storage: SMB/CIFS shares, remote storage boxes, storage appliances, hot-swappable disks, and filesystems that may disappear, stall, or reconnect incompletely.
The dangerous failure is not merely that storage becomes unavailable. Linux can leave a dead network filesystem mounted, block I/O for a long time, or reveal the ordinary local directory beneath a vanished mount. A storage service pointed at that pathname can then hang indefinitely—or begin writing to the container host while operators believe it is still writing remotely.
S3 Storage Node gives that storage an explicit safety boundary.
000 beneath the mounted filesystem. SeaweedFS runs unprivileged and cannot create fallback volumes on the container filesystem.503 as soon as readiness is withdrawn. The SeaweedFS master, volume server, filer, and S3 gateway are treated as one guarded unit..dat files can live remotely while filer metadata and .idx files remain on fast persistent local storage..dat, with original-index backups, atomic installation, upstream validation, and rollback on rejection. Global read-only snapshots and byte-identical candidates are refused rather than treated as index damage.This does not make a remote filesystem equivalent to an enterprise local disk array. It makes failure explicit and prevents the node from claiming success while its guarded storage path is unsafe.
public S3 :8333
│
┌──────▼──────┐
│ HAProxy │
│ fail-closed │
└──────┬──────┘
│ worker address, only while /ready = 200
┌───────────────────▼───────────────────┐
│ Guardian / appliance namespace │
│ health API • selector • local lease │
│ mount helpers • recovery controller │
└───────────────────┬───────────────────┘
│ private veth
│ physical fence
┌───────────────────▼───────────────────┐
│ One worker generation namespace │
│ │
│ selected CIFS or SSHFS data mount │
│ master → volume → filer → S3 │
│ │ │ │
│ │ └─ filer metadata│
│ ├─ .dat volume data │
│ └─ .idx volume indexes │
└──────────────────────────────────────┘
The container remains alive during a storage outage. Its PID 1 guardian owns transport selection, worker generations, mounts, probes, SeaweedFS process order, fencing, recovery, and readiness.
| Storage profile | Status | Behavior |
|---|---|---|
| SMB/CIFS | Supported | Mounted, certified, monitored, fenced, detached, and repaired by the appliance |
| CIFS with SSHFS recovery | Optional | Exclusively switches generations between two routes to the same sentinel-protected dataset |
| Fixed block device | Supported | UUID and filesystem verified, then mounted; never formatted automatically |
| Existing host path | Supported | Identity and I/O monitored; mount lifecycle remains external |
SSHFS is recovery-only. The canonical data target remains CIFS, namespace fencing is mandatory, fallback is sticky, and failback is manual. rclone, WebDAV, union filesystems, per-I/O path switching, simultaneous writers, and direct SSH-only storage are unsupported.
CAP_SYS_ADMIN for appliance-managed mounts and namespace entryCAP_NET_ADMIN for worker-generation veth fencingapparmor=unconfined/dev/fuse; the standard Compose file maps it for read-only index-repair projection and SSHFS recoveryThe image includes SeaweedFS, mount.cifs, SSHFS, HAProxy, filesystem tools, and the guardian, and is published for linux/amd64 and linux/arm64.
git clone https://github.com/magiccodingman/s3-storage-node.git
cd s3-storage-node
cp config/config.toml.example config/config.toml
cp secrets/cifs-credentials.example secrets/cifs-credentials
cp secrets/s3-access-key.example secrets/s3-access-key
cp secrets/s3-secret-key.example secrets/s3-secret-key
touch secrets/ssh-identity secrets/ssh-known-hosts
chmod 600 secrets/*
The standard Compose file declares both SSH secret paths so every deployment uses one command. CIFS-only deployments may leave secrets/ssh-identity and secrets/ssh-known-hosts empty; the guardian reads them only when an SSHFS recovery route is configured.
CIFS credentials use mount.cifs format:
username=u123456
password=your-storage-password
domain=
Generate S3 credentials:
openssl rand -hex 16 > secrets/s3-access-key
openssl rand -hex 32 > secrets/s3-secret-key
chmod 600 secrets/s3-access-key secrets/s3-secret-key
[storage.data]
type = "cifs"
source = "//u123456.your-storagebox.de/backup"
mountpoint = "/run/s3-storage-node/mounts/data"
subdirectory = "seaweedfs"
credentials_file = "/run/secrets/cifs_credentials"
sentinel_id = "storagebox-production-01"
allow_initialize = true
io_failure_policy = "soft"
sentinel_id is the stable logical dataset identity. On first intentional enrollment, allow_initialize = true creates the role directories and a strict Sentinel V2 document. After the node reaches ONLINE, set allow_initialize = false on every active target and restart. A missing or mismatched sentinel then fails closed.
Existing installations with valid V1 sentinels continue to work and are not silently upgraded. See Dataset sentinel format.
docker compose up -d
docker compose logs -f
curl -i http://localhost:9090/ready
A healthy node returns HTTP 200 with state ONLINE. An unhealthy node returns 503, and HAProxy also returns 503 on the public S3 port.
The default endpoint is:
http://HOST:8333
Example with AWS CLI:
AWS_ACCESS_KEY_ID="$(cat secrets/s3-access-key)" \
AWS_SECRET_ACCESS_KEY="$(cat secrets/s3-secret-key)" \
aws --endpoint-url http://localhost:8333 s3 mb s3://example
Docker persistent volume
├── master/ SeaweedFS master state
├── metadata/filer/ embedded filer metadata
├── index/volume-indexes/ persistent .idx files
└── guardian/ local lock, generations, transport state
Remote data target
└── seaweedfs/
├── .s3-storage-node.json
└── volumes/ bulk .dat files
The default does not require PostgreSQL. Embedded filer metadata is persistent and local. PostgreSQL remains an optional SeaweedFS filer metadata backend only; it is not used for distributed writer leasing or transport failover.
Enable namespace fencing and the failover table:
[appliance]
worker_fencing_mode = "namespace"
[storage.data.failover]
enabled = true
primary_name = "cifs-primary"
primary_priority = 10
failback_policy = "manual"
failure_cooldown_seconds = 60
[[storage.data.failover.transports]]
name = "sshfs-secondary"
type = "sshfs"
priority = 20
source = "[email protected]:/home"
identity_file = "/run/secrets/ssh_identity"
known_hosts_file = "/run/secrets/ssh_known_hosts"
port = 23
mount_options = []
Populate the pinned host-key file and, for key authentication, the private key:
secrets/ssh-known-hosts
secrets/ssh-identity
The base Compose file already maps /dev/fuse and both secret paths. Start the node normally:
docker compose up -d
Password-authenticated SSHFS may reuse secrets/cifs-credentials and does not need a populated private-key file. See Exclusive CIFS-to-SSHFS failover for both authentication modes.
Inspect persisted selection and failure state:
docker compose exec s3-storage-node \
s3-storage-node transport-status \
--config /etc/s3-storage-node/config.toml
Request controlled failback after the primary is healthy:
docker compose exec s3-storage-node \
s3-storage-node transport-select \
--config /etc/s3-storage-node/config.toml \
--transport cifs-primary
Restoring CIFS does not cause automatic failback.
503 and HAProxy withdraws the backend./status to identify unexpected read-only volumes. A transient all-volume read-only snapshot is held through SeaweedFS's disk-space refresh and is never accepted as a broad repair target. For volume-specific failures, the guardian stops every SeaweedFS writer, reconstructs only those .idx files from individually exposed read-only authoritative .dat inputs, and retains rollback backups./status; rejected candidates are rolled back and blocked from automatic loops.200 and HAProxy reopens traffic.Readiness always closes immediately. The physical fence remains mandatory before replacement, while a bounded drain avoids cutting the remote data path before SeaweedFS has had a chance to finish shutdown.
503.No replacement generation exists before the old generation is detached and fenced.
The three storage roles are configured independently:
[storage.data] # SeaweedFS .dat files
[storage.metadata] # embedded filer metadata, when used
[storage.index] # SeaweedFS .idx files
The logical mappings are separate:
[metadata]
backend = "embedded"
target = "metadata"
directory = "filer"
[index]
target = "index"
directory = "volume-indexes"
Any active target failure withdraws the entire endpoint. To use PostgreSQL for filer metadata, set metadata.backend = "postgres" and follow Configuration.
| Endpoint | Purpose |
|---|---|
GET :9090/live | Guardian process is running |
GET :9090/ready | Node is certified safe to receive S3 traffic |
GET :9090/healthz | Detailed appliance, generation history, index trust, SeaweedFS volumes, storage, sentinel, and transport state |
GET :9090/metrics | Prometheus metrics |
The public endpoint is gated by guardian readiness, not merely by whether SeaweedFS has opened a socket.
The bundled appliance supervises one master, one guarded volume server, one filer, and one S3 gateway. Its safe default is default_replication = "000". It does not supervise a distributed SeaweedFS cluster, replicate between independent storage nodes, or make CIFS and SSHFS separate replicas.
Cross-node or cross-location redundancy belongs to a higher-level replication, orchestration, backup, or storage system. This node's responsibility is to make its own copy fail closed and report its health accurately.
Mounting filesystems inside a container requires meaningful privilege. The supplied Compose configuration grants CAP_SYS_ADMIN and CAP_NET_ADMIN, relaxes AppArmor for mount operations, and exposes /dev/fuse. It does not mount the Docker socket, host root filesystem, host PID namespace, or host network namespace. SeaweedFS and HAProxy run as UID/GID 10001; the guardian alone performs privileged lifecycle work.
Protect configuration, CIFS credentials, SSH private keys, trusted host keys, S3 credentials, block devices, /dev/fuse, persistent state, and the detailed health endpoint.
Apache License 2.0. SeaweedFS is a separate project also distributed under the Apache License 2.0.
Content type
Image
Digest
sha256:9e5d7a81d…
Size
154.7 MB
Last updated
about 6 hours ago
docker pull magiccodingman/s3-storage-node