DirectorySecurity AdvisoriesPricing
Sign in
Directory
supabase-storage-api-fips logoFIPS

supabase-storage-api-fips

packaged by Chainguard

Last changed
Request a free trial

Contact our team to test out this image for free. Please also indicate any other images you would like to evaluate.

Tags
Overview
Comparison
Provenance
Specifications
SBOM
Vulnerabilities
Advisories

Chainguard Container for supabase-storage-api-fips

FIPS-mode S3 compatible object storage service that stores metadata in Postgres

Chainguard Containers are regularly-updated, secure-by-default container images.

Download this Container Image

For those with access, this container image is available on cgr.dev:

docker pull cgr.dev/ORGANIZATION/supabase-storage-api-fips:latest

Be sure to replace the ORGANIZATION placeholder with the name used for your organization's private repository within the Chainguard Registry.

Compatibility Notes

The Chainguard supabase-storage-api-fips image is a FIPS-mode replacement for the upstream supabase/storage-api image. It needs AUTH_ENCRYPTION_KEY, and it cannot read values that an existing deployment encrypted, as described under FIPS Support. Supabase Storage is a TypeScript (Node.js/Fastify) server that stores object metadata in PostgreSQL and the objects themselves on an S3-compatible backend or the local filesystem. It serves the REST, TUS resumable-upload, and S3-compatible protocols.

Like the upstream image, this image runs docker-entrypoint.sh with the default command node dist/start/server.js from the /app working directory, and sets the VERSION environment variable reported by the /version endpoint.

Unlike the upstream image, which runs as root, this image runs as the non-root user nonroot (UID 65532). When using the file storage backend, the directory configured with STORAGE_FILE_BACKEND_PATH must be writable by UID 65532.

The server listens on port 5000.

FIPS Support

This Chainguard Container ships with a validated redistribution of OpenSSL's FIPS provider module. For more on FIPS support in Chainguard Containers, refer to the guide on FIPS-enabled Chainguard Images on Chainguard Academy.

Supabase Storage encrypts some values with AES-256-CBC and a key derived from AUTH_ENCRYPTION_KEY. Upstream derives the key with MD5, which is not a FIPS-approved key derivation, so this image derives it with PBKDF2-HMAC-SHA256. This has two effects:

  • AUTH_ENCRYPTION_KEY must be at least 14 bytes long, because the FIPS provider refuses shorter PBKDF2 passwords. Upstream defaults it to an empty string. Without a valid key, requests that encrypt or decrypt fail. In single-tenant mode, these are S3-protocol multipart uploads. In multi-tenant mode, they also include tenant secrets, tenant JWKS, and stored S3 access keys. Generate a key with openssl rand -hex 32. The Getting Started example uses only the REST API, so it runs without one.
  • Values that the upstream image or the non-FIPS supabase-storage-api image encrypted do not decrypt in this image: the multi-tenant secrets, JWKS, and S3 access keys above, and S3 multipart uploads in progress. Re-create them after you switch to this image.

The file storage backend computes object ETags with MD5 by default, and always computes the ETags of S3 multipart parts with MD5. These are checksums, not security functions. They work in this image because the image enables the OpenSSL legacy provider by default. Do not set CHAINGUARD_LEGACY_ALLOWED=0 with the file backend, because multipart uploads then fail. STORAGE_FILE_ETAG_ALGORITHM=mtime removes MD5 from whole-object ETags only.

To connect to PostgreSQL over TLS, put sslmode=verify-full in DATABASE_URL and give the CA certificate in NODE_EXTRA_CA_CERTS:

-e DATABASE_URL="postgresql://postgres:<password>@<host>:5432/postgres?sslmode=verify-full" \
-e NODE_EXTRA_CA_CERTS=/certs/ca.crt \
-v "$PWD/ca.crt:/certs/ca.crt:ro"

DATABASE_SSL_ROOT_CERT alone is not enough: storage's pub/sub connection uses only DATABASE_URL, so it fails against a server that requires TLS. The upstream image behaves the same way.

Prerequisites

Supabase Storage needs a PostgreSQL database that the container can reach, passed as DATABASE_URL. Its migrations grant privileges to the Supabase roles anon, authenticated, and service_role:

  • With a plain PostgreSQL database, set DB_INSTALL_ROLES=true so the migrations create those roles.
  • With a Supabase Postgres database, which already has those roles, leave DB_INSTALL_ROLES unset.

