Django static files 404

1. Caddy version (caddy version):

2.2.1

2. How I run Caddy:

It is one of the services in docker-compose

a. System environment:

Docker with docker-compose on Linux (Ubuntu)

b. Command:

docker-compose up -d

c. Service/unit/compose file:

version: "3.8"

services:
  db:
    image: postgres:13.1
    env_file:
      - /home/deployer/config_files/db.env
    volumes:
      - postgres_data:/var/lib/postgresql/data/

  api:
    image: myuser/myapiimage
    volumes:
      - static_volume:/app/static
      - /home/deployer/config_files/gunicorn.py:/app/gunicorn.py
      - /home/deployer/log_files:/app/log_files
    env_file:
      - /home/deployer/config_files/api.env
    expose:
      - 8000
    depends_on:
      - db

  caddy:
    image: caddy:2.2.1-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /home/deployer/config_files/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
      - static_volume:/app/static

volumes:
  postgres_data:
  static_volume:
  caddy_data:
  caddy_config:

d. My complete Caddyfile or JSON config:

api.example.com {
    root /static/* /app/static/
    encode zstd gzip
    @css {
        path_regexp .css
    }
    @js {
        path_regexp .js
    }
    header @css Content-Type text/css
    header @js Content-Type text/js
    @notStatic {
        not path /static/*
    }
    reverse_proxy @notStatic api:8000
    file_server
}

3. The problem I’m having:

Static files are not found (404). Everything else appears to be working except that the static files aren’t being served.

4. Error messages and/or full log output:

{"level":"error","ts":1612418398.8943849,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_addr":"123.123.123.123:52609","proto":"HTTP/2.0","method":"GET","host":"api.example.com","uri":"/static/admin/css/base.css","headers":{"Accept":["text/css,*/*;q=0.1"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Mode":["no-cors"],"Referer":["https://api.example.com/foobar/"],"Accept-Encoding":["gzip, deflate, br"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Cookie":["csrftoken=tokenhere"],"Sec-Fetch-Dest":["style"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"api.example.com"}},"common_log":"123.123.123.123 - - [04/Feb/2021:05:59:58 +0000] \"GET /static/admin/css/base.css HTTP/2.0\" 404 0","duration":0.000209203,"size":0,"status":404,"resp_headers":{"Server":["Caddy"],"Content-Type":["text/css"]}}

5. What I already tried:

I tried changing root /static/* /app/static/ to root * /app/static/.

I also tried chaging file_server to file_server /static/*.

I also tried removing the file_server from the last line. This resulted in the static files being retrieved but they were empty.

Before migrating to Caddy I was using Nginx. The following is my old Nginx config for the sake of completeness. At this stage my static files were working but I did not have ssl yet which is why I want to switch to Caddy:

upstream api {
    server api:8000;
}
server {
    listen 80;
    server_name api.example.com;
    location / {
        proxy_pass http://api;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }
    location /static/ {
        alias /app/static/;
    }
}

This is because if the request was left unhandled by Caddy, it will respond with a empty 200 response. The file wasn’t actually served, Caddy just said “yep, I did what I was configured to do, i.e. nothing!”

So the problem actually is that your request is /static/foo but your root is /app/static, so Caddy assembles the path by concatenating those together, so it’s looking in /app/static/static/foo.

I would write your config like this:

api.example.com {
	encode zstd gzip

	handle_path /static/* {
		root * /app/static
		file_server
	}

	handle {
		reverse_proxy api:8000
	}
}

The handle_path directive takes care of stripping the path of the prefix you specify, so it will remove /static; this makes sure there’s no doubling up.

Also, I left out the header lines, because Caddy’s file server should handle that automatically.

1 Like

Awesome, that works. Thank you.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.