Archivos estáticos y multimedia#
Configuración para servir archivos estáticos y medios cargados por el usuario en producción.
Descripción general#
Construbot maneja dos tipos de archivos:
Archivos estáticos: CSS, JavaScript, imágenes (parte del código de la aplicación)
Archivos multimedia: cargas de usuarios (PDF, imágenes, documentos)
En producción, estos deben servirse de manera diferente que en desarrollo en términos de rendimiento y escalabilidad.
Estrategia de archivos estáticos#
WhiteNoise (recomendado para implementaciones pequeñas)#
¿Qué es WhiteNoise?
Sirve archivos estáticos directamente desde Django
No se necesita una configuración de servidor web independiente
Compresión Gzip incorporada
Compatible con CDN con encabezados de caché de futuro lejano
Ya configurado en construbot.config.settings.production:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # After Security, before others
...
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Recopilar archivos estáticos:
docker compose run --rm django python manage.py collectstatic --no-input
Verificar:
ls staticfiles/
# Should contain: admin/, css/, js/, staticfiles.json
Beneficios:
Configuración sencilla
No se requiere CDN
Eficiente para <10.000 usuarios
Compresión y almacenamiento en caché integrados
CDN (CloudFront/CloudFlare)#
Para mayor tráfico, proporcione archivos estáticos a través de una CDN:
Configuración de AWS CloudFront:
# Create S3 bucket for static files
aws s3 mb s3://construbot-static-yourdomain
# Sync static files to S3
aws s3 sync staticfiles/ s3://construbot-static-yourdomain/static/
# Create CloudFront distribution
aws cloudfront create-distribution \\
--origin-domain-name construbot-static-yourdomain.s3.amazonaws.com \\
--default-root-object index.html
Actualizar configuración:
# In .env
STATIC_URL=https://d111111abcdef8.cloudfront.net/static/
Beneficios:
Distribución global
Menor latencia
Descargar tráfico de los servidores de aplicaciones
Costos de ancho de banda reducidos
Estrategia de archivos multimedia#
Amazon S3 (recomendado)#
Por qué T3:
Almacenamiento escalable ilimitado
Alta disponibilidad (99,99%)
Integrado con Django a través de django-storages
Puede utilizar CDN de CloudFront
Copias de seguridad automáticas con versionado
Configurar depósito S3:
# Create bucket
aws s3 mb s3://construbot-media-yourdomain --region us-east-1
# Block public access (recommended - use signed URLs)
aws s3api put-public-access-block \\
--bucket construbot-media-yourdomain \\
--public-access-block-configuration \\
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Enable versioning (optional, for backups)
aws s3api put-bucket-versioning \\
--bucket construbot-media-yourdomain \\
--versioning-configuration Status=Enabled
# Configure CORS (if accessing from different domain)
aws s3api put-bucket-cors \\
--bucket construbot-media-yourdomain \\
--cors-configuration file://cors.json
cors.json:
{
"CORSRules": [
{
"AllowedOrigins": ["https://yourdomain.com"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}
Crear usuario de IAM:
# Create user
aws iam create-user --user-name construbot-s3
# Create policy
cat > s3-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": [
"arn:aws:s3:::construbot-media-yourdomain",
"arn:aws:s3:::construbot-media-yourdomain/*"
]
}
]
}
EOF
# Attach policy
aws iam put-user-policy \\
--user-name construbot-s3 \\
--policy-name S3FullAccess \\
--policy-document file://s3-policy.json
# Create access keys
aws iam create-access-key --user-name construbot-s3
Configurar Django:
# In .env
USE_S3=True
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_STORAGE_BUCKET_NAME=construbot-media-yourdomain
AWS_S3_REGION_NAME=us-east-1
# Optional: Custom domain (CloudFront)
AWS_S3_CUSTOM_DOMAIN=d111111abcdef8.cloudfront.net
Ya configurado en construbot.config.settings.production:
if env.bool('USE_S3', default=False):
AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME', default='us-east-1')
# Media files
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# Optional: CloudFront
if env('AWS_S3_CUSTOM_DOMAIN', default=None):
AWS_S3_CUSTOM_DOMAIN = env('AWS_S3_CUSTOM_DOMAIN')
Carga de prueba:
# Django shell
docker compose run --rm django python manage.py shell
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
# Upload test file
path = default_storage.save('test.txt', ContentFile(b'Hello S3'))
print(f"File saved to: {path}")
# Verify in S3
# aws s3 ls s3://construbot-media-yourdomain/
Espacios DigitalOceánicos#
Alternativa a S3:
# In .env
USE_S3=True # Spaces uses S3-compatible API
AWS_ACCESS_KEY_ID=your-spaces-key
AWS_SECRET_ACCESS_KEY=your-spaces-secret
AWS_STORAGE_BUCKET_NAME=construbot-media
AWS_S3_ENDPOINT_URL=https://nyc3.digitaloceanspaces.com
AWS_S3_REGION_NAME=nyc3
Más barato que S3 para un uso elevado de ancho de banda.
Almacenamiento local (pequeñas implementaciones)#
Para implementaciones pequeñas sin almacenamiento en la nube:
# In .env
USE_S3=False
Configure Nginx para servir archivos multimedia:
location /media/ {
alias /app/media/;
expires 1y;
add_header Cache-Control "public";
}
Montar volumen de medios en docker-compose.yml:
django:
volumes:
- media_volume:/app/media
nginx:
volumes:
- media_volume:/app/media:ro
Se requiere estrategia de respaldo - rsync al servidor remoto o S3.
Configuración de archivos estáticos#
Referencia de configuración#
base.py:
# Static files (CSS, JavaScript, Images)
STATIC_ROOT = str(ROOT_DIR / 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = [
str(APPS_DIR / 'static'),
]
producción.py:
# WhiteNoise
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Or with CDN:
STATIC_URL = 'https://cdn.yourdomain.com/static/'
Recopilar archivos estáticos#
Durante la implementación:
docker compose run --rm django python manage.py collectstatic --no-input
Lo que sucede:
Reúne todos los archivos estáticos de aplicaciones y STATICFILES_DIRS
Los procesa (minificación, hash)
Copias a STATIC_ROOT (
staticfiles/)Crea el manifiesto
staticfiles.json
Sistema de manifiesto:
{
"version": "1.0",
"paths": {
"css/main.css": "css/main.abc123.css",
"js/app.js": "js/app.def456.js"
}
}
Beneficios:
Eliminación de caché (nombres de archivos únicos)
Encabezados de caché de futuro lejano
Compresión automática (gzip/brotli)
Archivos estáticos personalizados#
Agregar CSS/JS personalizado:
# Place in
construbot/static/css/custom.css
construbot/static/js/custom.js
En plantillas:
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
<script src="{% static 'js/custom.js' %}"></script>
Recordar después de los cambios:
docker compose run --rm django python manage.py collectstatic --no-input
Configuración de archivos multimedia#
Referencia de configuración#
base.py:
# Media files (user uploads)
MEDIA_ROOT = str(APPS_DIR / 'media')
MEDIA_URL = '/media/'
producción.py con S3:
if USE_S3:
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/' if AWS_S3_CUSTOM_DOMAIN \\
else f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/'
Manejo de carga de archivos#
En modelos (ejemplo):
from django.db import models
class Document(models.Model):
file = models.FileField(upload_to='documents/%Y/%m/%d/')
uploaded_at = models.DateTimeField(auto_now_add=True)
Subir a S3:
# In views or forms
document = Document.objects.create(file=request.FILES['file'])
# File automatically uploaded to S3
URL del archivo:
# Get file URL
url = document.file.url
# Returns: https://bucket.s3.amazonaws.com/documents/2024/01/15/file.pdf
URL firmadas (archivos privados)#
Para acceso seguro a archivos:
from django.core.files.storage import default_storage
# Generate signed URL (expires in 1 hour)
url = default_storage.url('path/to/file.pdf', expire=3600)
Configurar los ajustes de S3:
# In production.py
AWS_QUERYSTRING_AUTH = True # Use signed URLs
AWS_S3_FILE_OVERWRITE = False # Don't overwrite files
AWS_DEFAULT_ACL = None # Use bucket ACL
Mejoramiento#
Optimización de imagen#
Instalar almohada (ya incluida):
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
def optimize_image(image_field):
img = Image.open(image_field)
# Resize if too large
if img.width > 1920:
img.thumbnail((1920, 1920), Image.LANCZOS)
# Convert to RGB if needed
if img.mode != 'RGB':
img = img.convert('RGB')
# Save with optimization
output = BytesIO()
img.save(output, format='JPEG', quality=85, optimize=True)
output.seek(0)
return ContentFile(output.read())
Compresión#
Archivos estáticos (WhiteNoise maneja automáticamente):
compresión gzip
Compresión Brotli (si está instalada)
Compresión manual:
# Install brotli
pip install brotli
# In production.py
WHITENOISE_COMPRESSION = True # Default
Almacenamiento en caché#
Almacenamiento en caché de archivos estáticos:
location /static/ {
alias /app/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
Almacenamiento en caché de archivos multimedia:
location /media/ {
alias /app/media/;
expires 30d;
add_header Cache-Control "public";
}
Configuración de CDN#
Nube Frontal para S3#
Crear distribución:
aws cloudfront create-distribution \\
--origin-domain-name construbot-media-yourdomain.s3.amazonaws.com \\
--comment "Construbot media files" \\
--default-cache-behavior '{
"TargetOriginId": "S3-construbot-media",
"ViewerProtocolPolicy": "redirect-to-https",
"AllowedMethods": {"Items": ["GET", "HEAD"], "Quantity": 2},
"CachedMethods": {"Items": ["GET", "HEAD"], "Quantity": 2},
"ForwardedValues": {
"QueryString": false,
"Cookies": {"Forward": "none"}
},
"MinTTL": 0,
"DefaultTTL": 86400,
"MaxTTL": 31536000
}'
Actualizar la configuración de Django:
# In .env
AWS_S3_CUSTOM_DOMAIN=d111111abcdef8.cloudfront.net
nubeflare#
Configuración:
Agregar dominio a CloudFlare
Actualizar servidores de nombres
Habilitar el almacenamiento en caché para rutas estáticas/media
Configurar reglas de página
Regla de página para archivos estáticos:
URL: *yourdomain.com/static/*
Cache Level: Cache Everything
Edge Cache TTL: 1 year
Copia de seguridad y recuperación#
Versiones S3#
# Enable versioning
aws s3api put-bucket-versioning \\
--bucket construbot-media-yourdomain \\
--versioning-configuration Status=Enabled
# List versions
aws s3api list-object-versions --bucket construbot-media-yourdomain
# Restore previous version
aws s3api copy-object \\
--copy-source construbot-media-yourdomain/path/to/file.pdf?versionId=xxx \\
--bucket construbot-media-yourdomain \\
--key path/to/file.pdf
Políticas de ciclo de vida de S3#
Eliminar archivos antiguos automáticamente:
{
"Rules": [
{
"Id": "DeleteOldVersions",
"Status": "Enabled",
"NoncurrentVersionExpiration": {
"NoncurrentDays": 90
}
},
{
"Id": "TransitionToGlacier",
"Status": "Enabled",
"Transitions": [
{
"Days": 365,
"StorageClass": "GLACIER"
}
]
}
]
}
aws s3api put-bucket-lifecycle-configuration \\
--bucket construbot-media-yourdomain \\
--lifecycle-configuration file://lifecycle.json
Copia de seguridad local#
Sincronizar S3 con local:
# Backup media files locally
aws s3 sync s3://construbot-media-yourdomain /backup/media/
# Schedule with cron
0 3 * * * aws s3 sync s3://construbot-media-yourdomain /backup/media/
Solución de problemas#
Los archivos estáticos no se cargan#
# Recollect
docker compose run --rm django python manage.py collectstatic --clear --no-input
# Check STATIC_ROOT
ls -la staticfiles/
# Verify STATIC_URL in settings
docker compose run --rm django python manage.py shell
>>> from django.conf import settings
>>> print(settings.STATIC_URL)
Error al cargar el archivo#
Verifique los permisos de S3:
# Test AWS credentials
aws s3 ls s3://construbot-media-yourdomain
# Check Django settings
docker compose run --rm django python manage.py shell
>>> from django.conf import settings
>>> print(settings.DEFAULT_FILE_STORAGE)
>>> print(settings.AWS_STORAGE_BUCKET_NAME)
Carga de prueba:
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
path = default_storage.save('test.txt', ContentFile(b'test'))
print(path)
Errores CORS#
Actualización S3 CORS:
aws s3api put-bucket-cors \\
--bucket construbot-media-yourdomain \\
--cors-configuration file://cors.json
Permita su dominio en cors.json AllowedOrigins.
Ver también#
Lista de verificación de producción - Deployment checklist
Implementación de AWS EC2 - AWS deployment guide
Variables de entorno - Environment configuration