Sign inSign up

thorntech/sftpgateway-backend

By thorntech

•Updated about 2 months ago

Lightweight SFTP server streaming files directly to AWS S3, Azure Blob, and Google Cloud Storage.

Image
Security
Integration & delivery
Databases & storage
1

7.9K

thorntech/sftpgateway-backend repository overview

⁠SFTP Gateway

⁠What's New in 3.9.0

  • Containers now run as a non-root user (UID 1000) with non-privileged in-container ports (SFTP moved from 22 to 2244, configurable via 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.
  • Concurrent SFTP connection limits (per user, per source IP, and server-wide) protect the server from runaway clients
  • The initial admin account can be created from environment variables (ADMIN_USERNAME, ADMIN_PASSWORD or ADMIN_PASSWORD_FILE) for unattended deployments, and a license supplied via LICENSE_CONTENT now activates automatically at boot
  • SSH compression (zlib, [email protected]) and algorithm aliases for legacy clients
  • Platform upgrade to Spring Boot 4.1 on Java 21; PostgreSQL 18 in shipped deployment configurations
  • The compressed backend image shrank from 620 MB to 408 MB (-34%)
  • Data-integrity fixes for Azure File Share uploads, timestamp preservation fixes across all providers, non-ASCII filename fixes for local storage, and dependency CVE remediations (Netty, PostgreSQL JDBC, Jackson, logback)

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

⁠Components

SFTP Gateway consists of two container images that work together:

ImageDescription
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).

⁠Quick Start

⁠1. Generate Credentials

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"
⁠2. Create docker-compose.yml

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
⁠3. Start the Services
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.

⁠4. Activate a Trial License

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.

⁠Self-service 30-day trial

Log in to the dashboard. While the instance is unlicensed, a Start a 30-day Trial card appears at the top of every page:

  1. Enter your email address and accept the End User License Agreement.
  2. Retrieve the six-digit verification code from your inbox and enter it.
  3. The trial license is issued, bound to this instance's cluster ID, and activated automatically. The SFTP server begins accepting connections on port 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.

⁠Supplying a license you already have

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.

⁠Configuration

⁠Backend Environment Variables
VariableDescriptionRequired
SPRING_DATASOURCE_URLPostgreSQL JDBC connection stringYes
SPRING_DATASOURCE_USERNAMEDatabase usernameYes
SPRING_DATASOURCE_PASSWORDDatabase passwordYes
SECURITY_CLIENT_IDOAuth client ID (must match UI)Yes
SECURITY_CLIENT_SECRETOAuth client secret (must match UI)Yes
SECURITY_JWT_SECRETJWT signing secretYes
SERVER_PORTAPI server port (default: 8080)No
SFTP_PORTSFTP server port (default: 2244)No
FEATURES_FIRST_CONNECTION_CLOUD_PROVIDERInitial cloud provider: lfs, s3, azureblob, gcp (default: none)No
FEATURES_FIRST_CONNECTION_BASE_PREFIXBase path for local file storage (default: none)No
AWS_REGIONDefault AWS region (default: us-east-1)No
LICENSE_CONTENTLicense file contents. An unbound license is activated automatically at boot.No
ADMIN_USERNAMEUsername for the initial admin account. Omit to use the setup wizard.No
ADMIN_PASSWORDPassword for the initial admin account. Must satisfy the configured password policy.No
ADMIN_PASSWORD_FILEPath to a file holding the initial admin password, for mounted secrets. Used when ADMIN_PASSWORD is not set.No
ADMIN_PASSWORD_FORCE_RESETReset the configured admin's password on a later boot (default: false).No
⁠Ports
PortProtocolDescription
2244TCPSFTP server (configurable via SFTP_PORT)
8080TCPREST API
⁠Volumes
PathDescription
/home/sftpgwApplication home directory
/mnt/sftpgw_1Local file storage mount point
⁠PostgreSQL Compatibility

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.

⁠Cloud Storage Authentication

SFTP Gateway supports multiple methods for authenticating with cloud storage providers:

  1. Admin Dashboard -- Configure credentials directly through the web interface
  2. Environment Variables -- Pass cloud provider credentials as environment variables (e.g., AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  3. IAM Roles -- When running on AWS, attach an IAM role to your container host or ECS task

⁠Cloud Storage Support

SFTP Gateway supports streaming files to:

  • AWS S3
  • Azure Blob Storage
  • Google Cloud Storage
  • Local filesystem

Configure storage destinations through the admin dashboard.

⁠Security

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}}'

⁠License & Terms

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

⁠Support

For technical support, contact [email protected]⁠.

⁠Documentation

Full documentation is available at sftpgateway.com⁠.

⁠About Thorn Technologies

SFTP Gateway is developed by Thorn Technologies⁠, specialists in secure file transfer and cloud integration solutions.

Tag summary

Content type

Image

Digest

sha256:1e699ca52…

Size

389.7 MB

Last updated

about 2 months ago

docker pull thorntech/sftpgateway-backend