Configure Storage Provider
Configure cloud storage for secure backup management across your environments.
Overview
Storage configuration is a prerequisite for creating backups. OEC.SH supports 6 cloud storage providers, allowing you to choose based on cost, performance, and compliance requirements.
Why Storage Configuration is Required
Before you can create backups, you must configure at least one cloud storage provider:
- Backups Require Storage: All environment backups (database + filestore) are stored in cloud storage
- Cannot Create Backups Without Config: The backup creation endpoint will fail if no storage is configured
- Multi-Cloud Support: Use different storage providers for different organizations or backup policies
- BYOS (Bring Your Own Storage): You control your backup data - we never lock you into our storage
Storage Architecture
Environment Backup
├─ Database Dump (PostgreSQL .sql)
├─ Filestore Archive (.tar.gz)
└─ Manifest (metadata.json)
↓
Cloud Storage Provider
(S3, R2, B2, MinIO, FTP, SFTP)Quick Start: Cloudflare R2 (Recommended)
Why Cloudflare R2?
- Zero Egress Fees: Download backups without bandwidth charges
- S3-Compatible: Drop-in replacement for AWS S3
- Affordable: $0.015/GB/month storage (50% cheaper than S3)
- Global Network: Cloudflare's edge network ensures fast uploads/downloads
- Built-in Security: Automatic encryption at rest
Step 1: Create Cloudflare R2 Bucket
-
Log in to Cloudflare Dashboard
- Navigate to https://dash.cloudflare.com/ (opens in a new tab)
- Select your account
-
Create R2 Bucket
- Go to R2 in the left sidebar
- Click "Create Bucket"
- Enter bucket name (e.g.,
oecsh-backups) - Choose location hint (optional, R2 auto-distributes)
- Click "Create Bucket"
-
Note Your Account ID
- Find your Account ID in the R2 overview page
- Format: 32 character hexadecimal string (e.g.,
a1b2c3d4e5f6...)
Step 2: Generate R2 API Token
-
Create API Token
- In R2 dashboard, click "Manage R2 API Tokens"
- Click "Create API Token"
- Enter token name:
OEC.SH Backups - Permissions:
- ✅ Object Read & Write
- ✅ Admin Read & Write (if you want lifecycle management)
- Click "Create API Token"
-
Save Credentials
Access Key ID: YOUR_ACCESS_KEY_ID_HERE Secret Access Key: YOUR_SECRET_ACCESS_KEY_HERE⚠️ Important: Copy these credentials immediately - you won't see them again!
Step 3: Configure in OEC.SH
-
Navigate to Storage Settings
- Go to Dashboard → Settings
- Click "Storage" tab
- Click "Add Storage Provider"
-
Fill R2 Configuration
Name: Cloudflare R2 Production Provider: Cloudflare R2 Bucket Name: oecsh-backups Account ID: YOUR_ACCOUNT_ID_HERE Access Key: YOUR_ACCESS_KEY_ID_HERE Secret Key: YOUR_SECRET_ACCESS_KEY_HERE Path Prefix: backups/ (optional, organizes your buckets) Set as Default: ✅ Yes -
Test Connection
- Click "Test Connection" button
- Wait for connection verification
- ✅ Success: "Connection test successful"
- ❌ Failed: See Troubleshooting section
-
Save Configuration
- Click "Save Storage Config"
- Configuration is now available for backups
Setup Complete! 🎉 You can now create backups. Proceed to Create Your First Project.
Storage Provider Comparison
Choose the right provider for your needs:
| Provider | Best For | Cost | Egress Fees | Setup Complexity | Notes |
|---|---|---|---|---|---|
| Cloudflare R2 | Most users | $0.015/GB/mo | $0 | ⭐ Easy | Zero egress, S3-compatible, recommended |
| AWS S3 | Enterprise | $0.023/GB/mo | $0.09/GB | ⭐ Easy | Industry standard, multi-region |
| Backblaze B2 | Archival | $0.005/GB/mo | $0.01/GB | ⭐⭐ Moderate | Lowest storage cost, good for long-term |
| MinIO | Self-hosted | Infrastructure cost | $0 | ⭐⭐⭐ Advanced | GDPR compliance, full control |
| SFTP | Legacy systems | Infrastructure cost | $0 | ⭐⭐ Moderate | Use existing SFTP servers |
| FTP | Legacy systems | Infrastructure cost | $0 | ⭐⭐ Moderate | Less secure than SFTP |
Feature Comparison
| Feature | R2 | S3 | B2 | MinIO | SFTP | FTP |
|---|---|---|---|---|---|---|
| S3-Compatible API | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Encryption at Rest | ✅ | ✅ | ✅ | ✅ | ⚠️ Manual | ⚠️ Manual |
| Versioning | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Lifecycle Policies | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Multi-Region | ✅ Auto | ✅ Manual | ✅ | ⚠️ DIY | N/A | N/A |
| Zero Egress | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
| Compliance (GDPR) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Test Storage Connection
Testing your storage connection ensures backups will succeed before you create them.
UI Method
-
Open Storage Config
- Navigate to Dashboard → Settings → Storage
- Find your storage configuration in the list
-
Click Test Connection
- Click the "Test Connection" button next to your config
- Wait for the test to complete (2-5 seconds)
-
View Results
-
✅ Success: Connection verified
✓ Connection test successful ✓ Bucket exists ✓ Can write to bucket ✓ Can read from bucket ✓ Latency: 145ms -
❌ Failed: Connection test failed
✗ Connection failed: Invalid credentials
-
API Method
Test connection before saving credentials:
curl -X POST https://api.oec.sh/api/v1/backups/storage-configs/test-connection?organization_id=YOUR_ORG_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Test R2 Config",
"provider": "cloudflare_r2",
"bucket": "oecsh-backups",
"account_id": "YOUR_ACCOUNT_ID",
"access_key": "YOUR_ACCESS_KEY",
"secret_key": "YOUR_SECRET_KEY",
"path_prefix": "backups/"
}'Response (Success):
{
"success": true,
"message": "Connection successful",
"bucket_exists": true,
"can_write": true,
"can_read": true,
"latency_ms": 145
}Response (Failed):
{
"success": false,
"message": "Connection failed: The AWS Access Key Id you provided does not exist in our records.",
"bucket_exists": null,
"can_write": null,
"can_read": null,
"latency_ms": null
}What the Test Checks
- Authentication: Verifies access key and secret key are valid
- Bucket Exists: Confirms the bucket name is correct
- Write Permission: Uploads a test file (
test-connection-{timestamp}.txt) - Read Permission: Downloads the test file
- Cleanup: Deletes the test file
- Latency: Measures round-trip time to storage
Set as Default Storage
The default storage is automatically used when creating backups without specifying a storage configuration.
Why Set a Default?
- Convenience: No need to select storage for every backup
- Automated Backups: Backup policies use the default storage
- Safety Net: Prevents backup failures due to missing storage config
Setting Default Storage
UI Method:
- Go to Settings → Storage
- Click "Set as Default" next to your preferred storage config
- Confirm the action
- ✅ The config is now marked with a "Default" badge
API Method:
curl -X PATCH https://api.oec.sh/api/v1/backups/storage-configs/{config_id} \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"is_default": true
}'Note: Only one storage config can be default per organization. Setting a new default automatically unsets the previous one.
Complete Provider Guides
For detailed setup instructions for each provider, see:
- AWS S3 Setup - Enterprise-grade cloud storage (Sprint 2E44 - STR-001)
- Cloudflare R2 Setup - Zero egress, S3-compatible (Sprint 2E44 - STR-002)
- Backblaze B2 Setup - Lowest cost archival storage (Sprint 2E44 - STR-003)
- MinIO Setup - Self-hosted S3-compatible storage (Sprint 2E44 - STR-004)
- FTP/SFTP Setup - Legacy file transfer protocols (Sprint 2E44 - STR-005)
API Reference
Create Storage Configuration
Endpoint: POST /api/v1/backups/storage-configs
Permission: org.storage.create
Request:
curl -X POST https://api.oec.sh/api/v1/backups/storage-configs?organization_id=YOUR_ORG_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Backups",
"provider": "cloudflare_r2",
"bucket": "oecsh-backups",
"account_id": "YOUR_ACCOUNT_ID_HERE",
"access_key": "YOUR_ACCESS_KEY_ID_HERE",
"secret_key": "YOUR_SECRET_ACCESS_KEY_HERE",
"path_prefix": "backups/",
"is_default": true
}'Response (201 Created):
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"organization_id": "org-uuid",
"name": "Production Backups",
"provider": "cloudflare_r2",
"bucket": "oecsh-backups",
"region": "auto",
"endpoint_url": null,
"path_prefix": "backups/",
"storage_class": null,
"is_default": true,
"is_active": true,
"total_size_bytes": 0,
"object_count": 0,
"last_used_at": null,
"created_at": "2024-12-11T10:30:00Z",
"updated_at": "2024-12-11T10:30:00Z"
}Note: Access key and secret key are encrypted at rest and never returned in API responses.
List Storage Configurations
Endpoint: GET /api/v1/backups/storage-configs?organization_id={org_id}
Permission: org.storage.list
curl -X GET https://api.oec.sh/api/v1/backups/storage-configs?organization_id=YOUR_ORG_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Response:
[
{
"id": "config-1-uuid",
"name": "Production Backups",
"provider": "cloudflare_r2",
"bucket": "oecsh-backups",
"is_default": true,
"is_active": true,
"total_size_bytes": 5368709120,
"object_count": 42,
"last_used_at": "2024-12-11T08:00:00Z",
"created_at": "2024-11-01T12:00:00Z",
"updated_at": "2024-12-11T08:00:00Z"
},
{
"id": "config-2-uuid",
"name": "Archive Storage",
"provider": "backblaze_b2",
"bucket": "oecsh-archive",
"is_default": false,
"is_active": true,
"total_size_bytes": 10737418240,
"object_count": 156,
"last_used_at": "2024-12-10T23:00:00Z",
"created_at": "2024-11-15T14:30:00Z",
"updated_at": "2024-12-10T23:00:00Z"
}
]Update Storage Configuration
Endpoint: PATCH /api/v1/backups/storage-configs/{config_id}
Permission: org.storage.update
curl -X PATCH https://api.oec.sh/api/v1/backups/storage-configs/config-uuid \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Name",
"is_default": true
}'Delete Storage Configuration
Endpoint: DELETE /api/v1/backups/storage-configs/{config_id}
Permission: org.storage.delete
curl -X DELETE https://api.oec.sh/api/v1/backups/storage-configs/config-uuid \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Important: You cannot delete a storage configuration that has backups. You must:
- Delete all backups using this config, OR
- Migrate backups to another storage config
Error Response (400 Bad Request):
{
"detail": "Cannot delete storage config with 42 backups. Delete or migrate backups first."
}Test Connection (Before Saving)
Endpoint: POST /api/v1/backups/storage-configs/test-connection?organization_id={org_id}
Permission: org.storage.create
This endpoint lets you test credentials before creating the configuration.
curl -X POST https://api.oec.sh/api/v1/backups/storage-configs/test-connection?organization_id=YOUR_ORG_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Config",
"provider": "cloudflare_r2",
"bucket": "test-bucket",
"account_id": "account-id",
"access_key": "access-key",
"secret_key": "secret-key"
}'Test Existing Configuration
Endpoint: POST /api/v1/backups/storage-configs/{config_id}/test
Permission: org.storage.list (read-only)
curl -X POST https://api.oec.sh/api/v1/backups/storage-configs/config-uuid/test \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Storage Platform Settings
Platform administrators can control which storage providers are available to organizations.
Check Enabled Providers
Endpoint: GET /api/v1/backups/storage-settings
curl -X GET https://api.oec.sh/api/v1/backups/storage-settings \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Response:
{
"enabled_providers": [
"aws_s3",
"cloudflare_r2",
"backblaze_b2",
"minio",
"sftp",
"ftp"
],
"allow_org_byos": true,
"has_default_storage": false
}Fields:
enabled_providers: List of storage providers available on this platformallow_org_byos: Whether organizations can configure their own storage (BYOS)has_default_storage: Whether platform has default storage configured
Error Response (403 Forbidden):
{
"detail": "Organization storage configuration is not enabled on this platform. Contact your platform administrator."
}Next Steps
Now that you have storage configured, you can:
- Create Your First Project - Set up your Odoo project
- Create Manual Backups - Create on-demand backups (Sprint 2E44 - BAK-001)
- Setup Backup Policies - Automate backups with GFS retention (Sprint 2E46 - BAK-003)
- Restore from Backup - Disaster recovery (ENV-003)
Troubleshooting
Connection Test Failed
Symptom: Test connection returns success: false
Common Causes:
-
Invalid Credentials
Error: The AWS Access Key Id you provided does not exist in our records.Solution:
- Verify access key and secret key are correct
- Check for extra spaces or special characters
- Regenerate credentials if needed
-
Bucket Not Found
Error: The specified bucket does not existSolution:
- Verify bucket name spelling
- Check bucket exists in provider dashboard
- Ensure bucket is in the correct region/account
-
Permission Denied
Error: Access DeniedSolution:
- Verify API token has Read & Write permissions
- For AWS S3: Check IAM policy includes
s3:GetObject,s3:PutObject,s3:DeleteObject - For R2: Token must have "Object Read & Write" enabled
-
Network/Firewall Issues
Error: Connection timeoutSolution:
- Check OEC.SH server can reach storage provider
- Verify no firewall blocking outbound HTTPS (port 443)
- For SFTP/FTP: Check port is open (22 for SFTP, 21 for FTP)
-
Incorrect Endpoint URL
Error: Could not connect to the endpoint URLSolution:
- For Cloudflare R2: Ensure account ID is correct (auto-generates endpoint)
- For Backblaze B2: Verify endpoint matches bucket region (e.g.,
s3.us-west-004.backblazeb2.com) - For MinIO: Ensure endpoint includes protocol and port (e.g.,
http://minio.example.com:9000)
Cannot Create Storage Config
Symptom: API returns 403 Forbidden
Cause: Platform has disabled organization BYOS (Bring Your Own Storage)
Solution:
- Contact platform administrator to enable
ALLOW_ORG_BYOS=true - Use platform-provided default storage (if available)
Cannot Delete Storage Config
Symptom: API returns 400 Bad Request with backup count
Solution:
-
Option 1: Delete All Backups
# List backups using this storage curl -X GET https://api.oec.sh/api/v1/backups/organization/YOUR_ORG_ID/backups \ -H "Authorization: Bearer YOUR_JWT_TOKEN" # Delete each backup curl -X DELETE https://api.oec.sh/api/v1/backups/{backup_id} \ -H "Authorization: Bearer YOUR_JWT_TOKEN" -
Option 2: Migrate Backups (Future Feature - Sprint 2E44)
- Create new storage config
- Use backup migration tool to move backups
- Delete old storage config
Storage Provider Errors
AWS S3
Error: The authorization header is malformed; the region 'us-east-1' is wrong; expecting 'eu-west-1'Solution: Update region field to match bucket's region
Cloudflare R2
Error: Account ID is required for Cloudflare R2Solution: Provide account_id field (32-character hex string)
Backblaze B2
Error: Invalid application keySolution:
- Use Application Key ID as
access_key - Use Application Key as
secret_key - Ensure key has access to the bucket
MinIO
Error: SSL: CERTIFICATE_VERIFY_FAILEDSolution:
- For self-signed certificates, update MinIO client config
- For production, use valid SSL certificates
- Ensure endpoint URL includes protocol (
http://orhttps://)
SFTP/FTP
Error: Authentication failed (publickey,password)Solution:
- For SFTP: Use username as
access_key, password assecret_key - Verify SSH server allows password authentication
- For key-based auth, contact support (Sprint 2E44 feature)
Error: Connection refusedSolution:
- Verify SFTP/FTP server is running
- Check port is correct (22 for SFTP, 21 for FTP)
- Ensure firewall allows connections from OEC.SH servers
Slow Upload/Download Performance
Symptom: Backups taking longer than expected
Solutions:
- Check Latency: Use test connection to measure latency
- Choose Closer Region: Select storage region closest to your OEC.SH server
- Use Cloudflare R2: Global edge network provides fast uploads worldwide
- Network Bandwidth: Verify OEC.SH server has sufficient bandwidth
Storage Costs Higher Than Expected
Solutions:
- Enable Lifecycle Policies: Auto-delete old backups (Sprint 2E44)
- Use Cheaper Storage: Backblaze B2 for archival ($0.005/GB/mo)
- Optimize Retention: Reduce daily/weekly/monthly retention periods
- Compress Backups: Already enabled by default (gzip compression)
Security Best Practices
Credential Management
- Use Dedicated Tokens: Create storage API tokens specifically for OEC.SH
- Least Privilege: Grant only required permissions (Read, Write, Delete)
- Rotate Regularly: Change access keys every 90 days
- Never Commit: Don't store credentials in Git repositories
Bucket Security
- Private Buckets: Never make backup buckets publicly accessible
- Encryption at Rest: Enable provider's encryption (most enable by default)
- Versioning: Enable bucket versioning for disaster recovery
- Lifecycle Policies: Auto-delete old backups after retention period
Access Control
- IP Restrictions: Limit bucket access to OEC.SH server IPs (if provider supports)
- MFA: Enable multi-factor authentication on cloud provider accounts
- Audit Logs: Enable access logging on buckets
- Role Separation: Use different storage configs for dev/staging/production
Storage Usage Tracking
Each storage configuration tracks usage metrics:
- Total Size:
total_size_bytes- Sum of all backup sizes - Object Count:
object_count- Number of backups stored - Last Used:
last_used_at- Timestamp of most recent backup
View usage in the Storage settings page or via API:
curl -X GET https://api.oec.sh/api/v1/backups/storage-configs?organization_id=YOUR_ORG_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Response Snippet:
{
"total_size_bytes": 5368709120, // 5 GB
"object_count": 42,
"last_used_at": "2024-12-11T08:00:00Z"
}Related Documentation
- Create Manual Backups - Create on-demand backups (Sprint 2E44 - BAK-001)
- Backup Policies & Scheduling - Automate backups with GFS retention (Sprint 2E46 - BAK-003)
- Restore from Backup - Disaster recovery (ENV-003)
- AWS S3 Setup - Detailed AWS S3 guide (Sprint 2E44 - STR-001)
- Cloudflare R2 Setup - Detailed R2 guide (Sprint 2E44 - STR-002)
- Backblaze B2 Setup - Detailed B2 guide (Sprint 2E44 - STR-003)
- MinIO Setup - Self-hosted storage guide (Sprint 2E44 - STR-004)
- FTP/SFTP Setup - Legacy protocol guide (Sprint 2E44 - STR-005)
Documentation Version: 1.0 Last Updated: December 11, 2024 Sprint: Phase 1 - Sprint 2E42 Contributors: PaaSPortal Documentation Team