Saif Ali

May 16, 2026 • 6 min read

Persistent storage in NEXUS AI: volumes and S3-compatible buckets for deployed apps

Persistent storage in NEXUS AI: volumes and S3-compatible buckets for deployed apps

Published: May 9, 2026
Category: Storage · DevOps
Reading time: 11 minutes
Author: NEXUS AI Team


Most application deployment platforms make stateless services easy and stateful workloads awkward. That works until your app needs user uploads, generated reports, SQLite files, model artifacts, or a shared object store for background jobs.

NEXUS AI now includes two storage primitives for deployed applications:

  • Volumes for persistent filesystem mounts.

  • Buckets for S3-compatible object storage.

Both are organization-scoped. Both can be attached to deployments. Both survive container restarts, redeploys, and deployment deletion until you explicitly delete the storage resource.

This post explains when to use each one, how to attach them, how they behave during scaling, and the exact CLI commands to run.


Two storage primitives

Volumes and buckets solve different problems.

FeatureBest forAccess patternAttachment modelVolumesFilesystem state, SQLite, local uploads, persistent cachesApp reads and writes a mounted path like /dataSingle deploymentBucketsUser media, reports, generated assets, object storageApp uses S3-compatible SDK callsMultiple deployments

Use a volume when your app expects a local filesystem path.

Use a bucket when your app can store data as objects by key using an S3 SDK.


Volumes: persistent filesystem mounts

A NEXUS AI volume is a persistent filesystem mount backed by a Docker named volume. Your app writes to a path, usually /data, and the data remains available after redeploys or container replacement.

Create a volume:

bash

Copy

nexus volume create app-data --display-name "App data"

Deploy your app:

bash

Copy

nexus deploy source \
 --repo https://github.com/your-org/your-app \
 --name myapp \
 --provider docker \
 --wait

Attach the volume:

bash

Copy

nexus volume attach <volume-id> <deployment-id> --mount /data

Redeploy so the new container starts with the mount:

bash

Copy

nexus deploy redeploy <deployment-id> --wait

That redeploy step matters. Docker mounts are applied when a container is created. You cannot add a volume mount to an already-running container.

Verify the mount:

bash

Copy

docker exec <container-id> ls -la /data

When to use volumes

Volumes are a good fit for:

  • SQLite databases for small apps.

  • User uploads written through the filesystem.

  • Persistent cache directories.

  • Generated files that your app expects to read from disk.

  • Model files or local indexes that survive redeploys.

Volumes are not a good fit for every scaled workload. If multiple app replicas write the same file at the same time, your application needs to handle locking or coordination.

Think of a NEXUS AI volume like a shared network drive. It persists, but it does not magically make unsafe concurrent writes safe.


How volumes behave when you scale

When you scale a deployment up, every replica mounts the same named volume at the same path.

bash

Copy

nexus deploy scale <deployment-id> 3

All three replicas now see the same files under /data.

That is useful for read-mostly data, shared assets, or workloads with explicit locking. It is risky for apps that assume a single writer, such as a default SQLite setup with multiple write-heavy replicas.

When you scale down:

bash

Copy

nexus deploy scale <deployment-id> 1

The removed containers detach. The volume remains. The remaining replica keeps using the same data.

When you delete the deployment, the volume still survives. You must explicitly detach and delete the volume if you want to destroy the data.

bash

Copy

nexus volume detach <volume-id>
nexus volume delete <volume-id> --yes

Buckets: S3-compatible object storage

A NEXUS AI bucket is S3-compatible object storage backed by MinIO. Your app interacts with it using standard AWS SDKs, boto3, or any S3-compatible client.

Create a bucket:

bash

Copy

nexus bucket create user-uploads --display-name "User uploads"

Attach it to a deployment:

bash

Copy

nexus bucket attach <bucket-id> <deployment-id>
nexus deploy redeploy <deployment-id> --wait

After redeploy, NEXUS AI injects S3 environment variables into the app container:

bash

Copy

S3_ENDPOINT=http://host.docker.internal:9000
S3_REGION=us-east-1
S3_BUCKET=org-<orgIdShort>-<bucketName>
S3_ACCESS_KEY=<scoped access key>
S3_SECRET_KEY=<scoped secret key>

If multiple buckets are attached, NEXUS AI also injects per-bucket aliases:

bash

Copy

S3_BUCKET_USER_UPLOADS=org-...-user-uploads
S3_BUCKET_USER_UPLOADS_ACCESS_KEY=...
S3_BUCKET_USER_UPLOADS_SECRET_KEY=...

Example: write files to a bucket from Python

Your app can use boto3 with the injected environment variables:

python

Copy

import os
import boto3

s3 = boto3.client(
 "s3",
 endpoint_url=os.environ["S3_ENDPOINT"],
 aws_access_key_id=os.environ["S3_ACCESS_KEY"],
 aws_secret_access_key=os.environ["S3_SECRET_KEY"],
 region_name=os.environ["S3_REGION"],
)

s3.put_object(
 Bucket=os.environ["S3_BUCKET"],
 Key="uploads/hello.txt",
 Body=b"hello from NEXUS AI",
)

