Some data does not belong in a block volume. User uploads, build artifacts, generated reports, and data exports grow without a clear ceiling, and they rarely need the low latency of an attached disk. Point the application at an S3-compatible endpoint instead. The app writes objects to a bucket, the block volume stays small and fast, and the large blobs live in object storage that any node can reach. This is also the recommended way for pods across different nodes to share data, ahead of a shared filesystem: any node reaches the same bucket, with no ReadWriteMany volume to run. An app needs four things to talk to S3: the endpoint, a region, the `forcePathStyle` setting, and credentials. Run [SeaweedFS](/docs/hetzner/apalla/storage/object/run-seaweedfs) for the endpoint, or use any S3-compatible bucket. ## Endpoint, region, and path style The endpoint is the address of the S3 gateway. The in-cluster SeaweedFS endpoint uses path-style addressing, where the bucket name sits in the URL path rather than in the hostname, so set `forcePathStyle` (some SDKs call it `s3ForcePathStyle`) to `true`. The region is often required by the SDK even for a self-hosted store; a placeholder like `us-east-1` is fine when the store does not use regions. ## Mount credentials from a Secret Keep the access key ID and secret in a Kubernetes Secret and pass them into the pod as environment variables. Never bake them into the image or the Deployment manifest: ```yaml title="s3-workload.yaml" apiVersion: apps/v1 kind: Deployment metadata: name: uploader spec: replicas: 1 selector: matchLabels: app: uploader template: metadata: labels: app: uploader spec: containers: - name: uploader image: your-app:latest env: - name: S3_ENDPOINT value: http://seaweedfs-s3.seaweedfs.svc.cluster.local:8333 - name: S3_REGION value: us-east-1 - name: S3_FORCE_PATH_STYLE value: "true" - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: s3-credentials key: AWS_ACCESS_KEY_ID - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: s3-credentials key: AWS_SECRET_ACCESS_KEY ``` The app then reads these variables and builds an S3 client from them. ## Three ways to reach the objects How the app talks to the bucket depends on what it does. Expand the one that matches your workload: An S3 SDK is the direct route. The application code calls `putObject` and `getObject` against the endpoint. This is the right choice for a service that manages its own objects. [`s3fs`](https://github.com/s3fs-fuse/s3fs-fuse) mounts a bucket as a directory, so an app that only knows how to read and write files can use object storage without code changes. It is convenient, but it is not a real filesystem: locking, renames, and random writes are slow or unsupported. Use it for simple read or append patterns, not for a database. Pre-signed URLs let a client upload or download directly from the bucket without your service handling the bytes. Your app signs a time-limited URL and hands it to the browser or client. This keeps large transfers off your pods entirely. ## When object storage replaces a PVC Reach for object storage over a PVC when the data is large, append-mostly, and read by many. A photo service storing user uploads, a CI system keeping build artifacts, a reporting job writing nightly exports: all of these fit object storage better than a growing block volume. Keep the PVC for data the application needs at low latency and treats as a live working set, like a database's files. > [!TIP] > Moving large blobs to object storage keeps your block volumes small. Small volumes attach faster, back up faster, and cost less. Store the metadata (which object, who owns it, when it was written) in your database, and store the blob itself in the bucket. ## Portability The S3 API is the same wherever the store runs. Code written against your in-cluster SeaweedFS talks to any other S3-compatible store by changing the endpoint and credentials. That keeps you from being locked to one provider: you can move the store, or move to a hosted bucket, without rewriting the application. Next, stand up the store these workloads talk to in [Run SeaweedFS](/docs/hetzner/apalla/storage/object/run-seaweedfs), or push backups to a bucket outside the cluster with [Offsite and immutable backups](/docs/hetzner/apalla/storage/backup/offsite-and-immutable-backups).