Requests are authorized with JSON Web Tokens signed with the secret in AUTH_JWT_SECRET, which must be at least 32 characters long. The examples below generate a random secret and keep it in the JWT_SECRET shell variable:

JWT_SECRET=$(openssl rand -hex 32)

Getting Started

The following runs Storage against a PostgreSQL container on a shared Docker network, with objects on the local file backend. First create the network and start PostgreSQL:

docker network create storage
docker run -d --name storage-db --network storage \
  -e POSTGRES_PASSWORD=postgres \
  cgr.dev/ORGANIZATION/postgres:latest
until docker exec storage-db pg_isready -h 127.0.0.1 -U postgres; do sleep 1; done

Then start Storage. DB_INSTALL_ROLES=true creates the Supabase roles in this plain PostgreSQL database:

docker run -d --name storage-api --network storage -p 5000:5000 \
  -e DATABASE_URL=postgresql://postgres:postgres@storage-db:5432/postgres \
  -e DB_INSTALL_ROLES=true \
  -e AUTH_JWT_SECRET="$JWT_SECRET" \
  -e TENANT_ID=stub \
  -e REGION=local \
  -e STORAGE_BACKEND=file \
  -e STORAGE_FILE_BACKEND_PATH=/tmp/storage \
  -e GLOBAL_S3_BUCKET=stub \
  cgr.dev/ORGANIZATION/supabase-storage-api-fips

Check that the server is up:

curl -sf --retry 30 --retry-delay 2 --retry-all-errors http://localhost:5000/status

Mint a short-lived service_role token signed with the same secret, using the Node.js runtime in the image:

