S3 Storage
By default NORA stores artifacts on the local filesystem. For production deployments you can switch to any S3-compatible backend — AWS S3, MinIO, RustFS, Ceph RGW, and others.
Configuration
Section titled “Configuration”| Variable | Default | Description |
|---|---|---|
NORA_STORAGE_MODE | local | Set to s3 to enable object storage |
NORA_STORAGE_S3_URL | — | S3-compatible endpoint URL (e.g. http://minio:9000) |
NORA_STORAGE_BUCKET | registry | Bucket name. Must exist before NORA starts |
NORA_STORAGE_S3_ACCESS_KEY | — | Access key. If omitted, anonymous access is used |
NORA_STORAGE_S3_SECRET_KEY | — | Secret key |
NORA_STORAGE_S3_REGION | us-east-1 | Region. Required by some S3 implementations |
MinIO is the most popular self-hosted S3-compatible storage. This example includes an init container that creates the bucket automatically.
Docker Compose
Section titled “Docker Compose”services: minio: image: minio/minio:latest command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: noraadmin MINIO_ROOT_PASSWORD: changeme-minio-secret ports: - 9000:9000 # S3 API - 9001:9001 # MinIO Console volumes: - minio-data:/data healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 5s timeout: 5s retries: 5
createbucket: image: minio/mc:latest depends_on: minio: condition: service_healthy entrypoint: > /bin/sh -c " mc alias set myminio http://minio:9000 noraadmin changeme-minio-secret; mc mb --ignore-existing myminio/nora-storage; exit 0; "
nora: image: ghcr.io/getnora-io/nora:latest depends_on: createbucket: condition: service_completed_successfully environment: NORA_HOST: "0.0.0.0" NORA_STORAGE_MODE: s3 NORA_STORAGE_S3_URL: http://minio:9000 NORA_STORAGE_BUCKET: nora-storage NORA_STORAGE_S3_ACCESS_KEY: noraadmin NORA_STORAGE_S3_SECRET_KEY: changeme-minio-secret NORA_STORAGE_S3_REGION: us-east-1 ports: - 4000:4000 restart: unless-stopped
volumes: minio-data:Verify
Section titled “Verify”# Push a Docker imagedocker tag alpine:latest localhost:4000/test/alpine:latestdocker push localhost:4000/test/alpine:latest
# Pull it backdocker rmi localhost:4000/test/alpine:latestdocker pull localhost:4000/test/alpine:latest
# Upload a raw filecurl -X PUT -d "hello s3" http://localhost:4000/raw/test/hello.txtcurl http://localhost:4000/raw/test/hello.txtCheck MinIO Console at http://localhost:9001 — you should see objects in the nora-storage bucket.
RustFS
Section titled “RustFS”RustFS is a lightweight S3-compatible storage written in Rust. Configuration is identical to MinIO with one difference: the health check endpoint is /health instead of /minio/health/live.
Docker Compose
Section titled “Docker Compose”services: rustfs: image: rustfs/rustfs:latest command: server /data --console-address ":9001" environment: RUSTFS_ROOT_USER: noraadmin RUSTFS_ROOT_PASSWORD: changeme-rustfs-secret ports: - 9000:9000 # S3 API - 9001:9001 # Console volumes: - rustfs-data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/health"] interval: 5s timeout: 5s retries: 5
createbucket: image: minio/mc:latest depends_on: rustfs: condition: service_healthy entrypoint: > /bin/sh -c " mc alias set myrustfs http://rustfs:9000 noraadmin changeme-rustfs-secret; mc mb --ignore-existing myrustfs/nora-storage; exit 0; "
nora: image: ghcr.io/getnora-io/nora:latest depends_on: createbucket: condition: service_completed_successfully environment: NORA_HOST: "0.0.0.0" NORA_STORAGE_MODE: s3 NORA_STORAGE_S3_URL: http://rustfs:9000 NORA_STORAGE_BUCKET: nora-storage NORA_STORAGE_S3_ACCESS_KEY: noraadmin NORA_STORAGE_S3_SECRET_KEY: changeme-rustfs-secret NORA_STORAGE_S3_REGION: us-east-1 ports: - 4000:4000 restart: unless-stopped
volumes: rustfs-data:AWS S3
Section titled “AWS S3”For AWS S3, point NORA_STORAGE_S3_URL to the regional endpoint:
environment: NORA_STORAGE_MODE: s3 NORA_STORAGE_S3_URL: https://s3.eu-central-1.amazonaws.com NORA_STORAGE_BUCKET: my-nora-registry NORA_STORAGE_S3_ACCESS_KEY: AKIA... NORA_STORAGE_S3_SECRET_KEY: wJal... NORA_STORAGE_S3_REGION: eu-central-1config.toml
Section titled “config.toml”[storage]mode = "s3"s3_url = "http://minio:9000"bucket = "nora-storage"s3_access_key = "noraadmin"s3_secret_key = "changeme"s3_region = "us-east-1"Troubleshooting
Section titled “Troubleshooting”NORA starts but artifacts go to local storage
Section titled “NORA starts but artifacts go to local storage”Check NORA_STORAGE_MODE is set to exactly s3 (lowercase). Any misspelling causes a silent fallback to local mode. Run:
docker exec nora env | grep NORA_STORAGEYou should see:
NORA_STORAGE_MODE=s3NORA_STORAGE_S3_URL=http://minio:9000NORA_STORAGE_BUCKET=nora-storage...Connection refused to S3 endpoint
Section titled “Connection refused to S3 endpoint”Ensure the S3 service is healthy before NORA starts. In Docker Compose, use depends_on with condition: service_healthy.
Bucket does not exist
Section titled “Bucket does not exist”NORA does not create buckets. Pre-create with mc mb or an init container (see examples above).
Proxied packages not visible in UI
Section titled “Proxied packages not visible in UI”Proxy-only downloads (Cargo, PyPI, Go, Maven, NuGet, Terraform, Conan, RubyGems, Pub) stream responses directly from upstream without storing them in S3. These packages will not appear in the web UI or in storage listings. Only published packages (via npm publish, twine upload, cargo publish, docker push, curl -X PUT /raw/) and cached tarballs (npm, Ansible) are persisted to storage.
See Also
Section titled “See Also”- Settings — all configuration options
- Production Guide — deployment best practices
- Docker Proxy — pull-through cache setup