Overview
SFTPGo is an SFTP server backed by object storage. Clients speak standard SFTP; files land in your bucket, with per-user folder isolation and declarative user management. This template offers a choice between an always-on server and a scale-to-zero mode that suspends the server when idle behind a tiny always-on proxy. The SFTPGo administrator login — which guards the REST API and the web admin — comes from a secret you create before installing, so it never passes through Helm values.Architecture
- SFTPGo — A single-replica stateful workload serving SFTP on port
2022. An embedded bolt database and the SSH host keys persist on a volume set so host keys stay stable across restarts and wakes. - Scale-to-zero proxy (scale_to_zero mode only) — An always-on activator workload that accepts client connections while SFTPGo sleeps, wakes it via the platform API, splices traffic through, and suspends it again after an idle window.
- Admin login from a secret — The administrator username and password come from a dictionary secret you create. SFTPGo consumes them only when the embedded database has no admin yet, i.e. on first boot.
What Gets Created
- Stateful SFTPGo Workload — Serves SFTP on port
2022; holds the embedded database and SSH host keys. - Standard Scale-to-Zero Proxy Workload (scale_to_zero mode only) — Always-on TCP activator that fronts SFTPGo and manages suspend/wake.
- Volume Set — 10 GiB persistent storage for the embedded database and SSH host keys.
- Secret — An opaque secret holding the declared-users file. The admin credentials are not created here — they live in the secret you create.
- Identity & Policy — An identity bound to the workloads with
revealaccess to the secrets it mounts, including your admin credentials secret; keyless cloud storage access for AWS and GCP backends; and a least-privilege policy letting the proxy’s identity suspend and wake exactly the SFTPGo workload — nothing else.
This template does not create a GVC. You must deploy it into an existing GVC.
Prerequisites
Admin Credentials
This secret must exist before you install. It is the SFTPGo administrator login for the REST API and web admin. Secrets are org-level, so no GVC flag is involved.1
Create the admin credentials secret
A dictionary secret holding exactly the keys Set
username and password:admin.secretName to the name you used.2
Read it back later
-o yaml is required; without it the command prints the secret’s metadata table rather than its contents:The admin secret seeds the first boot only. SFTPGo applies it when the embedded database has no administrator yet, so on an existing install editing the secret does not change the admin password — rotate that through the REST API or web admin instead. This is also why an upgrade from
1.0.0 leaves your current login working.Object Storage
SFTPGo requires an existing bucket in one of the supported backends and the access setup for it. Complete the steps for your chosen backend before installing.- AWS S3 (keyless)
- Google Cloud Storage (keyless)
- S3-compatible (MinIO, R2, Wasabi)
AWS S3 uses a Control Plane cloud identity — no credentials are stored; the workload’s identity vends temporary credentials at runtime.
1
Create a bucket
Create an S3 bucket. Set
storage.aws.bucket and storage.aws.region to match.2
Set up a Cloud Account
If you do not have one, create a Cloud Account for your AWS account. Set
storage.aws.cloudAccountName to its name.3
Create a bucket-scoped IAM policy
Create an AWS IAM policy with the JSON below (replace
YOUR_BUCKET), then set storage.aws.policyName to the policy’s name (bare name, not ARN):UI
Browse, install, and manage templates visually
CLI
Manage templates from your terminal
Terraform
Declare templates in your Terraform configurations
Pulumi
Declare templates in your Pulumi programs
Upgrading From 1.0.0
Version1.1.0 moved the SFTPGo administrator login out of Helm values. 1.0.0 shipped a working username and password as values, used exactly as written, guarding the REST API and web admin.
To upgrade an existing install:
1
Create the admin credentials secret
Follow Prerequisites, putting the username and password you already use into it. Your existing login keeps working either way — the secret only seeds an install whose database has no admin yet.
2
Drop the removed keys from your values
Remove
admin.username and admin.password, and set admin.secretName instead. Leave users[] and the storage block exactly as they are.3
Upgrade
In
scale_to_zero mode an upgrade while suspended wakes the server; it re-suspends after the next connection comes and goes.What Stays a Value
Two things are deliberately not prerequisite secrets, and this is a limitation rather than a preference:users[].password— the passwords of the declared SFTP usersstorage.minio.accessKey/storage.minio.accessSecret— static keys for an S3-compatible backend
cpln://secret/... reference placed there would be stored verbatim as the credential rather than resolved. The usual workaround — a startup script that substitutes the value — is unavailable too: the shipped image is distroless-slim and has no shell at all (/bin/sh, sh, /bin/bash, /bin/busybox, and even /bin/ls are all absent, verified in the running container). Whatever string sits in those fields is the credential.
Choosing a Mode
Themode value selects how SFTPGo runs. scale_to_zero (the default) suspends the server when idle to save cost; always_warm keeps it running for instant connections.
Configuration
The defaultvalues.yaml for this template:
Mode
mode—scale_to_zero(default) oralways_warm. See Choosing a Mode.scaleToZero.idleHold— Suspend SFTPGo after this window with no active connections (e.g.90s,5m,1h). Used only inscale_to_zeromode.scaleToZero.proxy.image/scaleToZero.proxy.resources— Image and resources for the always-on activator proxy. All SFTP traffic flows through the proxy, so provision its CPU accordingly.
SFTPGo
image— The SFTPGo container image.resources— CPU and memory for the SFTPGo container. CPU governs transfer throughput and wake speed;minCpu/minMemoryare the reservation andmaxCpu/maxMemorythe limit.admin.secretName— Name of your pre-created dictionary secret holdingusernameandpassword. It seeds the administrator on first boot only. See Prerequisites.volumeset.capacity— Volume size in GiB (minimum 10) for the embedded database and SSH host keys.webAdmin.enabled— Declare SFTPGo’s web admin / REST API on port8080. Reach it with a port-forward — not the canonical endpoint; see Reaching the Web Admin.
Storage Backend
Setstorage.type to aws, gcp, or minio, and configure that block. AWS and GCP use a Control Plane cloud identity — no credentials are stored; the workload’s identity vends temporary credentials at runtime. See Prerequisites for the per-backend setup steps.
Users
Declared users are re-applied on every start; each is isolated to the bucket folder{keyPrefix}{username}/ unless overridden per user. Provide a password, one or more publicKeys, or both — prefer publicKeys, which needs no credential in your values at all (see What Stays a Value).
users[].username— Login name.users[].password— Password. Used exactly as written and cannot be moved into a secret (why) — omit it whenpublicKeysis set.users[].publicKeys— SSH public keys, e.g.["ssh-ed25519 AAAA... user@laptop"].users[].keyPrefix— Optional per-user bucket folder override.
Access
publicAccess.enabled— Expose a public SFTP endpoint on the client-facing workload via a dedicated direct load balancer, required for raw-TCP protocols like SFTP. Set tofalsefor an internal-only endpoint (no load balancer; clients reach SFTPGo over the GVC network).internalAccess.type— Internal firewall scope of the SFTPGo workload:
Connecting
Reaching the Web Admin
The web admin is not served on the canonical endpoint. Public access usesloadBalancer.direct on port 2022, which makes the workload’s canonical endpoint a raw TCP address of the form tcp://....cpln.app:2022; an HTTPS request to that host reaches nothing at all. Reach the web admin and REST API through a port-forward instead, which tunnels through Control Plane infrastructure and needs no public exposure:
http://localhost:8080 and sign in with the credentials from your admin.secretName secret. This requires webAdmin.enabled: true, which declares port 8080 on the container.
Cold Starts and Client Configuration
Inscale_to_zero mode, the first connection after an idle period wakes the server (measured ~30s, occasionally up to ~75s — the persistent volume attaches on each wake). The proxy holds the TCP connection so nothing is refused, but clients with short SSH banner timeouts (~15s in several libraries) give up right at the finish line. Configure clients generously:
- paramiko —
connect(..., banner_timeout=120, timeout=120) - WinSCP — Session → Timeout ≥ 120s
- OpenSSH CLI — Tolerant by default; no change needed
- Unattended jobs — Retry with backoff. The first (even failed) attempt triggers the wake, and
idleHoldkeeps the server warm so the retry lands instantly. - Right after install — The load balancer needs a few minutes to warm up; the very first cold connect may time out once, then succeed on retry.
always_warm.
Important Notes
- Create the admin credentials secret before installing. A missing prerequisite secret wedges the deployment with no log output at all; Prerequisites gives the one command that diagnoses it.
- Change the default
users[]passwords before installing — they are used exactly as written, and cannot be moved into a secret. PreferpublicKeys; see What Stays a Value. - The admin secret seeds first boot only. Rotate an existing administrator’s password through the REST API or web admin, not by editing the secret.
- The web admin is reached by port-forward, not the canonical endpoint — see Reaching the Web Admin.
- Access changes take time to propagate — after toggling
publicAccessorinternalAccess, re-test over roughly 30 seconds to 5 minutes before concluding the knob is broken. - Declared users are authoritative — edits made to them via the admin API/UI are overwritten on the next restart or wake. Users created via the API are untouched.
- Upgrading while suspended wakes the server; it re-suspends after the next connection comes and goes.
- First install: the public endpoint’s DNS takes a few minutes to propagate after the load balancer is created.
- Switching modes moves the client-facing endpoint (proxy ↔ SFTPGo) — plan a client cutover if you change modes on a live install.
- The dedicated load balancer is the dominant idle cost in
scale_to_zeromode, not the proxy’s compute.
External References
SFTPGo Documentation
Official SFTPGo documentation
S3 Storage Backend
SFTPGo S3-compatible storage backend guide
Environment Variables
SFTPGo environment variables reference
Scale-to-Zero Proxy
Source for the always-on TCP activator proxy
SFTPGo Template
View the source files, default values, and chart definition