How to get dockerised Caddy to use self-signed certs for local dev with php-fpm SPA (VueJs)

1. Caddy version (caddy version):

2 (latest)

2. How I run Caddy:

In this case I am running the official caddy:latest docker image. It is launched by a docker-compose.yml file:

a. System environment:

I’m running docker images on my MacBook Pro M1 (OS 12.4)
Docker Desktop for Mac v 4.10.1 (82475)

b. Command:

docker-compose up

c. Service/unit/compose file:

  caddy:
    image: caddy:latest
    restart: always
    volumes:
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/logs:/logs
      - ./php:/var/www/html
    ports:
      - "8888:80"
      - "8899:443"
    networks:
      - web-network

d. My complete Caddyfile or JSON config:

:80 {
    root * /var/www/html/mnr-be/webroot/
    encode gzip
    php_fastcgi php:9000
    file_server
}

I have a customised php-fpm7 docker image:

FROM php:7-fpm
WORKDIR /tmp
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer
RUN apt-get -y update && apt-get -y upgrade && apt-get install -y \
    libicu-dev \
    git \
    zip \
    unzip

RUN docker-php-source extract \
    && docker-php-ext-configure intl \
    && docker-php-ext-install -j$(nproc) intl \
    && docker-php-ext-configure pdo_mysql \
    && docker-php-ext-install -j$(nproc) pdo_mysql \
    && docker-php-source delete

Here’s the full docker-compose.yml file

version: "3.9"
networks:
  web-network:
services:

  caddy:
    image: caddy:latest
    restart: always
    volumes:
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/logs:/logs
      - ./php:/var/www/html
    ports:
      - "8888:80"
      - "8899:443"
    networks:
      - web-network

  php:
    build: ./php
    tty: true
    restart: always
    volumes:
      - ./php:/var/www/html
    networks:
      - web-network

  mariadb:
    image: mariadb
    restart: always
    volumes:
      - ./mariadb/data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_ROOT_HOST: localhost
      MYSQL_DATABASE: mnr_dev_db
      MYSQL_USER: mnr_dev
      MYSQL_PASSWORD: slfid9fe898
    ports:
      - "23306:3306"
    networks:
      - web-network

3. The problem I’m having:

This works on http protocol only. I’m trying to set something up along the lines of this article: Run Local Development Over HTTPS Using Caddy (Traditional Setup) | by Ahmed Shendy | Medium
I.e. a local dev environment, which serves a PHP back-end and a VueJS front-end via HTTPS. The reason I’m doing this is that due to recent changes to the webkit implementation of cookie security and CORS my local MAMP environment is permanently broken – even if I get SSL certs installed on both front-end and back-end domains, the browser refuses to propagate the session cookie. I can’t make it work. So I’m looking for a solution where the front-end and back-end are seen as being on the same domain and there are no CORS or cookie security problems between the two for the browser to choke on. Also if I can get this working then I can potentially deploy it as a production environment too.

4. Error messages and/or full log output:

There’s no log output. I just don’t really understand how to map what the article is doing onto what I’m doing… mainly because the article doesn’t deal with php-fpm, so it’s unclear to me how to merge the two Caddyfiles.

5. What I already tried:

I tried this:

frontend.foo.bar {
    tls ./certs/_wildcard.foo.bar.pem ./certs/_wildcard.foo.bar-key.pem
    reverse_proxy localhost:8080 {
        header_up Host                  "localhost"
        header_up X-Real-IP             {remote}
        header_up X-Forwarded-Host      "localhost"
        header_up X-Forwarded-Server    "localhost"
        header_up X-Forwarded-For       {port}
        header_up X-Forwarded-Proto     {scheme}
    }
}

backend.foo.bar {
    tls ./certs/_wildcard.foo.bar.pem ./certs/_wildcard.foo.bar-key.pem
    root * /var/www/html/mnr-be/webroot/
    encode gzip
    php_fastcgi php:9000 {
        header_up Host                                  {host}
        header_up Origin                                {host}
        header_up X-Real-IP                             {remote}
        header_up X-Forwarded-Host                      {host}
        header_up X-Forwarded-Server                    {host}
        header_up X-Forwarded-Port                      {port}
        header_up X-Forwarded-For                       {remote}
        header_up X-Forwarded-Proto                     {scheme}
        header_down Access-Control-Allow-Origin         https://frontend.foo.bar
        header_down Access-Control-Allow-Credentials    true
    }
    file_server
}

The docker-compose up runs without error… but the virtual hosts yield:
ERR_CONNECTION_REFUSED

