Skip to main content

Object Storage

Configure where EUDIPLO stores uploaded files: credential images, logos and the images/ of imported tenant configurations. Use the local filesystem for a single backend, and S3-compatible storage (RustFS in the bundled stacks, AWS S3 or another provider) when several replicas run or the data must live outside the host.

KeyTypeAllowed valuesNotes
STORAGE_DRIVERstringlocal, s3The storage driver to use [optional] (default: local)
LOCAL_STORAGE_DIRstring-The directory to store files in when using local storage [optional] [when STORAGE_DRIVER is {"override":true} | "local" → then default=undefined]
S3_REGIONstring-The AWS region for the S3 bucket [optional] [when STORAGE_DRIVER is {"override":true} | "s3" → then required]
S3_BUCKETstring-The name of the S3 bucket [optional] [when STORAGE_DRIVER is {"override":true} | "s3" → then required]
S3_ACCESS_KEY_IDstring-The access key ID for the S3 bucket. Optional when using IRSA or instance profile credentials. [optional] [when STORAGE_DRIVER is {"override":true} | "s3"]
S3_SECRET_ACCESS_KEYstring-The secret access key for the S3 bucket. Optional when using IRSA or instance profile credentials. [optional] [when STORAGE_DRIVER is {"override":true} | "s3"]
S3_ENDPOINTstring-The endpoint URL for the S3 service (for S3-compatible services) [optional] [when STORAGE_DRIVER is {"override":true} | "s3"]
S3_FORCE_PATH_STYLEboolean-Whether to force path-style URLs for S3 [optional] [when STORAGE_DRIVER is {"override":true} | "s3" → then default=false]

Local storage​

STORAGE_DRIVER=local
LOCAL_STORAGE_DIR=/app/uploads # default: <FOLDER>/uploads

Each file is stored under a random UUID, with a <uuid>.meta file for its content type. Put the directory on a persistent volume and include it in your backups. Replicas cannot share it.

S3-compatible storage​

STORAGE_DRIVER=s3
S3_REGION=eu-central-1
S3_BUCKET=eudiplo-files
# For RustFS or other S3-compatible services:
S3_ENDPOINT=http://rustfs:9000
S3_FORCE_PATH_STYLE=true
# Static credentials; omit both to use the AWS SDK default chain (IAM role, IRSA)
S3_ACCESS_KEY_ID=<access key>
S3_SECRET_ACCESS_KEY=<secret key>

At startup, the backend sends a HeadObject request for a key that does not exist. A "not found" answer proves access; any other error (credentials, bucket, region, network) stops the startup with a message. The check needs object-level read permission only, not s3:ListBucket.

The bucket does not have to be public: wallets and browsers download files through the backend. Uploads through the API set the public-read ACL. If your bucket has ACLs disabled (on AWS the default object ownership Bucket owner enforced), such uploads fail with AccessControlListNotSupported; allow ACLs on the bucket or use a provider that ignores them. The bundled rustfs-init job creates S3_BUCKET and grants anonymous s3:GetObject; remove that policy if the bucket must stay private.

Checkpoint: upload an image in the web client (for example a credential logo) and open its URL.

How files are served​

EndpointAccessPurpose
POST /api/storageissuance:manage, multipart field file, up to 5 MBUpload a file for the token's tenant; returns key and URL
GET /storage/:keypublicDownload; the URL is <PUBLIC_URL>/storage/<key>

Downloads are public because wallets fetch logos and credential images without credentials. A file is protected only by its random key, so do not store confidential content. The mapping of keys to tenants and file names is kept in the database; deleting a tenant deletes its files.

To move to another storage driver, copy the objects with their keys (for local storage the files and their .meta files) and switch the variables. Moving existing MinIO data to RustFS is described in the upgrade guide.