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(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)", "Bash(Get-ChildItem -Path \"c:\\\\Users\\\\bruno\\\\Documents\\\\GitHub\\\\vontor-cz\\\\backend\\\\\" -Directory | Select-Object -ExpandProperty Name)",
"PowerShell(Get-Command python)", "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 POSTGRES_PASSWORD=AWSJeMocDrahaZalezitost
# ------------------ MEDIA / STATIC ------------------ # ------------------ 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/ MEDIA_URL=/media/
# ------------------ REDIS / CACHING / CHANNELS ------------------ # ------------------ REDIS / CACHING / CHANNELS ------------------
@@ -44,12 +46,29 @@ EMAIL_USER=
EMAIL_USER_PASSWORD= EMAIL_USER_PASSWORD=
DEFAULT_FROM_EMAIL= DEFAULT_FROM_EMAIL=
# ------------------ AWS (disabled unless USE_AWS=True) ------------------ # ------------------ S3 STORAGE ------------------
USE_AWS=False # Set USE_S3=True to enable. AWS_S3_ENDPOINT_URL selects the backend:
AWS_ACCESS_KEY_ID= # set → MinIO / S3-compatible
AWS_SECRET_ACCESS_KEY= # empty → real AWS S3
AWS_STORAGE_BUCKET_NAME=
AWS_S3_REGION_NAME=eu-central-1 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) ------------------ # ------------------ JWT / TOKENS (lifetimes defined in code) ------------------
# (No env vars needed; kept placeholder section) # (No env vars needed; kept placeholder section)

View File

@@ -626,63 +626,59 @@ STATICFILES_DIRS = [
if os.getenv("USE_AWS", "") == "True": USE_S3 = _env_bool("USE_S3")
USE_AWS = True print(f"USE_S3: {USE_S3}")
else:
USE_AWS = False
print(f"\n-------------- USE_AWS: {USE_AWS} --------------") STATIC_ROOT = BASE_DIR / 'collectedstaticfiles'
if USE_AWS is False: if not USE_S3:
# Development: Use local file system storage for static files # Local filesystem — development only
STORAGES = { STORAGES = {
"default": { "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"BACKEND": "django.core.files.storage.FileSystemStorage", "staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
},
"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') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATIC_URL = '/static/' 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 = { STORAGES = {
"default": { "default": {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"},
"BACKEND" : "storages.backends.s3boto3.S3StaticStorage", "staticfiles": {"BACKEND": "storages.backends.s3boto3.S3StaticStorage"},
},
"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/'
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_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY') AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME') AWS_STORAGE_BUCKET_NAME = S3_BUCKET
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'
AWS_S3_SIGNATURE_VERSION = 's3v4' # Use AWS Signature Version 4 AWS_S3_FILE_OVERWRITE = False
AWS_S3_USE_SSL = True AWS_DEFAULT_ACL = None
AWS_S3_FILE_OVERWRITE = True AWS_QUERYSTRING_AUTH = False
AWS_DEFAULT_ACL = None # Set to None to avoid setting a default ACL AWS_S3_USE_SSL = S3_SSL
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: depends_on:
- db - db
- redis - redis
- rustfs
volumes: volumes:
- ./backend:/app - ./backend:/app
- static-data:/app/collectedstaticfiles - static-data:/app/collectedstaticfiles
@@ -66,6 +67,7 @@ services:
- redis - redis
- db - db
- backend - backend
- rustfs
networks: networks:
- app_network - app_network
@@ -84,6 +86,7 @@ services:
- redis - redis
- db - db
- backend - backend
- rustfs
networks: networks:
- app_network - app_network
@@ -114,8 +117,7 @@ services:
env_file: env_file:
- ./frontend/.env - ./frontend/.env
ports: ports:
- 80:80 - 8001:80
# - 9000:80
depends_on: depends_on:
- backend - backend
networks: networks:
@@ -157,4 +159,10 @@ volumes:
type: none type: none
o: bind o: bind
device: ./volumes/media device: ./volumes/media
rustfs-data:
driver: local
driver_opts:
type: none
o: bind
device: ./volumes/rustfs