There are a few questions in my mind :

  1. I have php-fpm on port 80… the article is using a reverse proxy for the back-end. I’m not sure how to reconcile that… does their TLS solution require a reverse proxy? If so how to do that for php-fpm? If not then how to make the cert work with my php-fpm?
  2. The article is showing how to generate and install certs on the host machine but I’m not sure how that will map to docker.
  3. The article shows an /etc/hosts entry – is that relevant to a docker-based environment?

6. Links to relevant resources:

1 Like

Remove all of this. Caddy sets the appropriate headers automatically. See the docs:

You should use the header directive instead for this. The difference is that the header_down subdirective will only apply for requests that actually get proxied to your PHP app, whereas header will apply for all requests, including ones handled by file_server.

I don’t think you need to generate your certs outside of Caddy. You can use Caddy’s tls internal to have it use a local CA, then you can grab the root CA cert from ./caddy/data/caddy/pki/authorities/local/root.crt and install it in your browser/system’s trust store.

How are you making the request? Show us with curl -v what you’re trying and what you get.

If the domain you’re using doesn’t exist in public DNS, then you need to make your system resolve that domain to some IP address. So yes, possibly. But you could just use something like backend.localhost and frontend.localhost instead, since *.localhost should always resolve to ::1 or 127.0.0.1 on most machines (maybe Macs don’t, idk if they do that or not).

2 Likes

Based on your advice, I tried the following:

mnr-fe.localhost {
    tls internal
    reverse_proxy localhost:8080
}

mnr-be.localhost {
    tls internal
    root * /var/www/html/mnr-be/webroot/
    encode gzip
    php_fastcgi php:9000 {
        header Access-Control-Allow-Origin         https://mnr-fe.localhost
        header Access-Control-Allow-Credentials    true
    }
    file_server
}

This yields an error:

mnr-caddy-docker-caddy-1    | run: adapting config using caddyfile: parsing caddyfile tokens for 'php_fastcgi': /etc/caddy/Caddyfile:29 - Errorduring parsing: unrecognized subdirective header
mnr-caddy-docker-caddy-1 exited with code 1

If I remove the bit in braces, then caddy launches without errors, but obviously my CORS headers won’t be set.

I tried this:

mnr-fe.localhost {
    tls internal
    reverse_proxy localhost:8080
}

mnr-be.localhost {
    tls internal
    root * /var/www/html/mnr/be/
    encode gzip
    php_fastcgi php:9000 
    header Access-Control-Allow-Origin         https://mnr-fe.localhost
    header Access-Control-Allow-Credentials    true
    file_server
}

Which launches without errors but browsing in the browser to mnr-fe.localhost, mnr-be.localhost, https://mnr-fe.localhost or https://mnr-be.localhost yields the same old error:

ERR_CONNECTION_REFUSED

How are you making the request?

Like I said, please use curl -v to make the request, and show the output you see. Browser errors don’t give any usable information. Using curl in verbose mode will show more clearly what’s going on.

Keep in mind, you bound ports 8888 and 8899 to your host machine, not 80/443, so you must specify the port when trying to connect (or just change your port mapping to 80:80 and 443:443, which would be much better).

1 Like

Ok I will try that. My front-end when in dev mode runs using a node server and the config requires the certs so that it can run in https. The config looks like this:

    devServer: {
      https: true,
      port: 8080,
      open: true, // opens browser window automatically
      cert:
        "~/repo/github/mnr-caddy-docker/caddy/data/caddy/pki/authorities/local/root.crt",
      key:
        "~/repo/github/mnr-caddy-docker/caddy/data/caddy/pki/authorities/local/root.key"
    },

Are those the correct certs for the front-end?

I’m guessing the answer is no because it generates an error:

(node:2820) UnhandledPromiseRejectionWarning:   Error: error:0909006C:PEM routines:get_name:no start line
(node:2847) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function

I tried the intermediate cert and key instead and I get the same error. I believe it’s this that is causing the ERR_CONNECTION_REFUSED error, because if I remove the cert and key, then it connects (albeit with the usual insecure warnings).

You don’t need to use HTTPS in Node if you have Caddy proxying to it, because Caddy will terminate TLS.

But no, that’s not the right certificate to use. That’s the root CA certificate, it should never be used directly to serve TLS connections. Google “chain of trust PKI” if you want to better understand.

Ok. Interesting. What I am failing to understand is the port linkages between docker-compose and caddy.

My docker-compose.yml file is above. You’ve suggested 80:80 and 443:443 instead. But then in the Caddy file how does mnr-fe.localhost connect to that? Would I then also modify the Caddyfile to be reverse_proxy localhost:80?

The default HTTP port is 80, and the default HTTPS port is 443. When you type http:// in your browser, it uses port 80, for example.

Caddy is HTTPS by default, so if you just enter a domain name as a site address, with no scheme or port, Caddy will listen on port 443 for HTTPS, and set up HTTP->HTTPS redirects to make sure all clients actually use HTTPS.