SERVICE_KEY=$(docker run --rm -e JWT_SECRET="$JWT_SECRET" \
  cgr.dev/ORGANIZATION/supabase-storage-api-fips node -e '
    const crypto = require("crypto");
    const b64 = (o) => Buffer.from(JSON.stringify(o)).toString("base64url");
    const t = b64({ alg: "HS256", typ: "JWT" }) + "." +
      b64({ role: "service_role", exp: Math.floor(Date.now() / 1000) + 3600 });
    console.log(t + "." + crypto.createHmac("sha256", process.env.JWT_SECRET).update(t).digest("base64url"));
  ')

Create a bucket, upload an object, and read it back:

curl -sf -X POST http://localhost:5000/bucket \
  -H "Authorization: Bearer $SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "avatars"}'
curl -sf -X POST http://localhost:5000/object/avatars/hello.txt \
  -H "Authorization: Bearer $SERVICE_KEY" \
  -H "Content-Type: text/plain" \
  --data 'hello from storage'
curl -sf http://localhost:5000/object/authenticated/avatars/hello.txt \
  -H "Authorization: Bearer $SERVICE_KEY"

The last command prints the object:

hello from storage

Kubernetes

No official standalone Helm chart exists for Supabase Storage, so the following uses plain manifests. It assumes a PostgreSQL Service named postgres in the same namespace, with the password postgres, and the JWT_SECRET variable from Prerequisites. Store the configuration in a Secret:

kubectl create secret generic storage-api \
  --from-literal=DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres \
  --from-literal=DB_INSTALL_ROLES=true \
  --from-literal=AUTH_JWT_SECRET="$JWT_SECRET" \
  --from-literal=TENANT_ID=stub \
  --from-literal=REGION=local \
  --from-literal=STORAGE_BACKEND=file \
  --from-literal=STORAGE_FILE_BACKEND_PATH=/var/lib/storage \
  --from-literal=GLOBAL_S3_BUCKET=stub

Create a Deployment and a Service. The emptyDir volume holds the file backend and is writable by the nonroot user; use a PersistentVolumeClaim to keep objects across restarts:

cat > storage-api.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: storage-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: storage-api
  template:
    metadata:
      labels:
        app: storage-api
    spec:
      containers:
        - name: storage-api
          image: cgr.dev/ORGANIZATION/supabase-storage-api-fips
          ports:
            - containerPort: 5000
          envFrom:
            - secretRef:
                name: storage-api
          readinessProbe:
            httpGet:
              path: /status
              port: 5000
          volumeMounts:
            - name: data
              mountPath: /var/lib/storage
      volumes:
        - name: data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: storage-api
spec:
  selector:
    app: storage-api
  ports:
    - port: 5000
      targetPort: 5000
EOF
kubectl apply -f storage-api.yaml
kubectl rollout status deployment/storage-api

Forward the Service port in a separate terminal, then repeat the token and object commands from Getting Started against http://localhost:5000:

kubectl port-forward service/storage-api 5000:5000

Configuration

Supabase Storage is configured through environment variables. Refer to the upstream .env.sample for the full list of supported settings.

To keep objects in an S3-compatible object store instead of the local filesystem, switch the backend to s3, name an existing bucket, and pass the store's endpoint and credentials. For example, for a MinIO server reachable as minio on the storage network, replace the Storage container from Getting Started with:

docker run -d --name storage-api --network storage -p 5000:5000 \
  -e DATABASE_URL=postgresql://postgres:postgres@storage-db:5432/postgres \
  -e DB_INSTALL_ROLES=true \
  -e AUTH_JWT_SECRET="$JWT_SECRET" \
  -e TENANT_ID=stub \
  -e REGION=local \
  -e STORAGE_BACKEND=s3 \
  -e GLOBAL_S3_BUCKET=supabase-storage \
  -e STORAGE_S3_ENDPOINT=http://minio:9000 \
  -e STORAGE_S3_FORCE_PATH_STYLE=true \
  -e STORAGE_S3_REGION=us-east-1 \
  -e AWS_ACCESS_KEY_ID="$S3_ACCESS_KEY" \
  -e AWS_SECRET_ACCESS_KEY="$S3_SECRET_KEY" \
  cgr.dev/ORGANIZATION/supabase-storage-api-fips

Set S3_ACCESS_KEY and S3_SECRET_KEY to the store's credentials first. Storage writes each object to the bucket under <tenant>/<bucket>/<object>/<version>. Most self-hosted stores, such as MinIO, need path-style addressing.

Documentation and Resources

What are Chainguard Containers?

Chainguard's free tier of Starter container images are built with Wolfi, our minimal Linux undistro.

All other Chainguard Containers are built with Chainguard OS, Chainguard's minimal Linux operating system designed to produce container images that meet the requirements of a more secure software supply chain.

The main features of Chainguard Containers include:

For cases where you need container images with shells and package managers to build or debug, most Chainguard Containers come paired with a development, or -dev, variant.

In all other cases, including Chainguard Containers tagged as :latest or with a specific version number, the container images include only an open-source application and its runtime dependencies. These minimal container images typically do not contain a shell or package manager.

Although the -dev container image variants have similar security features as their more minimal versions, they include additional software that is typically not necessary in production environments. We recommend using multi-stage builds to copy artifacts from the -dev variant into a more minimal production image.

Need additional packages?

To improve security, Chainguard Containers include only essential dependencies. Need more packages? Chainguard customers can use Custom Assembly to add packages, either through the Console, chainctl, or API.

To use Custom Assembly in the Chainguard Console: navigate to the image you'd like to customize in your Organization's list of images, and click on the Customize image button at the top of the page.

Learn More

Refer to our Chainguard Containers documentation on Chainguard Academy. Chainguard also offers VMs and Libraries — contact us for access.

Trademarks

This software listing is packaged by Chainguard. The trademarks set forth in this offering are owned by their respective companies, and use of them does not imply any affiliation, sponsorship, or endorsement by such companies.

Licenses

Chainguard's container images contain software packages that are direct or transitive dependencies. The following licenses were found in the "latest" tag of this image:

  • Apache-2.0

  • ISC

  • MIT

For a complete list of licenses, please refer to this Image's SBOM.

Software license agreement

Compliance

Chainguard Containers are SLSA Level 3 compliant with detailed metadata and documentation about how it was built. We generate build provenance and a Software Bill of Materials (SBOM) for each release, with complete visibility into the software supply chain.

SLSA compliance at Chainguard

This image helps reduce time and effort in establishing PCI DSS 4.0 compliance with low-to-no CVEs.

PCI DSS at Chainguard

This is a FIPS validated image for FedRAMP compliance.

This image is STIG hardened and scanned against the DISA General Purpose Operating System SRG with reports available.

Learn more about STIGsGet started with STIGs

Related images
supabase-storage-api logo

supabase-storage-api


Category
FIPS
STIG

The trusted source for open source

Talk to an expert
PrivacyTerms

Product

Chainguard ContainersChainguard LibrariesChainguard VMsChainguard OS PackagesChainguard ActionsChainguard Agent SkillsIntegrationsPricing
© 2026 Chainguard, Inc. All Rights Reserved.
Chainguard® and the Chainguard logo are registered trademarks of Chainguard, Inc. in the United States and/or other countries.
The other respective trademarks mentioned on this page are owned by the respective companies and use of them does not imply any affiliation or endorsement.