Serve static websites from tar.gz archives in S3-compatible object storage with hot reloads.
s3site now supports two operating modes:
- Discovery mode — legacy bucket scan for
*.tar.gzobjects under a prefix. - Hosted mode — explicitly declared sites backed by stable object keys, with local-only refresh control.
Hosted mode is the recommended path for running s3site behind a declarative ingress layer such as Caddy/Nix.
go install github.com/rhnvrm/s3site/cmd/s3site@latestnix run github:rhnvrm/s3siteOr consume the package from another flake:
inputs.s3site.url = "github:rhnvrm/s3site";
# ...
inputs.s3site.packages.${system}.defaults3site \
-bucket static-assets \
-prefix sites/ \
-region us-east-1 \
-listen :8080 \
-poll 30sObjects under the prefix are mapped by filename:
s3://static-assets/sites/
foo.example.com.tar.gz -> serves foo.example.com
bar.example.com.tar.gz -> serves bar.example.comDeclare the sites that are allowed to go live:
{
"sites": [
{ "hostname": "rohanverma.net" },
{ "hostname": "oddship.net" }
]
}Start s3site with a local control socket:
s3site \
-bucket static-assets \
-prefix sites/ \
-sites-config /etc/s3site/sites.json \
-control-socket /run/s3site/control.sock \
-listen 127.0.0.1:9001 \
-poll 10mWhen key is omitted, s3site derives it as:
<prefix><hostname>.tar.gzSo the config above watches:
sites/rohanverma.net.tar.gz
sites/oddship.net.tar.gzRefresh one or more declared sites after CI uploads a new tarball:
s3site refresh -socket /run/s3site/control.sock rohanverma.netThat keeps deploys fast without requiring frequent bucket scans.
For hosted deployments, the intended split is:
- Caddy / Nix / ingress layer: TLS, public domains, reverse proxying, service wiring
- s3site: content activation, object fetch, hot reload
- CI: build tarball, upload to a stable key, call local refresh
| Flag | Env | Default | Description |
|---|---|---|---|
-bucket |
AWS_S3_BUCKET |
(required) | S3 bucket name |
-region |
AWS_S3_REGION |
us-east-1 |
AWS region |
-access-key |
AWS_S3_ACCESS_KEY |
AWS access key | |
-secret-key |
AWS_S3_SECRET_KEY |
AWS secret key | |
-endpoint |
AWS_S3_ENDPOINT |
Custom S3 endpoint | |
-prefix |
S3SITE_PREFIX |
S3 key prefix | |
-listen |
S3SITE_LISTEN |
:8080 |
HTTP listen address |
-poll |
S3SITE_POLL |
30s |
Poll interval |
-sites-config |
S3SITE_SITES_CONFIG |
Hosted-sites JSON config | |
-control-socket |
S3SITE_CONTROL_SOCKET |
Unix socket for local refresh control | |
-admin-host |
S3SITE_ADMIN_HOST |
Hostname for the insecure dev admin UI | |
-allow-insecure-admin |
S3SITE_ALLOW_INSECURE_ADMIN |
false |
Explicitly enable the insecure public admin UI/API |
-storage |
S3SITE_STORAGE |
memory |
Storage mode: memory or disk |
-data-dir |
S3SITE_DATA_DIR |
$TMPDIR/s3site-data |
Directory for disk storage |
If you previously used -admin-host, startup now fails unless you also pass -allow-insecure-admin.
That is intentional. The browser admin has no authentication and is now explicit opt-in only.
Typical CI flow in hosted mode:
deploy:
script:
- hugo
- tar czf site.tar.gz -C public .
- aws s3 cp site.tar.gz s3://static-assets/sites/rohanverma.net.tar.gz
- ssh oddship-web 's3site refresh -socket /run/s3site/control.sock rohanverma.net'Rollback is simply rerunning CI from an older commit and uploading to the same key.
The browser admin UI still exists for development, but it is intentionally gated behind -allow-insecure-admin because it has no authentication.
s3site \
-bucket static-assets \
-prefix sites/ \
-admin-host admin.example.com \
-allow-insecure-adminDo not expose that mode on the public internet unless you add your own auth and network isolation.
- List
*.tar.gzobjects under the configured prefix - Map archive filename to hostname
- Compare ETags against current state
- Download and extract only changed archives
- Remove sites whose archives were deleted
- Load a declared site registry from JSON
- Watch declared object keys only
- Compare ETags for declared keys only
- Download and activate updated archives
- Keep the previous on-disk version around for one generation in disk mode to reduce swap races
In memory mode (default), all files are served from RAM.
In disk mode (-storage disk), extracted sites are served from versioned directories on disk. s3site retains the previous on-disk version for one generation after an activation so a swap does not immediately delete the just-replaced tree.
| Mode | Pros | Cons |
|---|---|---|
memory |
Fast serving, no disk I/O | Uses RAM for all site content |
disk |
Minimal RAM usage | Disk I/O on every request, needs writable directory |
- only declared sites may go live
- hostname validation is strict
- tar path traversal is rejected
- archive file count and total extracted bytes are bounded
- refresh control is local-only via unix socket
- server timeouts and
SIGTERMhandling are enabled
Requires Docker for MinIO:
# Start MinIO
export AWS_S3_ENDPOINT=http://127.0.0.1:9000
export AWS_S3_ACCESS_KEY=minioadmin
export AWS_S3_SECRET_KEY=minioadmin
export AWS_S3_BUCKET=testbucket
go test -short ./...Run full integration tests with a live MinIO instance on 127.0.0.1:9000.
Static site serving library for both discovery mode and hosted mode.
In-memory file watcher for arbitrary S3 objects. See pkg/s3watcher/README.md.
BSD-2-Clause