Lightweight SFTP server streaming files directly to AWS S3, Azure Blob, and Google Cloud Storage.
7.9K
SFTP_PORT). The images pass the Kubernetes restricted pod security standard and support OpenShift arbitrary UIDs. Update compose files or manifests that assumed root or privileged ports — see the updated quick start below.ADMIN_USERNAME, ADMIN_PASSWORD or ADMIN_PASSWORD_FILE) for unattended deployments, and a license supplied via LICENSE_CONTENT now activates automatically at bootzlib, [email protected]) and algorithm aliases for legacy clientsFor the full changelog, see the CHANGELOG.
SFTP Gateway is a secure, scalable, and easy-to-use solution for transferring files via SFTP to cloud storage services such as AWS S3, Azure Blob, and Google Cloud Storage. Files are streamed directly to cloud storage—data is never stored in transit or exposed to a third party.
Administrators manage the SFTP service through an intuitive web dashboard with support for OIDC and LDAP integration.
SFTP Gateway consists of two container images that work together:
| Image | Description |
|---|---|
| thorntech/sftpgateway-backend (this image) | Core SFTP server, API, and cloud storage integration |
| thorntech/sftpgateway-admin-ui | Web-based administration dashboard |
A PostgreSQL database is also required (included in the quick start below).
Run the following to generate security credentials and a self-signed TLS certificate, and save them to a .env file that Docker Compose picks up automatically:
# Generate security credentials
SECURITY_CLIENT_ID=$(openssl rand -hex 16)
SECURITY_CLIENT_SECRET=$(openssl rand -hex 32)
SECURITY_JWT_SECRET=$(uuidgen 2>/dev/null \
|| cat /proc/sys/kernel/random/uuid 2>/dev/null \
|| openssl rand -hex 16)
# Generate a self-signed TLS certificate (valid for 1 year)
openssl req -x509 -newkey rsa:2048 -keyout tls.key -out tls.crt \
-days 365 -nodes -subj "/CN=localhost" 2>/dev/null
# Save everything to .env (Docker Compose reads this automatically)
{
echo "SECURITY_CLIENT_ID=${SECURITY_CLIENT_ID}"
echo "SECURITY_CLIENT_SECRET=${SECURITY_CLIENT_SECRET}"
echo "SECURITY_JWT_SECRET=${SECURITY_JWT_SECRET}"
printf 'WEBSITE_BUNDLE_CRT="%s"\n' "$(cat tls.crt)"
printf 'WEBSITE_KEY="%s"\n' "$(cat tls.key)"
} > .env
rm -f tls.crt tls.key
echo "Credentials saved to .env"
Create a docker-compose.yml file:
services:
db:
container_name: sftpgw_container_db
image: postgres:18-alpine
environment:
POSTGRES_DB: sftpgw
POSTGRES_USER: sftpgw
POSTGRES_PASSWORD: sftpgw
PGDATA: /var/lib/postgresql/data/pgdata
POSTGRES_INITDB_ARGS: "--data-checksums"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sftpgw -d sftpgw"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- sftpgw-network
init-permissions:
image: thorntech/sftpgateway-backend:3.9.0
user: root
entrypoint: ["/bin/sh", "-c"]
# The backend image no longer has a named sftpgw user/group (it runs as
# numeric UID 1000 / GID 0), so chown to the numeric owner instead.
command: ["chown -R 1000:0 /mnt/sftpgw_1"]
volumes:
- mount:/mnt/sftpgw_1
backend:
container_name: backend
image: thorntech/sftpgateway-backend:3.9.0
depends_on:
db:
condition: service_healthy
init-permissions:
condition: service_completed_successfully
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/sftpgw
SPRING_DATASOURCE_USERNAME: sftpgw
SPRING_DATASOURCE_PASSWORD: sftpgw
SECURITY_CLIENT_ID: ${SECURITY_CLIENT_ID}
SECURITY_CLIENT_SECRET: ${SECURITY_CLIENT_SECRET}
SECURITY_JWT_SECRET: ${SECURITY_JWT_SECRET}
SERVER_PORT: 8080
FEATURES_FIRST_CONNECTION_CLOUD_PROVIDER: lfs
FEATURES_FIRST_CONNECTION_BASE_PREFIX: /mnt/sftpgw_1
AWS_REGION: ${AWS_REGION:-us-east-1}
HOME: /home/sftpgw
volumes:
- sftpgw_home:/home/sftpgw
- mount:/mnt/sftpgw_1
ports:
- "8080:8080"
# The container serves SFTP on the non-privileged port 2244. To serve the
# standard SFTP port instead, change the mapping to "22:2244" (make sure
# the host's own sshd is not already bound to port 22).
- "2244:2244"
# The backend image runs as numeric UID 1000 / GID 0 (the named sftpgw user
# was removed for OpenShift arbitrary-UID compatibility); match it here.
user: "1000:0"
working_dir: /opt/sftpgw
restart: unless-stopped
networks:
- sftpgw-network
ui:
container_name: sftpgw-ui
image: thorntech/sftpgateway-admin-ui:3.9.0
environment:
BACKEND_URL: http://backend:8080/
SECURITY_CLIENT_ID: ${SECURITY_CLIENT_ID}
SECURITY_CLIENT_SECRET: ${SECURITY_CLIENT_SECRET}
CLOUD_PROVIDER: lfs
WEBSITE_BUNDLE_CRT: ${WEBSITE_BUNDLE_CRT}
WEBSITE_KEY: ${WEBSITE_KEY}
# The UI image serves HTTP on 8080 and HTTPS on 8443 (non-privileged
# in-container ports); map the standard host ports onto them.
ports:
- "80:8080"
- "443:8443"
restart: unless-stopped
networks:
- sftpgw-network
volumes:
postgres_data:
driver: local
sftpgw_home:
driver: local
mount:
driver: local
networks:
sftpgw-network:
driver: bridge
docker compose up -d
Access the admin dashboard at https://localhost. On first launch a setup wizard
creates the initial admin account. For an unattended deployment, set
ADMIN_USERNAME and one of ADMIN_PASSWORD or ADMIN_PASSWORD_FILE on the
backend service instead, and the account is created at startup before the
listeners accept traffic.
SFTP Gateway requires a valid license before it accepts SFTP connections. Until
one is in place, the admin dashboard is fully reachable but SFTP clients are
disconnected with the message The SFTP Server's license has expired.
Log in to the dashboard. While the instance is unlicensed, a Start a 30-day Trial card appears at the top of every page:
2244 right away, with no restart required.The verification request is made by your browser, which needs to reach
https://licensing.thorntech.com. The containers themselves do not require
outbound internet access for this step. One trial is issued per email address.
If you already hold a license key, whether a trial or a purchased one, there are two ways to install it. Use one or the other, not both.
Enter it in the dashboard. Go to Diagnostics → License Information, choose Update License, then paste the key into the License Key field or drop the license file onto it. The key is stored in the database, so it survives container restarts and image upgrades, every instance in a clustered deployment picks it up, and it can be replaced later from the same screen. No environment variable is involved. This is the better choice for most deployments.
Inject it through the environment. Set LICENSE_CONTENT on the backend
service, which suits immutable infrastructure and unattended deployments where
nobody signs in to the dashboard:
backend:
environment:
LICENSE_CONTENT: ${LICENSE_CONTENT}
An unbound license supplied this way is bound automatically at boot.
LICENSE_CONTENT takes precedence over the stored key on every license reload.
If it is set, entering a different key in the dashboard appears to work and is
then replaced by the environment value within a minute, so leave
LICENSE_CONTENT unset whenever you intend to manage licensing from the
dashboard.
| Variable | Description | Required |
|---|---|---|
SPRING_DATASOURCE_URL | PostgreSQL JDBC connection string | Yes |
SPRING_DATASOURCE_USERNAME | Database username | Yes |
SPRING_DATASOURCE_PASSWORD | Database password | Yes |
SECURITY_CLIENT_ID | OAuth client ID (must match UI) | Yes |
SECURITY_CLIENT_SECRET | OAuth client secret (must match UI) | Yes |
SECURITY_JWT_SECRET | JWT signing secret | Yes |
SERVER_PORT | API server port (default: 8080) | No |
SFTP_PORT | SFTP server port (default: 2244) | No |
FEATURES_FIRST_CONNECTION_CLOUD_PROVIDER | Initial cloud provider: lfs, s3, azureblob, gcp (default: none) | No |
FEATURES_FIRST_CONNECTION_BASE_PREFIX | Base path for local file storage (default: none) | No |
AWS_REGION | Default AWS region (default: us-east-1) | No |
LICENSE_CONTENT | License file contents. An unbound license is activated automatically at boot. | No |
ADMIN_USERNAME | Username for the initial admin account. Omit to use the setup wizard. | No |
ADMIN_PASSWORD | Password for the initial admin account. Must satisfy the configured password policy. | No |
ADMIN_PASSWORD_FILE | Path to a file holding the initial admin password, for mounted secrets. Used when ADMIN_PASSWORD is not set. | No |
ADMIN_PASSWORD_FORCE_RESET | Reset the configured admin's password on a later boot (default: false). | No |
| Port | Protocol | Description |
|---|---|---|
| 2244 | TCP | SFTP server (configurable via SFTP_PORT) |
| 8080 | TCP | REST API |
| Path | Description |
|---|---|
/home/sftpgw | Application home directory |
/mnt/sftpgw_1 | Local file storage mount point |
SFTP Gateway ships and is tested against PostgreSQL 18 (the version in the
quick start above). PostgreSQL 16 and 17 are also compatible — no application or
JDBC driver changes are required, and the driver negotiates both md5 and
scram-sha-256 authentication transparently. Run a supported, non-EOL major
version (16 or later); we recommend 18 for the longest support runway.
Already running on PostgreSQL 16 and upgrading in place? Two PostgreSQL 18
defaults catch most pg_upgrade runs — data checksums are on by default, and
md5 password roles emit deprecation warnings. See the PostgreSQL upgrade guide
in the documentation for the full procedure.
SFTP Gateway supports multiple methods for authenticating with cloud storage providers:
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)SFTP Gateway supports streaming files to:
Configure storage destinations through the admin dashboard.
SFTP Gateway container images are built on Docker Hub Infrastructure (DHI) with near-zero CVEs and a distroless runtime, minimizing the attack surface. Every release includes SBOMs (Software Bill of Materials) and SLSA Build Level 3 provenance attestations, providing verifiable proof of build integrity.
You can inspect the SBOM and provenance for any image using Docker Scout or the BuildKit imagetools:
# View attestations with Docker Scout
docker scout attestation list thorntech/sftpgateway-backend:latest
# Inspect SBOM and provenance with buildx
docker buildx imagetools inspect thorntech/sftpgateway-backend:latest --format '{{json .SBOM}}'
docker buildx imagetools inspect thorntech/sftpgateway-backend:latest --format '{{json .Provenance}}'
SFTP Gateway is commercial software. Use is governed by the End User License Agreement. A 30-day free trial is included — no payment information required.
To purchase a license, visit sftpgateway.com/purchase.
For technical support, contact [email protected].
Full documentation is available at sftpgateway.com.
SFTP Gateway is developed by Thorn Technologies, specialists in secure file transfer and cloud integration solutions.
Content type
Image
Digest
sha256:1e699ca52…
Size
389.7 MB
Last updated
about 2 months ago
docker pull thorntech/sftpgateway-backend