Search code examples
dockerproxyfastapitraefikstatic-files

Expose static files from FastAPI app behind Traefik proxy


In my FastAPI app I've mounted the static directory to serve static files as follows:

app.mount('/static', StaticFiles(directory='static'), name='static')

The FastAPI app runs from inside a Docker container defined as follows:

fhback:
    container_name: fhback
    build:
      context: ./back
      dockerfile: Dockerfile
    restart: unless-stopped
    expose:
      - 8000
    command: "gunicorn --workers 6 --timeout 60 --bind 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker main:app"
    volumes:
      - ./back:/app
    labels:
      - traefik.enable=true
      - traefik.http.routers.fhback.rule=Host(`example.com`) && PathPrefix(`/api`)
      - traefik.http.routers.fhback.entrypoints=https
      - traefik.http.routers.fhback.tls.certresolver=le
      - traefik.http.middlewares.firehouse-compress.compress=true
      - traefik.http.middlewares.fhback-stripprefix.stripprefix.prefixes=/api
      - traefik.http.routers.fhback.middlewares=firehouse-compress,fhback-stripprefix

With these Traefik labels, I can access my Swagger with https://example.com/api/docs and also call API endpoints from the front end with https://example.com/api/... (externally) or http://fhouse:8000/api/... (locally, from Docker).

But I never can access my static files in the static dir! When I do http://fhouse:8000/api/static/any-file.txt from any Docker container within the Docker network, it works fine. But when I try https://example.com/api/static/any-file.txt, I get connection error. I realize I must do something with the Traefik config... Any ideas?

Update

There is also another service running in the same Docker bundle defined as follows:

fhfront:
    container_name: fhfront
    build:
      context: ./front
      dockerfile: Dockerfile
    restart: unless-stopped
    expose:
      - 8090
    environment:
      - NODE_ENV=development
    volumes:
      - ./front:/app
      - /app/node_modules
    labels:
      - traefik.enable=true
      - traefik.http.routers.fhfront.rule=Host(`example.com`,`www.example.com`)
      - traefik.http.routers.fhfront.entrypoints=https
      - traefik.http.routers.fhfront.tls.certresolver=le
      - traefik.http.middlewares.firehouse-compress.compress=true
      - traefik.http.routers.fhfront.middlewares=firehouse-compress

This service is the frontend (website) which seems to mask all requests to example.com/static or example.com/api/static. How can I explicitly exclude the static route from example.com in the fhfront container? So that it gets picked up by fhback and serve static files?


Solution

  • ├── back
    │   ├── Dockerfile
    │   ├── main.py
    │   ├── requirements.txt
    │   └── static
    │       └── test.html
    └── docker-compose.yml
    

    Both Traefik and the API are set up as services.

    🗎 docker-compose.yml

    version: "3"
    
    services:
      traefik:
        container_name: traefik
        image: traefik:v2.5
        command:
          - "--log.level=DEBUG"
          - "--api.insecure=true"
          - "--providers.docker=true"
          - "--entrypoints.web.address=:80"
          - "--entrypoints.websecure.address=:443"
        ports:
          - "80:80"
          - "443:443"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock"
    
      fhback:
        container_name: fhback
        build:
          context: ./back
          dockerfile: Dockerfile
        restart: unless-stopped
        volumes:
          - ./back:/app
        labels:
          - traefik.enable=true
          - traefik.http.routers.fhback.rule=Host(`example.com`) && PathPrefix(`/`)
          - traefik.http.services.fhback.loadbalancer.server.port=8000
          - traefik.http.middlewares.firehouse-compress.compress=true
          - traefik.http.routers.fhback.middlewares=firehouse-compress,fhback-stripprefix
          - traefik.http.middlewares.testheader.headers.customresponseheaders.X-Test-Header=TraefikProxy
          - traefik.http.routers.fhback.middlewares=testheader
        depends_on:
          - traefik
    
    networks:
      default:
        external:
          name: traefik
    

    🗎 back/Dockerfile

    FROM python:3.11.4-slim
    
    COPY requirements.txt .
    
    RUN pip3 install --no-cache-dir -r requirements.txt
    
    WORKDIR /app
    
    COPY . /app
    
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    

    The static file is being baked straight into the image. You can use a volume mount to share static files from the host though.

    🗎 back/main.py

    from fastapi import FastAPI
    from fastapi.staticfiles import StaticFiles
    
    app = FastAPI()
    
    app.mount('/static', StaticFiles(directory='static'), name='static')
    
    @app.get("/")
    async def root():
        return {"message": "Hello World"}
    

    🗎 requirements.txt

    fastapi==0.109.2
    uvicorn==0.27.1
    

    To test:

    curl -v --resolve example.com:80:127.0.0.1 http://example.com/static/test.html
    

    I added a response header via the Traefik labels in docker-compose.yml. If you look at the output below you'll see that header, confirming that the response came via Traefik. You can also see it in the logs in the top panel of the screenshot.

    enter image description here