DEV Community

Cover image for Persistent storage in NEXUS AI
Saif Ali
Saif Ali

Posted on • Originally published at nexusai.run on

Persistent storage in NEXUS AI

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.

Feature Best for Access pattern Attachment model
Volumes Filesystem state, SQLite, local uploads, persistent caches App reads and writes a mounted path like /data Single deployment
Buckets User media, reports, generated assets, object storage App uses S3-compatible SDK calls Multiple 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:

nexus volume create app-data --display-name "App data"
Enter fullscreen mode Exit fullscreen mode

Deploy your app:

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

Attach the volume:

nexus volume attach <volume-id> <deployment-id> --mount /data
Enter fullscreen mode Exit fullscreen mode

Redeploy so the new container starts with the mount:

nexus deploy redeploy <deployment-id> --wait
Enter fullscreen mode Exit fullscreen mode

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:

docker exec <container-id> ls -la /data
Enter fullscreen mode Exit fullscreen mode

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.

nexus deploy scale <deployment-id> 3
Enter fullscreen mode Exit fullscreen mode

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:

nexus deploy scale <deployment-id> 1
Enter fullscreen mode Exit fullscreen mode

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.

nexus volume detach <volume-id>
nexus volume delete <volume-id> --yes
Enter fullscreen mode Exit fullscreen mode

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:

nexus bucket create user-uploads --display-name "User uploads"
Enter fullscreen mode Exit fullscreen mode

Attach it to a deployment:

nexus bucket attach <bucket-id> <deployment-id>
nexus deploy redeploy <deployment-id> --wait
Enter fullscreen mode Exit fullscreen mode

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

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>
Enter fullscreen mode Exit fullscreen mode

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

S3_BUCKET_USER_UPLOADS=org-...-user-uploads
S3_BUCKET_USER_UPLOADS_ACCESS_KEY=...
S3_BUCKET_USER_UPLOADS_SECRET_KEY=...
Enter fullscreen mode Exit fullscreen mode

Example: write files to a bucket from Python

Your app can use boto3 with the injected environment variables:

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",
)
Enter fullscreen mode Exit fullscreen mode

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:

nexus bucket files <bucket-id>
nexus bucket files <bucket-id> --prefix uploads/
Enter fullscreen mode Exit fullscreen mode

Upload a file:

nexus bucket upload <bucket-id> ./report.pdf --key reports/report.pdf
Enter fullscreen mode Exit fullscreen mode

Download a file:

nexus bucket download <bucket-id> reports/report.pdf --out ./report.pdf
Enter fullscreen mode Exit fullscreen mode

Generate a short-lived signed download URL:

nexus bucket download <bucket-id> reports/report.pdf --share --ttl 900
Enter fullscreen mode Exit fullscreen mode

Delete a file:

nexus bucket rm <bucket-id> reports/report.pdf --yes
Enter fullscreen mode Exit fullscreen mode

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:

nexus bucket credentials <bucket-id>
Enter fullscreen mode Exit fullscreen mode

Rotate credentials:

nexus bucket rotate-credentials <bucket-id> --yes
nexus deploy redeploy <deployment-id> --wait
Enter fullscreen mode Exit fullscreen mode

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.

nexus deploy scale <deployment-id> 3
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

Buckets:

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
Enter fullscreen mode Exit fullscreen mode

Example volume attach request:

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"}'
Enter fullscreen mode Exit fullscreen mode

Example bucket attach request:

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>"}'
Enter fullscreen mode Exit fullscreen mode

MCP tools

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

Volume tools:

nexusai_volume_list
nexusai_volume_create
nexusai_volume_attach
nexusai_volume_detach
nexusai_volume_delete
Enter fullscreen mode Exit fullscreen mode

Bucket tools:

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)