You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker部署Django时静态/媒体文件加载失败的配置咨询

Django + Nginx + Traefik 部署静态/媒体文件问题

我正尝试在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:

核心问题

  1. 配置Nginx以正确从Django容器中提供静态和媒体文件
  2. 确保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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 10:48:10