> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SFTPGo

> Deploy SFTPGo on Control Plane using the Template Catalog. An SFTP server backed by S3, GCS, or S3-compatible object storage, with a scale-to-zero mode that suspends the server when idle.

## 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.

### 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.

### 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.
* **Secrets** — A dictionary secret with the admin bootstrap credentials and an opaque secret with the declared-users file.
* **Identity & Policy** — An identity bound to the workloads with `reveal` access to the secrets, 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.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

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.

<Tabs>
  <Tab title="AWS S3 (keyless)">
    AWS S3 uses a Control Plane cloud identity — no credentials are stored; the workload's identity vends temporary credentials at runtime.

    <Steps>
      <Step title="Create a bucket">
        Create an S3 bucket. Set `storage.aws.bucket` and `storage.aws.region` to match.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) for your AWS account. Set `storage.aws.cloudAccountName` to its name.
      </Step>

      <Step title="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):

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            "Action": [
              "s3:ListBucket",
              "s3:GetBucketLocation",
              "s3:GetObject",
              "s3:PutObject",
              "s3:DeleteObject",
              "s3:AbortMultipartUpload"
            ],
            "Resource": [
              "arn:aws:s3:::YOUR_BUCKET",
              "arn:aws:s3:::YOUR_BUCKET/*"
            ]
          }]
        }
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Google Cloud Storage (keyless)">
    Google Cloud Storage uses a Control Plane cloud identity — no credentials are stored.

    <Steps>
      <Step title="Create a bucket">
        Create a GCS bucket. Set `storage.gcp.bucket` to its name.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) for your GCP project. Set `storage.gcp.cloudAccountName` to its name.
      </Step>

      <Step title="Confirm bucket access">
        No policy to author — the template grants the workload identity `roles/storage.objectAdmin` scoped to the bucket automatically. Ensure your cloud account setup permits its service account to receive that binding.
      </Step>
    </Steps>
  </Tab>

  <Tab title="S3-compatible (MinIO, R2, Wasabi)">
    Any S3-compatible server uses static access credentials, since these cannot federate with a cloud account.

    <Steps>
      <Step title="Create a bucket">
        Create your bucket on the server. Set `storage.minio.bucket` to its name.
      </Step>

      <Step title="Set the endpoint">
        Set `storage.minio.endpoint` to the S3 API address including port. For the `minio` marketplace template deployed in the same GVC, this is `http://WORKLOAD_NAME:9000`.
      </Step>

      <Step title="Set credentials">
        Set `storage.minio.accessKey` and `storage.minio.accessSecret` to credentials with access to the bucket. For the MinIO template, these are its `admin.username` and `admin.password`.
      </Step>
    </Steps>
  </Tab>
</Tabs>

Once your backend is ready, install the template using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## 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.

|                          | `scale_to_zero` (default)                               | `always_warm`                              |
| ------------------------ | ------------------------------------------------------- | ------------------------------------------ |
| Cost when idle           | Proxy (\~100m/128Mi) + the dedicated load balancer      | Full SFTPGo replica + load balancer        |
| First connect after idle | \~30s cold start (occasionally up to \~75s)             | Instant                                    |
| Client requirements      | Timeout ≥120s or retry (see below)                      | None — works unchanged                     |
| Best for                 | Cost-sensitive, periodic transfers, cooperative clients | Strict SLAs, arbitrary third-party clients |

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: drakkan/sftpgo:v2.7.4-distroless-slim

mode: scale_to_zero # options: scale_to_zero, always_warm

scaleToZero: # only used when mode is scale_to_zero
  idleHold: 5m # suspend after this window with no active connections (e.g. 90s, 5m, 1h)
  proxy:
    image: ghcr.io/controlplane-com/scale-to-zero-proxy:0.1.0
    resources: # all SFTP traffic flows through the proxy
      cpu: 500m
      memory: 256Mi
      minCpu: 100m
      minMemory: 128Mi

resources: # SFTPGo container; CPU governs transfer throughput and wake speed
  cpu: 1000m
  memory: 512Mi
  minCpu: 250m
  minMemory: 256Mi

admin: # SFTPGo administrator (REST API / optional web admin), created on first boot
  username: admin
  password: change-me-sftpgo-admin # change before installing

