Add S3/MinIO (RustFS) support and compose updates

Introduce S3-compatible storage support and update local/dev configuration.

- .claude: add "WebSearch" to local settings
- backend/.env.example: add USE_S3 flag and detailed S3/MinIO (RustFS) example env vars + comments to explain dev vs production usage
- backend/vontor_cz/settings.py: refactor to use USE_S3 boolean; set STATIC_ROOT; configure STORAGES for local filesystem vs S3 backends; add handling for S3 endpoint (MinIO/RustFS) vs real AWS S3 including path-style addressing, URL construction, and sensible AWS defaults
- docker-compose.yml: add rustfs to service dependencies, include rustfs in networks, change frontend port mapping to 8001:80, and add rustfs-data volume binding

These changes enable using a local S3-compatible backend (RustFS/MinIO) in development while keeping the option to swap to real AWS S3 for production.
This commit is contained in:
2026-06-10 01:29:30 +02:00
parent 2592a69790
commit 270d850572
4 changed files with 78 additions and 54 deletions

View File

@@ -18,7 +18,8 @@
"Bash(node -e \"const r = require\\('react-icons/si'\\); const celery = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('celery'\\) || k.toLowerCase\\(\\).includes\\('worker'\\) || k.toLowerCase\\(\\).includes\\('task'\\)\\).slice\\(0,10\\); console.log\\(celery\\);\")",
"Bash(Get-ChildItem -Path \"c:\\\\Users\\\\bruno\\\\Documents\\\\GitHub\\\\vontor-cz\\\\backend\\\\\" -Directory | Select-Object -ExpandProperty Name)",
"PowerShell(Get-Command python)",
"PowerShell(python -c \"import django; print\\(django.__version__\\)\")"
"PowerShell(python -c \"import django; print\\(django.__version__\\)\")",
"WebSearch"
]
}
}

View File

@@ -20,6 +20,8 @@ POSTGRES_USER=dockerDBuser
POSTGRES_PASSWORD=AWSJeMocDrahaZalezitost
# ------------------ MEDIA / STATIC ------------------
# USE_S3=True enables S3-compatible storage (MinIO or AWS).
# Leave USE_S3=False (default) to use local filesystem in development.
MEDIA_URL=/media/
# ------------------ REDIS / CACHING / CHANNELS ------------------
@@ -44,12 +46,29 @@ EMAIL_USER=
EMAIL_USER_PASSWORD=
DEFAULT_FROM_EMAIL=
# ------------------ AWS (disabled unless USE_AWS=True) ------------------
USE_AWS=False
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME=
AWS_S3_REGION_NAME=eu-central-1
# ------------------ S3 STORAGE ------------------
# Set USE_S3=True to enable. AWS_S3_ENDPOINT_URL selects the backend:
# set → MinIO / S3-compatible
# empty → real AWS S3
USE_S3=False
# RustFS (docker-compose default — S3-compatible MinIO successor)
AWS_S3_ENDPOINT_URL=http://rustfs:9000
AWS_S3_CUSTOM_DOMAIN=localhost:9000/vontor
AWS_STORAGE_BUCKET_NAME=vontor
AWS_ACCESS_KEY_ID=rustfsadmin
AWS_SECRET_ACCESS_KEY=rustfsadmin
RUSTFS_ACCESS_KEY=rustfsadmin
RUSTFS_SECRET_KEY=rustfsadmin
RUSTFS_BUCKET_NAME=vontor
# AWS S3 (swap in for production — clear AWS_S3_ENDPOINT_URL)
# AWS_STORAGE_BUCKET_NAME=my-bucket
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# AWS_S3_REGION_NAME=eu-central-1
# AWS_S3_ENDPOINT_URL=
# ------------------ JWT / TOKENS (lifetimes defined in code) ------------------
# (No env vars needed; kept placeholder section)

View File

