Sign inSign up

thorntech/storagelink-backend

By thorntech

•Updated 2 days ago

Browser-based file access to AWS S3, Azure Blob/File Share, and Google Cloud Storage.

Image
Security
Integration & delivery
Databases & storage
0

457

thorntech/storagelink-backend repository overview

⁠What's New in 1.3.1

A security patch of 1.3.0. Every deployment should upgrade: replace the 1.3.0 images with 1.3.1 and keep the existing database.

  • Clears the CRITICAL and HIGH dependency CVEs published since 1.3.0, including the Apache Tomcat findings under Scanner findings in 1.3.0⁠, and includes other security fixes identified by our engineering team
  • If you use Azure storage or rename items through the API, read Action required in the CHANGELOG⁠ before upgrading.

⁠What's New in 1.3.0

  • First container release. StorageLink is now available as two container images for Docker Compose, Kubernetes, and OpenShift, alongside the AWS, Azure, and GCP marketplace images.
  • Self-service 30-day free trial and license activation directly from the admin dashboard, plus non-interactive activation through LICENSE_CONTENT for unattended deployments
  • Local File System connections: serve a mounted directory (NFS, on-premises storage, or a Docker volume) alongside AWS S3, Azure Blob, Azure File Share, and Google Cloud Storage
  • In-app file previews, resumable and seekable downloads (HTTP Range), and downloadable transfer job reports
  • Uploads stream through to cloud storage with concurrent chunk uploads on every provider, and succeed up to each provider's maximum object size
  • Non-root images (UID 1000, GID 0) that pass the Kubernetes restricted pod security standard and support OpenShift arbitrary UIDs and readOnlyRootFilesystem
  • Structured JSON logs on standard output, and Kubernetes-style liveness and readiness probes

For the full changelog, see the CHANGELOG⁠.


StorageLink gives your users secure, browser-based access to files in AWS S3, Azure Blob Storage, Azure File Share, Google Cloud Storage, and local file systems. Users upload, download, preview, and move files through a web interface, and files stream through your own StorageLink deployment to your storage, never through a third-party service.

Administrators manage users, folder permissions, storage connections, transfer jobs, and single sign-on (OIDC) from the same web interface.

⁠Components

StorageLink consists of two container images that work together:

ImageDescription
thorntech/storagelink-backend (this image)REST API, cloud storage integration, and transfer jobs
thorntech/storagelink-ui⁠Web interface for users and administrators

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=$(openssl rand -hex 32)

# 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: storagelink_db
    image: postgres:18-alpine
    environment:
      POSTGRES_DB: swift_gw
      POSTGRES_USER: swiftgw
      POSTGRES_PASSWORD: swiftgw
      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 swiftgw -d swift_gw"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - storagelink-network

  init-permissions:
    image: thorntech/storagelink-backend:1.3.1
    user: root
    entrypoint: ["/bin/sh", "-c"]
    # The backend runs as numeric UID 1000 / GID 0, so the local storage
    # volume must be owned by that UID before the backend starts.
    command: ["chown -R 1000:0 /mnt/storagelink_1"]
    volumes:
      - storage:/mnt/storagelink_1

  backend:
    container_name: storagelink_backend
    image: thorntech/storagelink-backend:1.3.1
    depends_on:
      db:
        condition: service_healthy
      init-permissions:
        condition: service_completed_successfully
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/swift_gw
      SPRING_DATASOURCE_USERNAME: swiftgw
      SPRING_DATASOURCE_PASSWORD: swiftgw
      SECURITY_CLIENT_ID: ${SECURITY_CLIENT_ID}
      SECURITY_CLIENT_SECRET: ${SECURITY_CLIENT_SECRET}
      SECURITY_JWT_SECRET: ${SECURITY_JWT_SECRET}
      FEATURES_FIRST_CONNECTION_CLOUD_PROVIDER: lfs
      FEATURES_FIRST_CONNECTION_BASE_PREFIX: /mnt/storagelink_1
    volumes:
      - storage:/mnt/storagelink_1
    # The backend is reached only through the UI container, so it needs no
    # published ports.
    user: "1000:0"
    restart: unless-stopped
    networks:
      - storagelink-network

  ui:
    container_name: storagelink_ui
    image: thorntech/storagelink-ui:1.3.1
    depends_on:
      - backend
    environment:
      BACKEND_URL: http://backend:8080/
      SECURITY_CLIENT_ID: ${SECURITY_CLIENT_ID}
      SECURITY_CLIENT_SECRET: ${SECURITY_CLIENT_SECRET}
      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. Keep HTTPS on
    # host port 443: the HTTP landing page links to https://<host> with no port.
    ports:
      - "80:8080"
      - "443:8443"
    restart: unless-stopped
    networks:
      - storagelink-network

volumes:
  postgres_data:
    driver: local
  storage:
    driver: local

networks:
  storagelink-network:
    driver: bridge
⁠3. Start the Services
docker compose up -d

Open https://localhost and accept the self-signed certificate. While the backend is starting, the page shows a "server is starting" screen and loads the sign-in page automatically once the server is ready.

On first launch, a setup screen creates the initial administrator account. The quick start also creates a Local File System connection backed by the storage volume, ready for uploads and downloads as soon as a license is active (next step).

⁠4. Activate a Trial License