storage:
  type: aws # options: aws, gcp, minio

  aws: # AWS S3 — keyless via Control Plane cloud identity
    bucket: my-sftp-bucket # bucket must already exist
    region: us-east-1
    keyPrefix: "" # optional bucket-wide folder prefix, e.g. sftp/
    cloudAccountName: my-s3-cloud-account # Control Plane AWS cloud account
    policyName: my-sftp-s3-policy # custom IAM policy granting bucket access (bare name)

  gcp: # Google Cloud Storage — keyless via Control Plane cloud identity
    bucket: my-sftp-bucket # bucket must already exist
    keyPrefix: "" # optional bucket-wide folder prefix, e.g. sftp/
    cloudAccountName: my-gcs-cloud-account # Control Plane GCP cloud account

  minio: # Any S3-compatible server (MinIO, R2, Wasabi, …) — static keys only
    endpoint: http://my-minio-workload:9000 # e.g. an internal MinIO template deployment
    bucket: my-sftp-bucket # bucket must already exist
    region: us-east-1
    keyPrefix: "" # optional bucket-wide folder prefix, e.g. sftp/
    accessKey: my-minio-username
    accessSecret: my-minio-password

users: # Declarative user list, re-applied on every start
  - username: sftpuser
    password: change-me-sftp-user # omit when publicKeys is set
    publicKeys: [] # SSH public keys, e.g. ["ssh-ed25519 AAAA... user@laptop"]
    # keyPrefix: custom/folder/ # optional per-user bucket folder override

volumeset:
  capacity: 10 # GiB (minimum 10) — stores the embedded database and SSH host keys

publicAccess:
  enabled: true # public SFTP endpoint (dedicated NLB) on the client-facing workload

internalAccess: # internal firewall scope of the SFTPGo workload
  type: same-gvc # options: none, same-gvc, same-org, workload-list (scale_to_zero requires non-none)
  workloads: [] # used with workload-list; the scale-to-zero proxy is added automatically

webAdmin:
  enabled: false # declare SFTPGo's web admin/REST API port 8080
```

### Mode

* `mode` — `scale_to_zero` (default) or `always_warm`. See [Choosing a Mode](#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.
* `admin.username` / `admin.password` — The SFTPGo administrator, created on first boot for the REST API and optional web admin. **Change the password before installing.**
* `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`. Reachable via the canonical endpoint in `always_warm` mode when `publicAccess` is enabled.

### 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](#prerequisites) for the per-backend setup steps.

| Field                        | Backends       | Description                                       |
| ---------------------------- | -------------- | ------------------------------------------------- |
| `bucket`                     | all            | Target bucket. Must already exist.                |
| `region`                     | `aws`, `minio` | Bucket region.                                    |
| `keyPrefix`                  | all            | Optional bucket-wide folder prefix, e.g. `sftp/`. |
| `cloudAccountName`           | `aws`, `gcp`   | Control Plane cloud account for keyless access.   |
| `policyName`                 | `aws`          | Bare name of the custom bucket-scoped IAM policy. |
| `endpoint`                   | `minio`        | S3 API address including port.                    |
| `accessKey` / `accessSecret` | `minio`        | Static credentials with access to the bucket.     |

### 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.

* `users[].username` — Login name.
* `users[].password` — Password. Omit 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:

| Type            | Description                                                                                                            |
| --------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `none`          | No internal access. Not allowed in `scale_to_zero` mode (the proxy must reach SFTPGo).                                 |
| `same-gvc`      | Allow access from all workloads in the same GVC.                                                                       |
| `same-org`      | Allow access from all workloads in the same organization.                                                              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. The scale-to-zero proxy is added automatically. |

## Connecting

| What                   | Value                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Public SFTP endpoint   | `tcp://...cpln.app:2022` — `status.endpoint` of `{release}-sftpgo-proxy` (scale\_to\_zero) or `{release}-sftpgo` (always\_warm) |
| Connect                | `sftp -P 2022 {username}@{endpoint-host}`                                                                                       |
| In-GVC (internal)      | `{release}-sftpgo-proxy:2022` (scale\_to\_zero) or `{release}-sftpgo:2022` (always\_warm)                                       |
| Web admin (if enabled) | Canonical `*.cpln.app` endpoint of `{release}-sftpgo` (always\_warm + publicAccess)                                             |
| Credentials            | `users[]` entries; admin per `admin.*`                                                                                          |

## 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:

* **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 `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

* **Change the default admin and user passwords before installing.** The admin bootstrap only applies on first boot; changing `admin.*` later requires the REST API.
* **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

<CardGroup cols={2}>
  <Card title="SFTPGo Documentation" icon="book" href="https://docs.sftpgo.com/">
    Official SFTPGo documentation
  </Card>

  <Card title="S3 Storage Backend" icon="database" href="https://docs.sftpgo.com/2.7/s3/">
    SFTPGo S3-compatible storage backend guide
  </Card>

  <Card title="Environment Variables" icon="gear" href="https://docs.sftpgo.com/latest/env-vars/">
    SFTPGo environment variables reference
  </Card>

  <Card title="Scale-to-Zero Proxy" icon="github" href="https://github.com/controlplane-com/scale-to-zero-proxy">
    Source for the always-on TCP activator proxy
  </Card>

  <Card title="SFTPGo Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/sftpgo">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