That same pattern works with the AWS SDK for JavaScript, Go, Java, Ruby, PHP, and other S3-compatible clients.


Bucket file operations from the CLI

List files:

bash

Copy

nexus bucket files <bucket-id>
nexus bucket files <bucket-id> --prefix uploads/

Upload a file:

bash

Copy

nexus bucket upload <bucket-id> ./report.pdf --key reports/report.pdf

Download a file:

bash

Copy

nexus bucket download <bucket-id> reports/report.pdf --out ./report.pdf

Generate a short-lived signed download URL:

bash

Copy

nexus bucket download <bucket-id> reports/report.pdf --share --ttl 900

Delete a file:

bash

Copy

nexus bucket rm <bucket-id> reports/report.pdf --yes

The CLI upload path streams from disk, which is better for large files than browser uploads.


Scoped credentials and rotation

Each bucket gets scoped S3 credentials. A deployment attached to one bucket does not automatically get access to every bucket in the organization.

Reveal credentials for external clients:

bash

Copy

nexus bucket credentials <bucket-id>

Rotate credentials:

bash

Copy

nexus bucket rotate-credentials <bucket-id> --yes
nexus deploy redeploy <deployment-id> --wait

Redeploy is required because the running container already has the old environment variables. The next container start receives the new S3_ACCESS_KEY and S3_SECRET_KEY.


Buckets and scaling

Buckets are usually a better fit for scaled apps than shared filesystem volumes.

When you scale up, every replica receives the same S3 environment variables. Each replica makes independent S3 API calls. The object storage layer handles concurrent requests.

bash

Copy

nexus deploy scale <deployment-id> 3

For object keys, the normal S3 rule applies: if two replicas write the same key, the last write wins. Use unique keys when each replica should produce independent output.

When you scale down, nothing needs to detach. The removed container stops making S3 calls. The bucket and objects remain.


REST API reference

Volumes:

text

Copy

GET /api/volumes
POST /api/volumes
POST /api/volumes/:id/attach
POST /api/volumes/:id/detach
POST /api/volumes/:id/refresh-usage
DELETE /api/volumes/:id

Buckets:

text

Copy

GET /api/buckets
POST /api/buckets
POST /api/buckets/:id/attach
POST /api/buckets/:id/detach
POST /api/buckets/:id/refresh-usage
POST /api/buckets/:id/rotate-credentials
GET /api/buckets/:id/credentials
GET /api/buckets/:id/files
PUT /api/buckets/:id/files/:key
GET /api/buckets/:id/files/:key/download
POST /api/buckets/:id/files/:key/download-url
DELETE /api/buckets/:id/files/:key
GET /api/bucket-downloads/:token

Example volume attach request:

bash

Copy

curl -s -X POST "$NEXUS_API_BASE/volumes/<volume-id>/attach" \
 -H "Authorization: Bearer $NEXUS_JWT" \
 -H "Content-Type: application/json" \
 -d '{"deploymentId":"<deployment-id>","mountPath":"/data"}'

Example bucket attach request:

bash

Copy

curl -s -X POST "$NEXUS_API_BASE/buckets/<bucket-id>/attach" \
 -H "Authorization: Bearer $NEXUS_JWT" \
 -H "Content-Type: application/json" \
 -d '{"deploymentId":"<deployment-id>"}'

MCP tools

AI clients can operate storage through MCP tools with scoped permissions.

Volume tools:

text

Copy

nexusai_volume_list
nexusai_volume_create
nexusai_volume_attach
nexusai_volume_detach
nexusai_volume_delete

Bucket tools:

text

Copy

nexusai_bucket_list
nexusai_bucket_create
nexusai_bucket_attach
nexusai_bucket_detach
nexusai_bucket_rotate_credentials
nexusai_bucket_files_list
nexusai_bucket_file_download
nexusai_bucket_file_delete
nexusai_bucket_delete

Use MCP for operational flows like “create a bucket for this deployment, attach it, then remind me to redeploy,” while keeping destructive operations behind confirmation.


Which one should you choose?

Choose a volume when:

  • Your app requires a filesystem path.

  • You need persistent local files.

  • You are running a single replica or have safe file-locking behavior.

  • You want simple persistence for Docker-based deployments.

Choose a bucket when:

  • Your app stores uploads or generated files.

  • Multiple deployments need access to the same storage.

  • The app may scale to multiple replicas.

  • You want S3-compatible tooling and signed URLs.

For most user-upload and generated-asset workflows, buckets are the better long-term default. For apps that truly expect local disk, volumes are the practical answer.


Final takeaway

NEXUS AI now gives deployed apps durable storage without forcing every app into the same model.

Volumes give you persistent filesystem mounts. Buckets give you S3-compatible object storage with scoped credentials, file operations, signed URLs, and multi-deployment attachment.

The rule is simple: use volumes for local filesystem state, and use buckets for object storage. Then redeploy after attaching so your containers receive the mount or environment variables they need.

Join Saif on Peerlist!

Join amazing folks like Saif and thousands of other builders on Peerlist.

peerlist.io/

It’s available... this username is available! 😃

Claim your username before it's too late!

This username is already taken, you’re a little late.😐

0

0

0