Skip to main content

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.
Upgrading an install created with 1.0.0 is a breaking change. admin.username and admin.password no longer exist, and an upgrade that still sets either one stops with an error naming its replacement. Your existing admin login is unaffected — the secret only seeds a fresh install. See Upgrading From 1.0.0.

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 reveal access 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 username and password:
Set 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.
A missing prerequisite secret wedges the install rather than failing it. cpln helm install still exits 0 and reports success, the resources are created, and the workload then never starts. Because the container never ran, cpln logs returns zero lines, which reads as a broken platform rather than a missing prerequisite.The only diagnostic is status.versions[].message, which names the missing secret:
It is get-deployments — plain cpln workload get has no versions key at all. Creating the missing secret clears the wedge on its own, but slowly: recovery across the catalog has been measured between 5.5 and 10.5 minutes, so poll rather than time-boxing it. A forced redeployment shortcuts it to roughly 90 seconds.

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 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):
Once the admin secret exists and your backend is ready, install the template using your preferred method:

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

Version 1.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.
A helm upgrade that still carries either removed key is rejected before anything is applied. A real cpln helm upgrade carrying the old keys failed at render, created no Helm revision, and left the running release healthy and untouched:
Leaving admin.secretName empty is refused the same way.
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 users
  • storage.minio.accessKey / storage.minio.accessSecret — static keys for an S3-compatible backend
SFTPGo reads both from its LOADDATA file, a literal JSON document it does no variable substitution in, so a 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.
Prefer public-key users. Setting users[].publicKeys and omitting password needs no secret at all, is better practice for SFTP regardless, and sidesteps the limitation entirely. The AWS and GCP storage backends are keyless already — only the minio backend takes static keys.

Choosing a Mode

The mode 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 default values.yaml for this template:

Mode

  • modescale_to_zero (default) or always_warm. See Choosing a Mode.
  • scaleToZero.idleHold — Suspend SFTPGo after this window with no active connections (e.g. 90s, 5m, 1h). Used only in scale_to_zero mode.
  • 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 / minMemory are the reservation and maxCpu / maxMemory the limit.
  • admin.secretName — Name of your pre-created dictionary secret holding username and password. 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 port 8080. Reach it with a port-forward — not the canonical endpoint; see Reaching the Web Admin.

Storage Backend

Set storage.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 when publicKeys is 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 to false for 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 uses loadBalancer.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:
Then open 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

In scale_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:
  • paramikoconnect(..., 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 idleHold keeps 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.
For third-party clients you cannot configure, use 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. Prefer publicKeys; 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 publicAccess or internalAccess, 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_zero mode, 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