localhost inside Docker means “the current container”. So trying to proxy to localhost would have Caddy connect to itself (infinite loop). So don’t do that.

You should be proxying to another service. Docker’s built-in DNS resolver will resolve your docker-compose service names to the IP address of their running containers. So use the service name of the service you want to proxy to.

Ok tx. Some of that went over my head.

I have this now in my docker-compose.yml:

...
  caddy:
    image: caddy:latest
    restart: always
    volumes:
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/logs:/logs
      - ./mnr:/var/www/html
    ports:
      - "80:80"
      - "443:443"
    networks:
      - web-network

And this in Caddyfile:

...
mnr-fe.localhost {
    tls internal
    reverse_proxy localhost:8080
}

My front-end dev server reports this:

 DONE  Compiled successfully in 18870ms                                                                                               5:42:02 PM


 N  App dir........... /Users/noeldacosta/repo/github/mnr-fe-fix-devtools
    App URL........... http://localhost:8080/
    Dev mode.......... spa
    Pkg quasar........ v1.14.1
    Pkg @quasar/app... v2.1.1
    Transpiled JS..... yes (Babel)

If I browse to http://localhost:8080/ I get the front-end (but obviously on the wrong domain for the SPA to work).

If I browse to https://mnr-fe.localhost I get a 502 error. Via curl as you suggested, I get the following:

% curl -v https://mnr-fe.localhost
*   Trying 127.0.0.1:443...
* Connected to mnr-fe.localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.


If I then tell the system to trust the certificate, I still get a 502 error in the browser:

And via curl I then get the following:

 % curl -v https://mnr-fe.localhost
*   Trying 127.0.0.1:443...
* Connected to mnr-fe.localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: [NONE]
*  start date: Aug 11 11:28:21 2022 GMT
*  expire date: Aug 11 23:28:21 2022 GMT
*  subjectAltName: host "mnr-fe.localhost" matched cert's "mnr-fe.localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x12a80f400)
> GET / HTTP/2
> Host: mnr-fe.localhost
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 502
< server: Caddy
< content-length: 0
< date: Thu, 11 Aug 2022 17:07:14 GMT
<
* Connection #0 to host mnr-fe.localhost left intact

In Firefox is looks thusly:


And if I click continue then I get a blank screen with 502 errors in the console:

For the sake of just trying to get this to work (and also likely as it will be during development as opposed to when deployed to a remote environment), my front-end is not running as a docker service. It’s just running as a local VueJS dev server (i.e. served by node running locally). My expectation is that the dockerized caddy will still be able to reverse_proxy to this local Dev server.

I should also point out that even on production, it’s likely that the front-end will be static files, not a docker container. Locally on my dev env as I mentioned above, I would likely be running an express server on the front-end, not docker, but the back-end would be docker. Although if I could figure out how to run the front-end in docker too that would be useful in certain circumstances.

We figured this out out-of-band on a call :+1:

(Thanks for the sponsorship, @geoidesic!)

2 Likes

We did but this morning when I spun it up again I got an error:

