Expose static files from FastAPI app behind Traefik proxy

70 views Asked by At

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?

1

There are 1 answers

5
datawookie On BEST ANSWER
├── 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