Caddy-Flask-Docker static/templates issues

1. Caddy version (caddy version):

v2.4.6 h1:HGkGICFGvyrodcqOOclHKfvJC0qTU7vny/7FhYp9hNw=

2. How I run Caddy:

I run caddy inside docker to serve a Flask application.

Docker is runing on my home network, from a local computer with local ip 192.168.1.100.
I have a domain name (my_domain.com below) that is linked to my public ip.
Both ports 443 and 80 are forwarded to 192.168.1.100 on my router.

a. System environment:

Debian 11.
Docker

b. Command:

sudo docker-compose up --build

c. Service/unit/compose file:

version: '3'

services:
 caddy:
         image: caddy:latest
         restart: unless-stopped
         ports:
                 - "80:80"
                 - "443:443"
         volumes:
                 - ./Caddyfile:/etc/caddy/Caddyfile
                 - caddy_data:/data
                 - caddy_config:/config
 app:
    build: ./
    volumes:
      - ./app:/app
    container_name: app 
    environment:
      - FLASK_APP=main.py
    links:
            - caddy

volumes:
        caddy_data:
        caddy_config: 

d. My complete Caddyfile or JSON config:

https://my_domain.com, http://my_domain.com  {
    log
    reverse_proxy app:8080
}

app.localhost {
    log
    tls internal
    reverse_proxy app:8080
}

3. The problem I’m having:

