Disk storage provider
Local filesystem provider implementing storage.ProviderPort against a sandboxed directory on disk.
Overview
The disk provider stores objects directly on the local filesystem under a configured base directory. Path resolution goes through Piko's safedisk sandbox. User-supplied keys cannot escape the configured root via traversal (../), absolute paths, or symlink games. The sandbox rejects them at the boundary. The sandbox builds on Go's os.Root. That uses kernel-level path resolution (openat2 with RESOLVE_BENEATH on Linux) to guard against symlink escapes and time-of-check-to-time-of-use races, not just static prefix checks.
Atomic writes use the temp-file-and-rename pattern, so a crash mid-write leaves either the old object or no object, never a partial one. Each Put fsyncs the temporary data file before the rename and fsyncs the parent directory after. A successful return survives a crash even on journaling filesystems, where the metadata journal flushes independently of file data.
Disk's character is simple, fast, single-node. It fits local development, single-node deployments, and any workload whose data fits on a single attached volume and where per-request object-storage costs would otherwise dominate. The disk provider does not synchronise across replicas. Every node has its own filesystem. It is not suitable for horizontally scaled production deployments unless paired with a shared network filesystem like NFS or EFS. Even then a cloud object store (S3, GCS, R2) is usually a better fit.
Requirements
- A writable directory at
BaseDirectorywith enough free space for your workload. - The process user must own or have read/write permission on the base directory.
Configuration
import "piko.sh/piko/wdk/storage/storage_provider_disk"
provider, err := storage_provider_disk.NewDiskProvider(storage_provider_disk.Config{
BaseDirectory: "/var/lib/myapp/storage", // required; root of the sandboxed area
})
if err != nil {
return err
}
NewDiskProvider returns a storage.ProviderPort, the same port type every storage provider returns. There is no context argument, no migration step, and no code generation. One call produces a port that registers directly through piko.WithStorageProvider. BaseDirectory is the only required field. The constructor fails at boot when the base directory is missing and the config supplies no sandbox, so a misconfigured path surfaces before the first request.
Pass storage.ProviderOption values as variadic arguments after the config for rate-limiter overrides.
For advanced containment or testing, supply your own sandbox through Config.Sandbox, or a Config.SandboxFactory to build one. The constructor prefers either over building a default sandbox from BaseDirectory, so the call site stays the same. safedisk is a reusable Piko primitive, not specific to storage.
Bootstrap
ssr := piko.New(
piko.WithStorageProvider("disk", provider),
piko.WithDefaultStorageProvider("disk"),
)
Presigned URLs and multipart
The disk provider does not generate native presigned URLs or support native multipart uploads. The storage service layer fills the gap. When a caller requests a presigned upload or download URL, the service substitutes an HMAC-signed token paired with an HTTP endpoint instead of delegating to the provider. The behaviour differs from S3, GCS, or R2, which sign URLs the object store serves directly.
For a frontend on a different host or port, set the presign base URL so the generated URLs resolve cross-origin.
ssr := piko.New(
piko.WithStorageProvider("disk", provider),
piko.WithDefaultStorageProvider("disk"),
piko.WithStoragePresignBaseURL("https://cms.example.com"),
)
Health checks
The readiness probe reports the provider degraded when free space under the base directory drops below 1024 MB. A single-node operator watching readiness probes should account for this floor when sizing the volume.
See also
Other storage providers:
- Amazon S3, the gold standard for production object storage.
- Google Cloud Storage, GCP-native object storage with strong consistency.
- Cloudflare R2, S3-compatible storage with zero egress fees.
Storage transformers:
- Crypto transformer, at-rest encryption applied before write.
- Gzip transformer, gzip compression in the storage pipeline.
- Zstd transformer, Zstandard compression in the storage pipeline.
Framework docs:
- How to handle file storage, wiring storage providers, repositories, and transformers end-to-end.
- Storage API reference, every type and method on the storage service.