Skip to content

Bare metal

The preferred way to run PutFS. No Docker overhead, direct filesystem access, systemd process management. The python dependencies for the API are minimal and can be installed on any system.

Example file layout

/opt/putfs/venv/          Python venv with putfs + granian
/srv/putfs/               Data root (or ZFS mountpoint)
/etc/putfs/keys/          API keys + presign secrets (nginx map format)
/etc/putfs/snippets/      Reusable nginx blocks (CORS, proxy tuning, XSS hardening)
/etc/putfs/cors.nginx.conf      $cors_origin map (origin allowlist)
/run/putfs/putfs.sock     Unix socket (created by granian)
/etc/systemd/system/putfs.service
/etc/nginx/sites-available/putfs

Setup

Install

python3 -m venv /opt/putfs/venv
/opt/putfs/venv/bin/pip install putfs

Create data directory

mkdir -p /srv/putfs
chown putfs:putfs /srv/putfs

For ZFS:

zfs create tank/putfs
zfs set mountpoint=/srv/putfs tank/putfs
chown putfs:putfs /srv/putfs

Keys

/etc/putfs/keys/auth.nginx.conf (start from contrib/keys/auth.nginx.conf and replace the test keys):

map "$http_x_api_key:$http_x_api_secret" $key_ok {
    default 0;
    "PUTFS_CHANGEME:CHANGEME" 1;
}
map "$http_x_api_key:$request_method:$uri" $auth_ok {
    default 0;
    "~^PUTFS_CHANGEME:[^:]+:/.+" 1;
}
# Single deny gate – /_/dl/ bypasses (HMAC validates inside that location)
map "$uri:$key_ok:$auth_ok" $deny {
    default          1;
    "~^/_/dl/"       0;
    "~:1:1$"         0;
}

See Auth for key scoping. Presigned-URL signing secrets live in keys/presign.nginx.conf – see Presigned URLs.

Systemd service

Copy contrib/putfs.service to /etc/systemd/system/:

cp contrib/putfs.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now putfs

All granian settings are configured via Environment= directives. Override by editing the service file or using a drop-in:

systemctl edit putfs
[Service]
Environment=GRANIAN_WORKERS=8
Environment=PUTFS_ROOT=/tank/putfs

Nginx

The server config includes /etc/putfs/keys/, /etc/putfs/snippets/, and /etc/putfs/cors.nginx.conf by literal path – populate the whole layout from contrib/, or nginx -t fails at config parse:

mkdir -p /etc/putfs
cp -r contrib/keys contrib/snippets contrib/cors.nginx.conf /etc/putfs/
cp contrib/putfs.nginx.conf /etc/nginx/sites-available/putfs
ln -s /etc/nginx/sites-available/putfs /etc/nginx/sites-enabled/
# Edit server_name + root path; replace the test keys under /etc/putfs/keys/
nginx -t && systemctl reload nginx

Verify

# Check service
systemctl status putfs

# Test write
curl -X PUT -H "X-Api-Key: PUTFS_CHANGEME" -H "X-Api-Secret: CHANGEME" \
  -d "hello" http://localhost/test/hello.txt

# Test read
curl -H "X-Api-Key: PUTFS_CHANGEME" -H "X-Api-Secret: CHANGEME" \
  http://localhost/test/hello.txt

# Check the file on disk
ls -la /srv/putfs/test/hello.txt