@@ -626,63 +626,59 @@ STATICFILES_DIRS = [
if os.getenv("USE_AWS", "") == "True":
USE_AWS = True
else:
USE_AWS = False
USE_S3 = _env_bool("USE_S3")
print(f"USE_S3: {USE_S3}")
print(f"\n-------------- USE_AWS: {USE_AWS} --------------")
STATIC_ROOT = BASE_DIR / 'collectedstaticfiles'
if USE_AWS is False:
# Development: Use local file system storage for static files
if not USE_S3:
# Local filesystem — development only
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
}
# Media and Static URL for local dev
MEDIA_URL = os.getenv("MEDIA_URL", "/media/")
MEDIA_URL = os.getenv("MEDIA_URL", "/media/")
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'collectedstaticfiles'
elif USE_AWS:
# PRODUCTION
AWS_LOCATION = "static"
else:
# S3-compatible storage — MinIO (AWS_S3_ENDPOINT_URL set) or real AWS S3 (endpoint empty)
S3_BUCKET = os.getenv('AWS_STORAGE_BUCKET_NAME', 'vontor')
S3_ENDPOINT = os.getenv('AWS_S3_ENDPOINT_URL', '')
S3_CUSTOM_DOMAIN = os.getenv('AWS_S3_CUSTOM_DOMAIN', '')
S3_SSL = not S3_ENDPOINT or S3_ENDPOINT.startswith('https')
S3_PROTO = 'https' if S3_SSL else 'http'
# Production: Use S3 storage
STORAGES = {
"default": {
"BACKEND" : "storages.backends.s3boto3.S3StaticStorage",
},
"staticfiles": {
"BACKEND" : "storages.backends.s3boto3.S3StaticStorage",
},
"default": {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"},
"staticfiles": {"BACKEND": "storages.backends.s3boto3.S3StaticStorage"},
}
# Media and Static URL for AWS S3
MEDIA_URL = f'https://{os.getenv("AWS_STORAGE_BUCKET_NAME")}.s3.amazonaws.com/media/'
STATIC_URL = f'https://{os.getenv("AWS_STORAGE_BUCKET_NAME")}.s3.amazonaws.com/static/'
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = S3_BUCKET
AWS_S3_SIGNATURE_VERSION = 's3v4'
AWS_S3_FILE_OVERWRITE = False
AWS_DEFAULT_ACL = None
AWS_QUERYSTRING_AUTH = False
AWS_S3_USE_SSL = S3_SSL
CSRF_TRUSTED_ORIGINS.append(STATIC_URL)
# Static files should be collected to a local directory and then uploaded to S3
STATIC_ROOT = BASE_DIR / 'collectedstaticfiles'
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME', 'us-east-1') # Default to 'us-east-1' if not set
AWS_S3_SIGNATURE_VERSION = 's3v4' # Use AWS Signature Version 4
AWS_S3_USE_SSL = True
AWS_S3_FILE_OVERWRITE = True
AWS_DEFAULT_ACL = None # Set to None to avoid setting a default ACL
if S3_ENDPOINT:
# MinIO / S3-compatible: needs path-style addressing
AWS_S3_ENDPOINT_URL = S3_ENDPOINT
AWS_S3_ADDRESSING_STYLE = 'path'
if S3_CUSTOM_DOMAIN:
AWS_S3_CUSTOM_DOMAIN = S3_CUSTOM_DOMAIN
AWS_S3_URL_PROTOCOL = f'{S3_PROTO}:'
base_host = S3_CUSTOM_DOMAIN or f'{S3_ENDPOINT.split("://")[1]}/{S3_BUCKET}'
MEDIA_URL = f'{S3_PROTO}://{base_host}/'
STATIC_URL = f'{S3_PROTO}://{base_host}/static/'
else:
# AWS S3
AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME', 'us-east-1')
MEDIA_URL = f'https://{S3_BUCKET}.s3.amazonaws.com/media/'
STATIC_URL = f'https://{S3_BUCKET}.s3.amazonaws.com/static/'
CSRF_TRUSTED_ORIGINS.append(STATIC_URL)

View File

@@ -12,6 +12,7 @@ services:
depends_on:
- db
- redis
- rustfs
volumes:
- ./backend:/app
- static-data:/app/collectedstaticfiles
@@ -66,6 +67,7 @@ services:
- redis
- db
- backend
- rustfs
networks:
- app_network
@@ -84,6 +86,7 @@ services:
- redis
- db
- backend
- rustfs
networks:
- app_network
@@ -114,8 +117,7 @@ services:
env_file:
- ./frontend/.env
ports:
- 80:80
# - 9000:80
- 8001:80
depends_on:
- backend
networks:
@@ -157,4 +159,10 @@ volumes:
type: none
o: bind
device: ./volumes/media
rustfs-data:
driver: local
driver_opts:
type: none
o: bind
device: ./volumes/rustfs