Overview
Applad’s storage system is adapter-agnostic. You define buckets in YAML under storage/buckets/, and Applad handles file operations consistently across local filesystem, S3, R2, GCS, and other providers.
Storage Configuration
Configure your storage adapter in storage/storage.yaml:
adapter : "s3"
config :
bucket : ${S3_BUCKET}
region : ${S3_REGION}
access_key : ${S3_ACCESS_KEY}
secret_key : ${S3_SECRET_KEY}
endpoint : ${S3_ENDPOINT} # Optional — for R2 or custom S3-compatible
ssl : true
Supported Adapters
local — Local filesystem (development)
s3 — Amazon S3
r2 — Cloudflare R2 (S3-compatible)
gcs — Google Cloud Storage
azure — Azure Blob Storage
Environment Overrides
Use different storage backends for different environments:
environment_overrides :
development :
adapter : "local"
config :
path : "./data/storage"
staging :
adapter : "s3"
config :
bucket : ${STAGING_S3_BUCKET}
region : ${S3_REGION}
access_key : ${S3_ACCESS_KEY}
secret_key : ${S3_SECRET_KEY}
Defining Buckets
Each bucket lives in its own YAML file under storage/buckets/.
Public Bucket Example
storage/buckets/avatars.yaml
name : "avatars"
public : true
max_file_size : "5mb"
allowed_types : [ "image/jpeg" , "image/png" , "image/webp" ]
scan :
enabled : true
block_on_threat : true
permissions :
- role : "user"
actions : [ "read" , "write" ]
filter : "owner_id == $user.id"
- role : "*"
actions : [ "read" ]
- role : "admin"
actions : [ "*" ]
Private Bucket Example
storage/buckets/documents.yaml
name : "documents"
public : false
max_file_size : "50mb"
allowed_types : [ "application/pdf" , "text/*" , "application/msword" ]
scan :
enabled : true
block_on_threat : true
encryption :
at_rest : true
permissions :
- role : "admin"
actions : [ "*" ]
- role : "user"
actions : [ "read" , "write" ]
filter : "org_id == $user.org_id"
Bucket Configuration
Visibility
public: true — Files are accessible via public URL
public: false — Files require authentication to access
File Size Limits
Specify maximum file size:
max_file_size : "5mb" # Accepts kb, mb, gb
Allowed File Types
Restrict file uploads by MIME type:
allowed_types :
- "image/jpeg"
- "image/png"
- "image/webp"
- "application/pdf"
- "text/*" # Wildcard for all text types
Virus Scanning
Enable automatic virus scanning on upload:
scan :
enabled : true
block_on_threat : true # Reject infected files
Encryption at Rest
Enable encryption for sensitive files:
encryption :
at_rest : true
Permissions
Permission rules control who can upload, download, and delete files:
permissions :
- role : "user"
actions : [ "read" , "write" ]
filter : "owner_id == $user.id"
- role : "*"
actions : [ "read" ]
- role : "admin"
actions : [ "*" ]
Permission Actions
read — Download files
write — Upload files
delete — Delete files
* — All actions
Permission Filters
# Only own files
filter : "owner_id == $user.id"
# Org-level access
filter : "org_id == $user.org_id"
# Admin or owner
filter : "owner_id == $user.id OR $user.role == 'admin'"
Working with Files
List buckets
Shows all buckets with visibility, size limits, and allowed types.
List files in a bucket
applad storage ls avatars
Shows filename, size, content type, upload date, and owner.
Upload a file
applad storage upload avatars ./avatar.png
Applies virus scan, file type validation, and size limit before accepting.
Download a file
applad storage download avatars avatar.png
Works with both public and private buckets. For private buckets, your SSH key identity is used to authorize the download.
Delete a file
applad storage delete avatars avatar.png
File deletion is permanent and cannot be undone.
Move or rename a file
applad storage move avatars old-avatar.png new-avatar.png
The source file is removed after the copy is confirmed.
Scan bucket for threats
applad storage scan avatars
Runs a virus scan on all files currently stored in a bucket. By default, Applad scans files at upload time — use this to scan existing files retrospectively.
Environment Variables
Storage configuration requires these environment variables:
# Storage — S3
S3_BUCKET =
S3_REGION =
S3_ACCESS_KEY = # [SECRET] applad secrets set S3_ACCESS_KEY
S3_SECRET_KEY = # [SECRET] applad secrets set S3_SECRET_KEY
S3_ENDPOINT = # Optional — for R2 or custom S3-compatible endpoints
# Staging override
STAGING_S3_BUCKET =
Switching Storage Adapters
Switching from local to S3 to R2 is a one-line change. Application code never changes.
Update adapter configuration
adapter : "r2" # Changed from "s3"
config :
bucket : ${R2_BUCKET}
region : ${R2_REGION}
access_key : ${R2_ACCESS_KEY}
secret_key : ${R2_SECRET_KEY}
endpoint : ${R2_ENDPOINT}
ssl : true
Update environment variables
applad secrets set R2_ACCESS_KEY
applad secrets set R2_SECRET_KEY
Reconcile
applad up --dry-run --diff
applad up
Applad updates the storage adapter. No application code changes required.
Complete storage configuration for a media-heavy application:
adapter : "s3"
config :
bucket : ${S3_BUCKET}
region : ${S3_REGION}
access_key : ${S3_ACCESS_KEY}
secret_key : ${S3_SECRET_KEY}
ssl : true
environment_overrides :
development :
adapter : "local"
config :
path : "./data/storage"
storage/buckets/avatars.yaml
name : "avatars"
public : true
max_file_size : "5mb"
allowed_types : [ "image/jpeg" , "image/png" , "image/webp" ]
scan :
enabled : true
block_on_threat : true
permissions :
- role : "user"
actions : [ "read" , "write" ]
filter : "owner_id == $user.id"
- role : "*"
actions : [ "read" ]
storage/buckets/videos.yaml
name : "videos"
public : true
max_file_size : "500mb"
allowed_types : [ "video/mp4" , "video/webm" ]
scan :
enabled : true
block_on_threat : true
permissions :
- role : "user"
actions : [ "read" , "write" ]
filter : "owner_id == $user.id"
- role : "*"
actions : [ "read" ]
storage/buckets/private-documents.yaml
name : "private-documents"
public : false
max_file_size : "50mb"
allowed_types : [ "application/pdf" , "text/*" , "application/msword" ]
scan :
enabled : true
block_on_threat : true
encryption :
at_rest : true
permissions :
- role : "admin"
actions : [ "*" ]
- role : "user"
actions : [ "read" , "write" ]
filter : "org_id == $user.org_id"
Next Steps
Functions Create serverless functions for backend logic
Messaging Configure email, SMS, and push notifications