StorageLink requires a valid license before it transfers files. Until one is in place, administrators can sign in and manage settings, users, and licensing, but uploads, downloads, and transfer jobs are blocked for every role.

⁠Self-service 30-day trial

Sign in as the administrator. While the instance is unlicensed, a Start a 30-day free trial card appears:

  1. Enter your work email address and accept the End User License Agreement.
  2. Choose Send verification code, then enter the six-digit code from your inbox.
  3. Choose Start trial. The trial license is issued, bound to this deployment's cluster ID, and activated immediately, 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 need 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, 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. In Settings, find the License card, paste the license into the field or drop the license file onto it, and choose Activate. The license 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 card. 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}

A license supplied this way that is not yet bound to a cluster is activated automatically at startup.

LICENSE_CONTENT takes precedence over the stored license on every license reload. If it is set, activating a different license 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 the UI)Yes
SECURITY_CLIENT_SECRETOAuth client secret (must match the UI)Yes
SECURITY_JWT_SECRETJWT signing secret. If unset, a random value is generated at every start and all sessions end on restart.Yes
SERVER_PORTAPI server port (default: 8080)No
FEATURES_FIRST_CONNECTION_CLOUD_PROVIDERStorage connection created on first launch: lfs, aws, azure, azure_fileshare, or gcp (default: none)No
FEATURES_FIRST_CONNECTION_BASE_PREFIXStorage location for that connection, such as a directory for lfs. No connection is created unless this is set.No
LICENSE_CONTENTLicense contents. A license not yet bound to a cluster is activated automatically at startup.No
SECURITY_CLIENTIP_TRUSTEDPROXIESComma-separated CIDR ranges whose X-Forwarded-For headers are trusted when recording client addresses (default: loopback and private ranges)No
AWS_REGIONDefault AWS region (default: us-east-1)No
JAVA_OPTSExtra JVM options. The heap defaults to 80% of the container memory limit.No
⁠UI Environment Variables

See thorntech/storagelink-ui⁠ for the UI container's variables, TLS options, and ports.

⁠Ports
PortProtocolDescription
8080TCPREST API, reached by the UI container at BACKEND_URL
⁠Volumes
PathDescription
/mnt/storagelink_1Local File System storage in the quick start. Any path outside /opt/swiftgw works; the storage directory must be writable by UID 1000 or GID 0.
/tmpUpload chunks are spooled here during transfers. Under readOnlyRootFilesystem, mount a writable volume with room for the uploads in flight.
⁠Health Checks
PathDescription
/actuator/health/livenessLiveness probe
/actuator/health/readinessReadiness probe, including the database check
⁠Logging

The backend writes structured JSON logs to standard output. Each line carries a logger_type field of application, audit, or license-audit, so a log collector can route the streams separately. No log files are written inside the container.

⁠PostgreSQL Compatibility

StorageLink ships and is tested against PostgreSQL 18 (the version in the quick start above). PostgreSQL 16 and 17 are also compatible, with no application or JDBC driver changes required.

⁠Cloud Storage Support

StorageLink connects to:

  • AWS S3
  • Azure Blob Storage
  • Azure File Share
  • Google Cloud Storage
  • Local File System (any directory mounted into the backend container)

Configure storage connections and their credentials through the admin dashboard.

⁠Security

StorageLink container images are built on Docker Hardened Images⁠ base images, run as a non-root user, and listen only on non-privileged ports. Every release includes an SBOM (Software Bill of Materials) and SLSA⁠ provenance attestations, so you can verify what an image contains and how it was built.

You can inspect the SBOM and provenance for any image using Docker Scout⁠ or BuildKit imagetools:

# View attestations with Docker Scout
docker scout attestation list thorntech/storagelink-backend:latest

# Inspect SBOM and provenance with buildx
docker buildx imagetools inspect thorntech/storagelink-backend:latest --format '{{json .SBOM}}'
docker buildx imagetools inspect thorntech/storagelink-backend:latest --format '{{json .Provenance}}'
⁠Scanner findings in 1.3.0

Vulnerability scanners report three CRITICAL findings in the 1.3.0 backend image, all in its embedded Apache Tomcat 11.0.22: CVE-2026-65182, CVE-2026-65905, and CVE-2026-68525. None of them is reachable in StorageLink. Each one requires a Tomcat feature that StorageLink does not use:

CVERequires
CVE-2026-65182Security constraints declared to Tomcat itself (<security-constraint>)
CVE-2026-65905Tomcat DIGEST authentication
CVE-2026-68525Tomcat FORM authentication

StorageLink declares no Tomcat security constraints or authenticators. It authenticates and authorizes every request in the application instead. The Apache Tomcat project rates these issues Important, Low, and Low. StorageLink 1.3.1 upgrades Tomcat to 11.0.25, which clears all three.

⁠License & Terms

StorageLink is commercial software. Use is governed by the End User License Agreement⁠. A 30-day free trial is included, with no credit card required.

To purchase a license, visit storagelink.co/purchase⁠ or contact [email protected]⁠.

⁠Support

For technical support, contact [email protected]⁠.

⁠About Thorn Technologies

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

Tag summary

Content type

Image

Digest

sha256:519f1c6c8…

Size

572.2 MB

Last updated

2 days ago

docker pull thorntech/storagelink-backend