AnnouncementsIntroducing Arkoren

    Menu

    Drivers

    Choose and configure memory, filesystem, or S3 storage with explicit lifecycle, ownership, and backend limits.

    What's on this page
    1. Compare process-local memory, persistent filesystem roots, and shared S3 storage.
    2. Configure each driver and retain ownership of lifecycle and native dependencies.
    3. Account for filesystem containment, permissions, replacement, and staging cleanup.
    4. Use S3 prefixes, compatible endpoints, native access, and a disposable local fixture.
    On this page

    Choose storage by where your application runs and which processes must share its files. Memory suits isolated tests; filesystem storage suits one host with a persistent volume; S3 suits replicas that need the same objects and clients that transfer directly through signed URLs.

    All three use the same object operations and portable key rules. The facade borrows its driver: storage.New neither starts nor stops it. Examples import storage from arkoren.dev/core/service/storage and use a non-nil operation context ctx. Run installation commands in your application’s Go module, and check constructor errors before wrapping a driver.

    Use memory for isolated tests, then run integration tests against the driver you deploy; passing memory tests does not establish provider permissions or limits.

    Compatibility Matrix

    This compares behavior exposed by Arkoren, not every native provider feature. Download ranges refer to the application-served Download and Inline helpers.

    Driver Shared Across Hosts Survives App Restart Seekable Reads Download Ranges Signed Downloads Signed Uploads
    Memory No No Yes Yes No No
    Filesystem Yes No No
    Amazon S3 Yes Yes No No
    S3-Compatible Yes No No

    Shared access assumes the same bucket/prefix or root and appropriate permissions. Restart support does not promise backups, retention, or recovery from backend failure. An ephemeral container directory is not persistent storage. S3-compatible services use the S3 driver with a custom endpoint.

    Every driver supports paged listing and the facade’s lazy traversal. Neither provides a multi-page snapshot. Stopping a walk prevents later facade requests, not work already done preparing a page.

    Memory

    The bundled memory driver stores objects only in this process. A new driver starts empty, needs no lifecycle hooks, and is safe for concurrent use:

    import "arkoren.dev/core/service/storage/memorystorage"
    
    files := storage.New(memorystorage.New())

    Use the shared write and read tasks with this store. Bytes are copied into storage and into opened snapshots; later writes or deletions do not change an already opened body. Bodies support seeking and HTTP ranges, but objects consume application memory without a total-size quota. Signing returns storage.ErrUnsupported.

    If housekeeping needs a different default page size, configure the driver at construction:

    driver := memorystorage.New(func(store *memorystorage.Builder) {
    	store.ListLimit(500)
    })

    The default is 100; nonpositive configured values restore it. An explicit positive ListOptions.Limit overrides it for that request.

    See the memorystorage API.

    Filesystem

    Choose filesystem storage when one host owns a persistent directory. The application’s operating-system identity must be able to create and access that directory. Keep the root application-controlled and separate from unrelated data; portable key validation is not a substitute for filesystem permissions.

    go get arkoren.dev/driver/filesystem

    Import filesystemstorage from arkoren.dev/driver/filesystem/filesystemstorage. Inside an existing compose *application.Composer (from arkoren.dev/core/application), construct the driver and register its lifecycle:

    driver := compose.Must(filesystemstorage.New("./data/uploads"))
    compose.Cleanup(driver.Stop)
    compose.Startup(driver.Start)
    files := storage.New(driver)

    New rejects an empty root and resolves an absolute path without disk I/O. Start creates the directory if absent and opens it with os.Root; a nondirectory root fails startup. Operations before Start or after Stop fail. There are no optional construction settings. Follow Lifecycle and Ownership for shutdown ordering: finish active work before Stop, which closes the root without joining readers or writers. Already opened bodies remain yours to close.

    Stored Files

    After startup, the shared write task stores reports/2026-q1.csv at <root>/reports/2026-q1.csv; intermediate directories are created as needed. New directories request mode 0750, subject to umask, and stored files are set to 0640 where the platform supports Unix modes. Existing directory permissions are not repaired. Restrict root ownership and access explicitly in your deployment.

    Replacement writes a temporary file beside the destination, syncs and closes it, then renames it into place. Atomic replacement depends on the volume’s rename semantics; it does not promise crash durability on every filesystem. A later metadata failure can report an error after replacement succeeded.

    New staging names start with .storage- and end in a dot, outside portable key syntax, so listings omit unpublished bytes. A valid name such as .storage-report is an ordinary object. Never remove files by prefix alone: stop writers, inspect ownership and leftovers, and remove only confirmed staging files. Back up the root before native migrations or cleanup.

    Path Containment

    Operations through os.Root cannot traverse symbolic links outside the root; such attempts fail with an operational error, not storage.ErrNotFound. Internal symlinks can be followed for object operations. Listings skip symlinks encountered during walking, including links that point inside the root; a listing prefix that traverses an outside link fails.

    Open and Stat accept regular files only. Directories are missing logical objects; FIFOs, sockets, and other special entries report operational errors. On Unix, FIFO opens do not wait for a writer. Regular bodies support seeking and download ranges.

    Listings walk the directory implied by the prefix, sort matching portable keys, and default to 100 objects per page. Native regular files with portable names can appear as objects; invalid names and special entries are omitted.

    Shared network volumes can have different rename behavior and competing writers can overwrite one another. Prefer S3 for replicas on separate hosts; do not infer shared-write safety from a successful local filesystem test.

    No emulator is needed locally. Write and read a test object, restart with the same persistent root to verify persistence, then delete only that test key. Stopping the driver does not remove stored data.

    See the filesystemstorage API.

    Amazon S3

    Use a pre-existing bucket and grant only needed actions: s3:ListBucket on the bucket, and s3:GetObject, s3:PutObject, s3:DeleteObject on allowed objects. Put also reads metadata after uploading, so it needs read permission.

    go get arkoren.dev/driver/aws

    Import awsstorage from arkoren.dev/driver/aws/awsstorage. Configure a region and optional namespace while constructing the driver:

    driver, err := awsstorage.New(ctx, "private-documents", func(store *awsstorage.Builder) {
    	store.Region("eu-west-1")
    	store.Prefix("reports")
    })

    After checking the error, wrap it with files := storage.New(driver). New validates the bucket and prefix and loads AWS configuration; loading can read local configuration or consult configured credential services. Object requests begin with operations, not construction. No Start or Stop is required, and construction does not verify bucket access.

    Credentials and an omitted region use the AWS default configuration chain, including environment, shared files, and workload roles. Builder.Credentials accepts a shared aws.CredentialsProvider when that chain is unsuitable.

    The prefix defaults empty; a configured reports becomes reports/ before logical keys. A logical annual.pdf addresses reports/annual.pdf in the bucket, including for signing. Prefixes separate names, not permissions: enforce access in application authorization and bucket policy.

    Transfer Limits

    Use the shared write and read tasks. Put streams from a seekable source but buffers a non-seekable reader fully in memory before upload. Bound source sizes; choose a seekable file or direct signed upload for large transfers.

    An upload is followed by a metadata request. If that request fails, the new object can already exist despite the returned error; inspect with Stat before deciding recovery. No facade write event is emitted for a failed call.

    Open returns a non-seekable response body. App-served downloads stream the whole object without ranges or conditional handling; a signed GET lets S3 serve the client directly. Signing supports GET and PUT, at most seven days, and temporary credentials can shorten that validity. See Signed URLs for bindings and authorization.

    List maps positive limits to MaxKeys; S3 returns at most 1,000 keys per page. Nonportable native keys are omitted. Continuation checks can read ahead through physical pages, adding requests while preserving native opaque cursor strings. The empty cursor means no further matching portable object was found.

    S3-Compatible

    To use another S3 implementation, set its endpoint. This also enables path-style addressing. For the disposable local server below:

    driver, err := awsstorage.New(ctx, "dev", func(store *awsstorage.Builder) {
    	store.Endpoint("http://localhost:9001")
    	store.Region("us-east-1")
    })

    Supply that service’s credentials through the default chain or Builder.Credentials. Check its persistence, listing, SigV4, and signed-header enforcement rather than assuming all S3 behavior. A successful emulator test does not establish cloud IAM permissions or durability.

    See the awsstorage API.

    Native Access

    If SDK retry, HTTP timeout, or middleware settings need customization, configure a native client first. With awsconfig imported from github.com/aws/aws-sdk-go-v2/config and s3 from github.com/aws/aws-sdk-go-v2/service/s3:

    settings, err := awsconfig.LoadDefaultConfig(ctx)
    if err != nil {
    	return err
    }
    client := s3.NewFromConfig(settings)
    driver, err := awsstorage.NewFrom(client, "private-documents", func(store *awsstorage.Builder) {
    	store.Prefix("reports")
    })

    Only Prefix applies to NewFrom; region, endpoint, and credential settings already belong to the client. The driver neither copies nor closes it. Arkoren adds no timeout or retry policy over SDK settings; use operation deadlines and configure native policies before sharing the client.

    For an operation outside the facade, recover the concrete driver with files.DriverAs[*awsstorage.Driver](). Both this result and files.Driver() are shared, not copied. With your existing bucket name bucket:

    if driver, ok := files.DriverAs[*awsstorage.Driver](); ok {
    	if client := driver.Client(); client != nil {
    		_, err := client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket})
    		if err != nil {
    			return err
    		}
    	}
    }

    Client() is nil for non-native injected clients. A successful bucket check does not verify every object permission. Native operations bypass facade validation, prefix mapping, events, and telemetry. Large non-seekable streams can use this client with the AWS upload manager instead of unbounded Put.

    A native client supplied to NewFrom must provide presigning to sign URLs. For bound uploads, its presigner must report the exact requested content type as the sole signed Content-Type value; missing or changed bindings return storage.ErrUnsupported, not a weaker URL. Arkoren does not rewrite borrowed middleware. See upload bindings.

    The reporting-only awsstorage.Config redacts credential-provider details, not endpoint or prefix text. Native diagnostics, middleware, and provider errors are not sanitized by facade telemetry; keep secrets out of those settings and review native logging separately.

    Local S3

    For disposable local storage, install Docker with Compose/BuildKit and the AWS CLI. The original pinned minio/minio image is no longer served by Docker Hub. Build this release from its official artifacts, not an unverified mirror. This fixture supports Linux amd64/arm64 containers, not a production MinIO deployment.

    Save this Dockerfile in a new empty development directory. Keep the pinned base and binary checksums; a mismatch or unsupported architecture must fail:

    FROM alpine:3.22.7@sha256:2e0b81d60597f449baa56dc25d355367869d2dca4cd57e866f1e2b52af3eb55f
    ARG TARGETARCH
    RUN set -eu; \
        case "${TARGETARCH}" in \
            amd64) checksum=7c5bd8512c6e966455b1d198209358b2d191c77a83ab377c4073281065fb855f ;; \
            arm64) checksum=5c83cd2cf151717ba0243f73e1c7802ff36e272b67144bdd7f1f7d684fd6f03d ;; \
            *) printf 'Unsupported MinIO fixture architecture: %s\n' "${TARGETARCH}" >&2; exit 1 ;; \
        esac; \
        release=RELEASE.2025-09-07T16-13-09Z; \
        wget -q -O /usr/local/bin/minio \
            "https://github.com/minio/minio/releases/download/${release}/minio.linux-${TARGETARCH}.${release}"; \
        printf '%s  /usr/local/bin/minio\n' "${checksum}" | sha256sum -c -; \
        chmod 0755 /usr/local/bin/minio
    ENTRYPOINT ["minio"]

    Beside it, save compose.yaml, not over an existing application configuration. These public credentials are for disposable local data only; retain loopback binding:

    services:
      storage:
        build: .
        command: server /data
        environment:
          MINIO_ROOT_USER: localstorage
          MINIO_ROOT_PASSWORD: localstorage-password
        ports:
          - "127.0.0.1:9001:9000"

    Start the service and configure the local client’s credentials:

    docker compose up -d --build
    export AWS_ACCESS_KEY_ID=localstorage
    export AWS_SECRET_ACCESS_KEY=localstorage-password
    export AWS_DEFAULT_REGION=us-east-1

    After the endpoint is ready, create the bucket; reuse it if it already exists:

    aws --endpoint-url http://localhost:9001 s3 mb s3://dev

    Use the S3-compatible constructor, write and read a small test object through the facade, then inspect it with aws --endpoint-url http://localhost:9001 s3 ls s3://dev. The signing endpoint must also be reachable by the client using the URL.

    Run docker compose down when finished. This fixture does not preserve objects after its container is removed.

    For facade operations, see the storage API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive