How to execute the Caddy API in Docker?

1. My Caddy version (caddy version):

Docker image caddy/caddy:alpine (2.0.0-beta.17)

2. How I run Caddy:

Docker Compose

a. System environment:

Ubuntu 18.04
Docker 19.03.8
docker-compose version 1.25.4

b. Command:

The image default command.

c. Service/unit/compose file:

version: "3.7"

services:
  caddy:
    container_name: caddy
    image: caddy/caddy:alpine
    restart: always
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./config:/config
      - ./data:/data
    ports:
      - "80:80"
      - "443:443"
    environment:
      - GHOST
      - ISSO
      - SHAARLI
networks:
  default:
    external:
      name: $DEFAULT_NETWORK

with .env file being:

GHOST=itsallsotireso.me
ISSO=marginalia.itsallsotireso.me
SHAARLI=normco.re
DEFAULT_NETWORK=firefly

d. My complete Caddyfile or JSON config:

        {
        http_port 80
        https_port 443
        experimental_http3
        debug
}
(secureheaders) {
        header {
                Strict-Transport-Security "max-age=31536000; includesubdomains; preload"
                X-Content-Type-Options nosniff
                X-Frame-Options SAMEORIGIN
                Referrer-Policy strict-origin-when-cross-origin
                X-Xss-Protection "1; mode=block"
                Feature-Policy "accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'self'$
                Expect-CT "max-age=604800"
        }
}
(robots) {
        header {
                X-Robots-Tag "noindex, nofollow, noarchive, nosnippet, notranslate, noimageindex"

        }
}
(proxy) {
        header_up X-Forwarded-Proto {scheme}
        header_up X-Forwarded-For {remote}
        header_up X-Real-IP {remote}
        header_down X-Powered-By "the Holy Spirit"
        header_down Server "CERN httpd"
}
(compression) {
        encode zstd gzip
}
www.{$GHOST}, www.{$SHAARLI} {
        redir * https://{http.request.host.labels.1}.{http.request.host.labels.0}{path} permanent
}

{$GHOST} {
        import secureheaders
        import robots
        reverse_proxy ghost:2368 {
                import proxy
        }
#       import compression
}

{$ISSO} {
        import secureheaders
        import robots
        reverse_proxy isso:8080 {
                header_down X-Powered-By "the Holy Spirit"
                header_down Server "CERN httpd"
import proxy
        }

}

{$SHAARLI} {
        import secureheaders
        import robots
        reverse_proxy shaarli:80 {
                import proxy
        }
        import compression
}

3. The problem I’m having:

After reading the documentation, I just want to learn and practice the Caddy API.

Using Caddy in Docker, I do not manage to execute the API commands.

For example:

docker exec caddy caddy run

I get:
run: loading initial config: loading new config: starting caddy administration endpoint: listen tcp 127.0.0.1:2019: bind: address already in use

or:

curl localhost:2019/config/

I get:
curl: (7) Failed to connect to localhost port 2019: Connection refused

4. Error messages and/or full log output:

see above

5. What I already tried:

In my docker-compose.yml, I tried to bind the port 2019:

ports:
  - "80:80"
  - "443:443"
  - "2019:2019"

Then:
curl localhost:2019/config/

I get:
curl: (56) Recv failure: Connection reset by peer

I also tried to change the Admin endpoint in my Caddyfile global options:

{
http_port 80
https_port 443
experimental_http3
debug
admin localhost:2019
}

then:
curl: (7) Failed to connect to localhost port 2019: Connection refused

I tried a different endpoint (2121), all the same

caddy | 2020/03/18 19:30:35.221 INFO admin admin endpoint started caddy | 2020/03/18 19:30:35.221 INFO admin admin endpoint started {"address": "localhost:2121", "enforce_origin": false, "origins": ["localhost:2121"]}

curl: (7) Failed to connect to localhost port 2121: Connection refused

I tried other things like caddy:2019, or the Docker internal IP, but nothing works.

It is weird because I use other commands with docker exec caddy caddy like fmt, validate or reload, and all work fine.

Any idea how I could use the API to work with the JSON? I would really love to learn and practice, thank you for the help.