Flask app home page (https://my_domain.com) renders the right html file (app/templates/home.html) but cannot display static files (e.g., app/static/background.jpg). Also, when trying to access new route (e.g.,https://my_domain.com/contact), a blank page is displayed (e.g., instead of app/templates/contact.html). 

4. Error messages and/or full log output:

caddy_1  | {"level":"info","ts":1637721946.1761045,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"my_Public_IP:60154","proto":"HTTP/2.0","method":"GET","host":"my_domain.com","uri":"/static/background.jpeg","headers":{"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"],"Accept":["image/webp,*/*"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://my_domain.com/"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"my_domain.com"}},"common_log":"my_Public_IP - - [24/Nov/2021:02:45:46 +0000] \"GET /static/background.jpeg HTTP/2.0\" 0 0","user_id":"","duration":0.000007635,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}
caddy_1  | {"level":"info","ts":1637722274.63188,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"my_Public_IP:34476","proto":"HTTP/2.0","method":"GET","host":"my_domain.com","uri":"/contact","headers":{"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://my_domain.com/"],"Upgrade-Insecure-Requests":["1"],"Cache-Control":["max-age=0"],"Te":["trailers"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"my_domain.com"}},"common_log":"my_Public_IP - - [24/Nov/2021:02:51:14 +0000] \"GET /contact HTTP/2.0\" 0 0","user_id":"","duration":0.000027359,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}

5. What I already tried:

(1) check apps runs in docker without caddy (successfull)
(2) ckeck that all files have been copied in app image (docker image inspect)
(3) route directive
(4) handle directive

6. Links to relevant resources:

Did try this:
https://caddy.community/t/caddy-fails-to-serve-static-files/13244

The logs I’m seeing don’t seem to align with the config you gave.

Please don’t redact your config, please paste it as-is. All characters are important and relevant.

Here is the log. Cheers

Building app
Sending build context to Docker daemon  43.41MB
Step 1/7 : FROM tiangolo/uwsgi-nginx-flask:python3.8
 ---> d85a43ce9487
Step 2/7 : ENV LISTEN_PORT 8080
 ---> Using cache
 ---> 2baa76b377a0
Step 3/7 : EXPOSE 8080
 ---> Using cache
 ---> cf70a848d594
Step 4/7 : COPY ./deps/pygslib-0.0.0.6.0.0-cp38-cp38-linux_x86_64.whl .
 ---> Using cache
 ---> 4ee9beb6315b
Step 5/7 : RUN  apt-get update && apt-get install -y         gfortran     python3-pip &&     rm -rf /var/lib/apt/lists/* &&     pip install --no-cache-dir --upgrade pip  &&     pip install flask-sqlalchemy &&     pip3 install wtforms==2.3.3 &&     pip3 install flask==1.1.2         flask-security         email_validator &&     pip3 install tabula-py   &&     pip3 install compoda &&     pip3 install Flask-Security-Too 
 ---> Using cache
 ---> 19afbb003dd2
Step 6/7 : ENV STATIC_INDEX 0
 ---> Using cache
 ---> 33da3a0cb433
Step 7/7 : COPY ./app /app
 ---> Using cache
 ---> 3cc24d576e3a
Successfully built 3cc24d576e3a
Successfully tagged myapp_app:latest
Starting myapp_caddy_1 ... done
Starting app            ... done
Attaching to myapp_caddy_1, app
app      | Checking for script in /app/prestart.sh
app      | There is no script /app/prestart.sh
caddy_1  | {"level":"info","ts":1637724747.7626176,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
caddy_1  | {"level":"warn","ts":1637724747.7642944,"msg":"input is not formatted with 'caddy fmt'","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
caddy_1  | {"level":"info","ts":1637724747.765477,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}
caddy_1  | {"level":"info","ts":1637724747.7773254,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
caddy_1  | {"level":"info","ts":1637724747.7773726,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
caddy_1  | {"level":"info","ts":1637724747.7774303,"logger":"http","msg":"server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server","server_name":"srv1","http_port":80}
caddy_1  | {"level":"info","ts":1637724747.7856658,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0003982a0"}
caddy_1  | {"level":"info","ts":1637724747.8175814,"logger":"pki.ca.local","msg":"root certificate is already trusted by system","path":"storage:pki/authorities/local/root.crt"}
caddy_1  | {"level":"info","ts":1637724747.817819,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/data/caddy"}
caddy_1  | {"level":"info","ts":1637724747.8178656,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["My_Domain.com","app.localhost"]}
caddy_1  | {"level":"info","ts":1637724747.8186505,"logger":"tls","msg":"finished cleaning storage units"}
caddy_1  | {"level":"warn","ts":1637724747.8194544,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [app.localhost]: no OCSP server specified in certificate"}
caddy_1  | {"level":"info","ts":1637724747.8196726,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
caddy_1  | {"level":"info","ts":1637724747.8196893,"msg":"serving initial configuration"}
app      | /usr/lib/python2.7/dist-packages/supervisor/options.py:461: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
app      |   'Supervisord is running as root and it is searching '
app      | 2021-11-24 03:32:28,317 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  If you intend to run as root, you can set user=root in the config file to avoid this message.
app      | 2021-11-24 03:32:28,317 INFO Included extra file "/etc/supervisor/conf.d/supervisord.conf" during parsing
app      | Unlinking stale socket /var/run/supervisor.sock
app      | 2021-11-24 03:32:28,626 INFO RPC interface 'supervisor' initialized
app      | 2021-11-24 03:32:28,626 CRIT Server 'unix_http_server' running without any HTTP authentication checking
app      | 2021-11-24 03:32:28,626 INFO supervisord started with pid 1
app      | 2021-11-24 03:32:29,628 INFO spawned: 'quit_on_failure' with pid 10
app      | 2021-11-24 03:32:29,630 INFO spawned: 'nginx' with pid 11
app      | 2021-11-24 03:32:29,632 INFO spawned: 'uwsgi' with pid 12
app      | 2021-11-24 03:32:29,635 INFO success: nginx entered RUNNING state, process has stayed up for > than 0 seconds (startsecs)
app      | 2021-11-24 03:32:29,635 INFO success: uwsgi entered RUNNING state, process has stayed up for > than 0 seconds (startsecs)
app      | [uWSGI] getting INI configuration from /app/uwsgi.ini
app      | [uWSGI] getting INI configuration from /etc/uwsgi/uwsgi.ini
app      | 
app      | ;uWSGI instance configuration
app      | [uwsgi]
app      | cheaper = 2
app      | processes = 16
app      | ini = /app/uwsgi.ini
app      | module = main
app      | callable = app
app      | ini = /etc/uwsgi/uwsgi.ini
app      | socket = /tmp/uwsgi.sock
app      | chown-socket = nginx:nginx
app      | chmod-socket = 664
app      | hook-master-start = unix_signal:15 gracefully_kill_them_all
app      | need-app = true
app      | die-on-term = true
app      | show-config = true
app      | ;end of configuration
app      | 
app      | *** Starting uWSGI 2.0.20 (64bit) on [Wed Nov 24 03:32:29 2021] ***
app      | compiled with version: 8.3.0 on 26 October 2021 17:01:27
app      | os: Linux-5.10.0-9-amd64 #1 SMP Debian 5.10.70-1 (2021-09-30)
app      | nodename: e37dffe1667c
app      | machine: x86_64
app      | clock source: unix
app      | pcre jit disabled
app      | detected number of CPU cores: 4
app      | current working directory: /app
app      | detected binary path: /usr/local/bin/uwsgi
app      | your memory page size is 4096 bytes
app      | detected max file descriptor number: 1048576
app      | lock engine: pthread robust mutexes
app      | thunder lock: disabled (you can enable it with --thunder-lock)
app      | uwsgi socket 0 bound to UNIX address /tmp/uwsgi.sock fd 3
app      | uWSGI running as root, you can use --uid/--gid/--chroot options
app      | *** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** 
app      | Python version: 3.8.12 (default, Oct 13 2021, 09:22:51)  [GCC 8.3.0]
app      | *** Python threads support is disabled. You can enable it with --enable-threads ***
app      | Python main interpreter initialized at 0x562ab49f03a0
app      | uWSGI running as root, you can use --uid/--gid/--chroot options
app      | *** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** 
app      | your server socket listen backlog is limited to 100 connections
app      | your mercy for graceful operations on workers is 60 seconds
app      | mapped 1239640 bytes (1210 KB) for 16 cores
app      | *** Operational MODE: preforking ***
app      | 2021-11-24 03:32:30,672 INFO success: quit_on_failure entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
app      | /usr/local/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py:872: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
app      |   warnings.warn(FSADeprecationWarning(
app      | WSGI app 0 (mountpoint='') ready in 2 seconds on interpreter 0x562ab49f03a0 pid: 12 (default app)
app      | uWSGI running as root, you can use --uid/--gid/--chroot options
app      | *** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** 
app      | *** uWSGI is running in multiple interpreter mode ***
app      | spawned uWSGI master process (pid: 12)
app      | spawned uWSGI worker 1 (pid: 18, cores: 1)
app      | spawned uWSGI worker 2 (pid: 19, cores: 1)
app      | running "unix_signal:15 gracefully_kill_them_all" (master-start)...
app      | [pid: 18|app: 0|req: 1/1] 172.19.0.2 () {44 vars in 664 bytes} [Wed Nov 24 03:33:05 2021] GET / => generated 5252 bytes in 19 msecs (HTTP/1.1 200) 2 headers in 81 bytes (1 switches on core 0)
app      | 172.19.0.2 - - [24/Nov/2021:03:33:05 +0000] "GET / HTTP/1.1" 200 5252 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0" "My_Public_IP"
caddy_1  | {"level":"info","ts":1637724785.6960702,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"My_Public_IP:54716","proto":"HTTP/2.0","method":"GET","host":"My_Domain.com","uri":"/","headers":{"Accept-Encoding":["gzip, deflate, br"],"Upgrade-Insecure-Requests":["1"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"My_Domain.com"}},"common_log":"My_Public_IP - - [24/Nov/2021:03:33:05 +0000] \"GET / HTTP/2.0\" 200 5252","user_id":"","duration":0.024437424,"size":5252,"status":200,"resp_headers":{"Server":["Caddy","nginx/1.21.1"],"Date":["Wed, 24 Nov 2021 03:33:05 GMT"],"Content-Type":["text/html; charset=utf-8"],"Content-Length":["5252"]}}
caddy_1  | {"level":"info","ts":1637724785.7362423,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"My_Public_IP:54716","proto":"HTTP/2.0","method":"GET","host":"My_Domain.com","uri":"/static/LOGO/CMJN/jpg/rouge%20et%20bleu.jpg","headers":{"Accept":["image/webp,*/*"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://My_Domain.com/"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"My_Domain.com"}},"common_log":"My_Public_IP - - [24/Nov/2021:03:33:05 +0000] \"GET /static/LOGO/CMJN/jpg/rouge%20et%20bleu.jpg HTTP/2.0\" 0 0","user_id":"","duration":0.000025508,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}
caddy_1  | {"level":"info","ts":1637724785.7416828,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"My_Public_IP:54716","proto":"HTTP/2.0","method":"GET","host":"My_Domain.com","uri":"/static/logoplot.png","headers":{"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"],"Accept":["image/webp,*/*"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://My_Domain.com/"],"Te":["trailers"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"My_Domain.com"}},"common_log":"My_Public_IP - - [24/Nov/2021:03:33:05 +0000] \"GET /static/logoplot.png HTTP/2.0\" 0 0","user_id":"","duration":0.000019182,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}
caddy_1  | {"level":"info","ts":1637724785.7419143,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"My_Public_IP:54716","proto":"HTTP/2.0","method":"GET","host":"My_Domain.com","uri":"/static/background.jpeg","headers":{"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"],"Accept":["image/webp,*/*"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://My_Domain.com/"],"Te":["trailers"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"My_Domain.com"}},"common_log":"My_Public_IP - - [24/Nov/2021:03:33:05 +0000] \"GET /static/background.jpeg HTTP/2.0\" 0 0","user_id":"","duration":0.000020452,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}
caddy_1  | {"level":"info","ts":1637724788.2324994,"logger":"http.log.access","msg":"handled request","request":{"remote_addr":"My_Public_IP:54716","proto":"HTTP/2.0","method":"GET","host":"My_Domain.com","uri":"/contact","headers":{"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://My_Domain.com/"],"Upgrade-Insecure-Requests":["1"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"My_Domain.com"}},"common_log":"My_Public_IP - - [24/Nov/2021:03:33:08 +0000] \"GET /contact HTTP/2.0\" 0 0","user_id":"","duration":0.000009198,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}

What about your Caddyfile config? I think it’s a config problem, but I think you probably accidentally hid the problem by redacting it.

Specifically, my guess is that your site address probably had a trailing / at the end of it, which would cause Caddy to set up a path matcher. Path matching is exact in Caddy, so it would only match requests to exactly / and nothing else, and everything else would fall through unhandled, resulting in an empty response.

1 Like

Well spoted mate! Removing the trailing / worked perfectly!
Thanks a lot for the prompt reponses !

FYI:
My original Caddyfile file was as below:

https://my_domain.com/, http://my_domain.com/ {
log
reverse_proxy app:8080
}

app.localhost {
log
tls internal
reverse_proxy app:8080
}

1 Like