mnr-caddy-docker-caddy-1  | {"level":"error","ts":1661237166.5890849,"logger":"http.log.error","msg":"dial tcp 127.0.0.1:8080: connect: connection refused","request":{"remote_ip":"172.20.0.1","remote_port":"61632","proto":"HTTP/2.0","method":"GET","host":"mnr-fe.localhost","uri":"/","headers":{"Sec-Fetch-Site":["none"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"macOS\""],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Accept-Encoding":["gzip, deflate, br"],"Cache-Control":["max-age=0"],"Sec-Ch-Ua":["\"Chromium\";v=\"104\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"104\""],"Upgrade-Insecure-Requests":["1"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mnr-fe.localhost"}},"duration":0.001468875,"status":502,"err_id":"h8xag5xcf","err_trace":"reverseproxy.statusError (reverseproxy.go:1184)"}

Here’s the curl

% curl -v mnr-fe.localhost
*   Trying 127.0.0.1:80...
* Connected to mnr-fe.localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: mnr-fe.localhost
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://mnr-fe.localhost/
< Server: Caddy
< Date: Tue, 23 Aug 2022 06:47:50 GMT
< Content-Length: 0
<
* Closing connection 0

And as you can see, the app is listening on port 8080

 DONE  Compiled successfully in 19186ms                                                                                               7:40:51 AM


 N  App dir........... /Users/noeldacosta/repo/github/mnr-fe-fix-devtools
    App URL........... http://localhost:8080/
    Dev mode.......... spa
    Pkg quasar........ v1.14.1
    Pkg @quasar/app... v2.1.1
    Transpiled JS..... yes (Babel)

ℹ 「wds」: Project is running at http://0.0.0.0:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: 404s will fallback to /index.html
 App · Opening default browser at http://localhost:8080/

That looks correct; you’re making an HTTP request and Caddy is responding with an HTTP->HTTPS redirect.

Make sure to make your request with https://, or use the -L flag to follow the Location header for redirects.

The key is though… is this with Caddy inside Docker? If so, from Caddy’s perspective, 127.0.0.1 means “connect to something within the same container”. Containers are isolated (that’s the point) by default, so it won’t “see” things on your host machine as being localhost.

There’s a bunch of workarounds, but I’ll wait until you confirm before suggesting anything.

1 Like

Yes, this is with Caddy inside Docker.

Here’s the curl with https:// and -L:

 % curl -vL https://mnr-fe.localhost
*   Trying 127.0.0.1:443...
* Connected to mnr-fe.localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: [NONE]
*  start date: Aug 23 06:32:45 2022 GMT
*  expire date: Aug 23 18:32:45 2022 GMT
*  subjectAltName: host "mnr-fe.localhost" matched cert's "mnr-fe.localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x12480f400)
> GET / HTTP/2
> Host: mnr-fe.localhost
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 502
< server: Caddy
< content-length: 0
< date: Tue, 23 Aug 2022 06:56:18 GMT
<
* Connection #0 to host mnr-fe.localhost left intact

Then that’s exactly the issue.

You can have Caddy connect to the host machine (which is annoyingly tricky, this solution should work everywhere on new versions of Docker I think but I don’t use a Mac so I can’t confirm) by adding this to your docker-compose.yml for the caddy service:

    extra_hosts:
        host.docker.internal: host-gateway

Then connect to the host with reverse_proxy host.docker.internal:8080

Alternatively, run Caddy in network_mode: host mode which will completely get rid of Docker networking semantics, but that has plenty of downsides, means you can’t as easily connect to other containers that aren’t also using host mode.

1 Like

Thanks Francis, that seems to work on Mac too!
So I am now seeing my front-end on the mnr-fe.localhost domain and the back-end on mnr-be.localhost. Hopefully plain sailing from here.

1 Like

Aaargh. This was working this morning. It has stopped working and I don’t know why. I’m getting 502 bad gateway again in Chrome:

mnr-fe.localhost is currently unable to handle this request.
HTTP ERROR 502

Caddyfile

{"level":"error","ts":1661273616.249677,"logger":"http.log.error","msg":"EOF","request":{"remote_ip":"172.20.0.1","remote_port":"61764","proto":"HTTP/2.0","method":"GET","host":"mnr-fe.localhost","uri":"/","headers":{"Cache-Control":["max-age=0"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Ch-Ua":["\"Chromium\";v=\"104\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"104\""],"Sec-Ch-Ua-Mobile":["?0"],"Upgrade-Insecure-Requests":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["none"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mnr-fe.localhost"}},"duration":0.002274958,"status":502,"err_id":"67yte0exd","err_trace":"reverseproxy.statusError (reverseproxy.go:1184)"}

Curl

 % curl -Lv https://mnr-fe.localhost
*   Trying 127.0.0.1:443...
* Connected to mnr-fe.localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: [NONE]
*  start date: Aug 23 14:33:28 2022 GMT
*  expire date: Aug 24 02:33:28 2022 GMT
*  subjectAltName: host "mnr-fe.localhost" matched cert's "mnr-fe.localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x14f811200)
> GET / HTTP/2
> Host: mnr-fe.localhost
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 502
< server: Caddy
< content-length: 0
< date: Tue, 23 Aug 2022 16:51:08 GMT
<
* Connection #0 to host mnr-fe.localhost left intact

Here’s the docker-compose.yml file:

version: "3.9"
networks:
  web-network:
services:
  caddy:
    image: caddy:latest
    restart: always
    volumes:
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/logs:/logs
      - ./mnr:/var/www/html
    ports:
      - "80:80"
      - "443:443"
    networks:
      - web-network
    # this bit allows caddy on docker to see the listener on the external (to docker) port 8080 (which listens locally on my laptop)
    extra_hosts:
      host.docker.internal: host-gateway
  php:
    build: ./php
    tty: true
    restart: always
    volumes:
      - ./mnr:/var/www/html
    networks:
      - web-network

  mysql:
    image: mysql/mysql-server:latest-aarch64
    restart: always
    volumes:
      - ./mysql/data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_ROOT_HOST: localhost
      MYSQL_DATABASE: mnr_dev_db
      MYSQL_USER: mnr_dev
      MYSQL_PASSWORD: slfid9fe898
    ports:
      - "23306:3306"
    networks:
      - web-network