Oh, and netstat -plnt | grep ':2019' returns nothing.

6. Links to relevant resources:

Not much, I googled the best I could and I found nothing helpful.

point is that 127.0.0.1 or localhost are inside docker container, so you either need to attach inside docker container shell to access localhost or to set admin endpoint to listen on 0.0.0.0

2 Likes

Sorry I am kind of a newbie. So, if I understand correctly, in my docker-compose.yml, I let “2019:2019” in the ports array, and in the Caddyfile I set admin 0.0.0.0:2019?

or if you want easier setup just for testing ( not for production ) just set container network to “host”, then you dont even need to map ports and you should be able to access all localhost ports inside conatainer.

2 Likes

Oh I did not think of that, I will try :slight_smile:

Actually I tried the method from my previous comment and it worked!

curl localhost:2019/config/

{"admin":{"listen":"0.0.0.0:2019"},"apps":{"http":{"http_port":80,"https_port":443,"servers":{"srv0":{"experimental_http3":true,"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"headers","response":{"set":{"Expect-Ct":["max-age=604800"],"Feature-Policy":["accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'self'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'self'; speaker 'none'; usb 'none';"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Strict-Transport-Security":["max-age=31536000; includesubdomains; preload"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"]}}},{"handler":"headers","response":{"set":{"X-Robots-Tag":["noindex, nofollow, noarchive, nosnippet, notranslate, noimageindex"]}}},{"handler":"reverse_proxy","headers":{"request":{"set":{"X-Forwarded-For":["{http.request.remote}"],"X-Forwarded-Proto":["{http.request.scheme}"],"X-Real-Ip":["{http.request.remote}"]}},"response":{"set":{"Server":["CERN httpd"],"X-Powered-By":["the Holy Spirit"]}}},"upstreams":[{"dial":"isso:8080"}]}]}]}],"match":[{"host":["marginalia.itsallsotireso.me"]}],"terminal":true},{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"static_response","headers":{"Location":["https://{http.request.host.labels.1}.{http.request.host.labels.0}{http.request.uri.path}"]},"status_code":301}]}]}],"match":[{"host":["www.itsallsotireso.me","www.normco.re"]}],"terminal":true},{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"headers","response":{"set":{"Expect-Ct":["max-age=604800"],"Feature-Policy":["accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'self'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'self'; speaker 'none'; usb 'none';"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Strict-Transport-Security":["max-age=31536000; includesubdomains; preload"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"]}}},{"handler":"headers","response":{"set":{"X-Robots-Tag":["noindex, nofollow, noarchive, nosnippet, notranslate, noimageindex"]}}},{"handler":"reverse_proxy","headers":{"request":{"set":{"X-Forwarded-For":["{http.request.remote}"],"X-Forwarded-Proto":["{http.request.scheme}"],"X-Real-Ip":["{http.request.remote}"]}},"response":{"set":{"Server":["CERN httpd"],"X-Powered-By":["the Holy Spirit"]}}},"upstreams":[{"dial":"ghost:2368"}]}]}]}],"match":[{"host":["itsallsotireso.me"]}],"terminal":true},{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"headers","response":{"set":{"Expect-Ct":["max-age=604800"],"Feature-Policy":["accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'self'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'self'; speaker 'none'; usb 'none';"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Strict-Transport-Security":["max-age=31536000; includesubdomains; preload"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"]}}},{"handler":"headers","response":{"set":{"X-Robots-Tag":["noindex, nofollow, noarchive, nosnippet, notranslate, noimageindex"]}}},{"encodings":{"gzip":{},"zstd":{}},"handler":"encode"},{"handler":"reverse_proxy","headers":{"request":{"set":{"X-Forwarded-For":["{http.request.remote}"],"X-Forwarded-Proto":["{http.request.scheme}"],"X-Real-Ip":["{http.request.remote}"]}},"response":{"set":{"Server":["CERN httpd"],"X-Powered-By":["the Holy Spirit"]}}},"upstreams":[{"dial":"shaarli:80"}]}]}]}],"match":[{"host":["normco.re"]}],"terminal":true}]}}}},"logging":{"logs":{"default":{"level":"DEBUG"}}}}

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