Docker部署Django时静态/媒体文件加载失败的配置咨询
我正尝试在Docker环境中部署Django应用,使用Nginx处理静态和媒体文件,Traefik作为反向代理并管理SSL。尽管尝试了多种配置,静态和媒体文件仍无法正常加载。
期望架构:Nginx负责静态与媒体文件的分发,Traefik处理HTTPS请求并将流量路由到对应服务(主业务流量到Django应用,静态/媒体请求到Nginx)。
以下是我的docker-compose.yml文件:
# PostgreSQL Database Service db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: ${DATABASE_USER} POSTGRES_PASSWORD: ${DATABASE_PASSWORD} POSTGRES_DB: ${DATABASE_NAME} volumes: - postgres_data:/var/lib/postgresql/data - ./pg_hba.conf:/var/lib/postgresql/data/pg_hba.conf networks: - backend-network healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${DATABASE_USER}" ] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: cpus: '1.0' memory: 2G # Redis Cache Service redis: image: redis:7-alpine restart: unless-stopped command: [ "redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}" ] volumes: - redis_data:/data networks: - backend-network healthcheck: test: [ "CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "PING" ] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: cpus: '0.5' memory: 512M # Django Web Application Service web: image: ganiyevuz/tana-backend:latest restart: unless-stopped command: > /bin/bash -c " ./wait_for_db.sh db 5432 && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn conf.wsgi:application --bind 0.0.0.0:8000 --workers 4 --threads 4 " volumes: - static_volume:/app/static - media_volume:/app/media expose: - "8000" depends_on: db: condition: service_healthy redis: condition: service_healthy environment: DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY} DJANGO_DEBUG: "True" DATABASE_USER: ${DATABASE_USER} DATABASE_PASSWORD: ${DATABASE_PASSWORD} DATABASE_NAME: ${DATABASE_NAME} DATABASE_HOST: db DATABASE_PORT: 5432 CELERY_BROKER_URL: redis://:${REDIS_PASSWORD}@redis:6379/0 CACHE_BACKEND_URL: redis://:${REDIS_PASSWORD}@redis:6379/1 REDIS_PASSWORD: ${REDIS_PASSWORD} ESKIZ_EMAIL: ${ESKIZ_EMAIL} ESKIZ_PASSWORD: ${ESKIZ_PASSWORD} ESKIZ_AUTH_URL: ${ESKIZ_AUTH_URL} ESKIZ_SEND_URL: ${ESKIZ_SEND_URL} OPENAI_API_KEY: ${OPENAI_API_KEY} networks: - backend-network - frontend-network - monitoring labels: traefik.enable: "true" traefik.http.routers.web.rule: "Host(`${DOMAIN}`)" traefik.http.routers.web.entrypoints: "websecure" traefik.http.routers.web.tls.certresolver: "le" traefik.http.services.web.loadbalancer.server.port: "8000" traefik.http.services.web.loadbalancer.sticky: "true" deploy: replicas: 3 resources: limits: cpus: '1.0' memory: 1G restart_policy: condition: on-failure # Nginx for Serving Static and Media Files nginx: image: nginx:stable-alpine restart: unless-stopped volumes: - static_volume:/usr/share/nginx/html/static - media_volume:/usr/share/nginx/html/media - ./nginx_logs:/var/log/nginx - ./server/nginx.conf:/etc/nginx/conf.d/default.conf networks: - frontend-network depends_on: web: condition: service_started labels: traefik.enable: "true" traefik.http.routers.staticfiles.rule: "Host(`${DOMAIN}`) && (PathPrefix(`/static`) || PathPrefix(`/media`))" traefik.http.routers.staticfiles.entrypoints: "websecure" traefik.http.routers.staticfiles.tls.certresolver: "le" traefik.http.services.staticfiles.loadbalancer.server.port: "80" deploy: resources: limits: cpus: '0.5' memory: 256M # Traefik Reverse Proxy and Load Balancer traefik: image: traefik:v2.10 restart: unless-stopped command: - "--api.dashboard=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.le.acme.httpchallenge=true" - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web" - "--certificatesresolvers.le.acme.email=ssl@${DOMAIN}" - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--log.level=INFO" - "--accesslog=true" - "--metrics.prometheus=true" ports: - "80:80" - "443:443" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "./letsencrypt:/letsencrypt" networks: - frontend-network - backend-network - monitoring deploy: resources: limits: cpus: '0.5' memory: 512M labels: traefik.enable: "true" traefik.http.routers.traefik-dashboard.rule: "Host(`dashboard.tanahealth.uz`)" traefik.http.routers.traefik-dashboard.entrypoints: "websecure" traefik.http.routers.traefik-dashboard.tls.certresolver: "le" traefik.http.services.traefik-dashboard.loadbalancer.server.port: "8080" # Prometheus Monitoring Service prometheus: image: prom/prometheus:latest restart: unless-stopped volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro ports: - "9090:9090" networks: - monitoring depends_on: - web deploy: resources: limits: cpus: '1.0' memory: 1G # Grafana Visualization Service grafana: image: grafana/grafana:latest restart: unless-stopped ports: - "3000:3000" volumes: - grafana_data:/var/lib/grafana networks: - monitoring depends_on: - prometheus deploy: resources: limits: cpus: '0.5' memory: 512M # PostgreSQL Exporter for Prometheus postgres-exporter: image: prometheuscommunity/postgres-exporter restart: unless-stopped environment: DATA_SOURCE_NAME: "postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@db:5432/${DATABASE_NAME}?sslmode=disable" networks: - backend-network - monitoring depends_on: db: condition: service_healthy # Redis Exporter for Prometheus redis-exporter: image: oliver006/redis_exporter restart: unless-stopped command: - "--redis.addr=redis:6379" - "--redis.password=${REDIS_PASSWORD}" networks: - backend-network - monitoring depends_on: redis: condition: service_healthy volumes: postgres_data: redis_data: static_volume: media_volume: grafana_data: networks: frontend-network: backend-network: monitoring:
核心问题
- 配置Nginx以正确从Django容器中提供静态和媒体文件
- 确保Traefik能处理HTTPS并将请求路由到对应服务
具体咨询
- docker-compose.yml中的Nginx设置是否适合提供静态和媒体文件?
- Traefik标签配置是否能正确区分静态/媒体请求与Django应用请求?
- 在此架构下,Django是否需要额外的静态/媒体文件配置?
1. Nginx设置是否适合提供静态/媒体文件?
你的Nginx挂载逻辑是正确的:通过共享卷static_volume和media_volume,将Django容器中collectstatic生成的静态文件、用户上传的媒体文件同步到Nginx容器的/usr/share/nginx/html/static和/media目录。但需要确保Nginx配置文件(./server/nginx.conf)的路径映射准确,否则会出现404错误。
正确的Nginx配置示例(default.conf):
server { listen 80; server_name _; location /static/ { alias /usr/share/nginx/html/static/; expires 30d; add_header Cache-Control "public, max-age=2592000"; } location /media/ { alias /usr/share/nginx/html/media/; expires 7d; add_header Cache-Control "public, max-age=604800"; } }
注意:alias末尾的斜杠必须和请求路径的斜杠对应,否则Nginx会拼接错误的文件路径。另外,Nginx容器与Traefik同属frontend-network,网络连通性没问题。
2. Traefik标签是否能正确区分请求?
当前配置存在优先级问题:web服务的路由规则Host(${DOMAIN})会匹配该域名下所有请求,包括/static和/media路径,导致Traefik不会将静态请求转发到Nginx。
解决方法是给静态文件路由设置更高优先级,让Traefik优先匹配静态/媒体路径:
# 在nginx服务的labels中添加优先级配置 traefik.http.routers.staticfiles.priority: "100"
Traefik路由优先级默认是0,数值越高优先级越高。添加后,/static和/media请求会先被Nginx路由匹配,剩余请求才会转发到Django的web服务。
另外,你的Traefik配置中providers.docker.exposedbydefault=false、仅显式开启traefik.enable=true的服务代理,这部分设置是正确的。
3. Django需要的额外静态/媒体配置
在当前架构下,Django需要调整以下配置:
- 静态文件路径匹配:确保
settings.py中的路径与Docker挂载路径一致:STATIC_URL = '/static/' STATIC_ROOT = '/app/static/' # 和docker-compose中web服务的挂载路径一致 MEDIA_URL = '/media/' MEDIA_ROOT = '/app/media/' # 和docker-compose中web服务的挂载路径一致 - 调试模式与权限:生产环境需关闭
DJANGO_DEBUG,避免Django自行处理静态文件请求;同时确保Django容器对/app/media目录有写入权限,可在Dockerfile中添加:RUN mkdir -p /app/media && chown -R app:app /app/media - 信任域名配置:如果前端独立部署,需添加域名到CSRF信任列表:
CSRF_TRUSTED_ORIGINS = [f'https://{DOMAIN}']
内容的提问来源于stack exchange,提问作者Jakhongir Ganiev

