1. My Caddy version (caddy version
):
Docker image caddy/caddy:2.0.0-beta.19-alpine
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, though I do not use the Caddyfile (I configure Caddy loading a json with the API).
c. Service/unit/compose file:
version: "3.7"
services:
caddy:
container_name: caddy
image: caddy/caddy:alpine
restart: always
volumes:
- ./:/etc/caddy/
- ./config:/config
- ./data:/data
ports:
- "80:80"
- "443:443"
- "2019:2019"
networks:
default:
external:
name: firefly
d. My complete Caddyfile or JSON config:
{
"admin": {
"listen": "0.0.0.0:2019"
},
"apps": {
"http": {
"http_port": 80,
"https_port": 443,
"servers": {
"galactica": {
"experimental_http3": true,
"listen": [
":443"
],
"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": [
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode"
},
{
"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"
]
}
}
}
]
},
{
"handle": [
{
"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}"
],
"X-Script-Name": [
"/isso"
]
}
},
"response": {
"set": {
"Server": [
"CERN httpd"
],
"X-Powered-By": [
"the Holy Spirit"
]
}
}
},
"upstreams": [
{
"dial": "isso:8080"
}
]
}
],
"match": [
{
"path": [
"/isso*"
]
}
]
},
{
"handle": [
{
"handler": "headers",
"response": {
"set": {
"X-Robots-Tag": [
"index, follow, noarchive, snippet, translate, 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": [
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode"
},
{
"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": "shaarli:80"
}
]
}
],
"match": [
{
"host": [
"normco.re"
]
}
],
"terminal": true
}
],
"strict_sni_host": true,
"tls_connection_policies": [
{
"alpn": [
"h2"
],
"curves": [
"x25519",
"p521",
"p384",
"p256"
]
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuer": {
"email": "address@mail.com",
"module": "acme"
},
"key_type": "p384",
"must_staple": true
}
]
}
}
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
}
}
3. The problem I’m having:
Not a problem per se, just needing some inputs. I am still at a development stage, so I would like to experiment the cache module. I would like to see it on action, and this helps me practice the json config too.
Ghost is not the most efficient at serving static content, but since this is just a simple personal project, I do not want to bother adding nginx or Varnish for caching. I will just wait until Caddy is ready!
The documentation is a bit scarce, so I would like to get more info on how to configure this handler.
Basically, I have 1 domain which reverse proxies to 2 services:
- a Ghost blog instance (ghost:2368)
- an Isso instance (isso:8080) only on the path /isso/
I do not know if what my json config is the most efficient or elegant, but it works (any feedback on the structure is welcome btw!)
So what I would like to achieve with the cache module, is to cache everything in the Ghost instance, except the paths /p/ and /ghost/ (which are the post previews and the admin panel). Isso must not be cached.
4. Error messages and/or full log output:
No error yet, it is still just all theoretical for now.
5. What I already tried:
Below is the json I would like to load with the API with a cache handler.
I created a subroute (so that it does not apply to /isso/ reverse proxy) and added a negation match for /p/ and /ghost/
I do not know if the match will be OK like that, I made a try with a dummy header, just on /ghost/ and I could see that the header was not responded in the /ghost/ path. Maybe a path_regexp would be better to handle both /p/ and /ghost/?
I do not know what to value in self
and peers
. What kind of data should I put?
I also just notice that there is an issue with my whole route: encode comes after cache, and logically (and according to the doc) cache should be last. I will change this later but manipulating manually a json a is a bit tedious.
{
"admin": {
"listen": "0.0.0.0:2019"
},
"apps": {
"http": {
"http_port": 80,
"https_port": 443,
"servers": {
"galactica": {
"experimental_http3": true,
"listen": [
":443"
],
"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": [
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode"
}
]
},
{
"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-Robots-Tag": [
"noindex, nofollow, noarchive, nosnippet, notranslate, noimageindex"
],
"X-Xss-Protection": [
"1; mode=block"
]
}
}
},
{
"handler": "reverse_proxy",
"headers": {
"request": {
"set": {
"X-Forwarded-For": [
"{http.request.remote}"
],
"X-Forwarded-Proto": [
"{http.request.scheme}"
],
"X-Real-Ip": [
"{http.request.remote}"
],
"X-Script-Name": [
"/isso"
]
}
},
"response": {
"set": {
"Server": [
"CERN httpd"
],
"X-Powered-By": [
"the Holy Spirit"
]
}
}
},
"upstreams": [
{
"dial": "isso:8080"
}
]
}
],
"match": [
{
"path": [
"/isso*"
]
}
]
},
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"match": [
{
"not": {
"path": [
"/ghost/*"
],
"path": [
"/p/*"
]
}
}
],
"handle": [
{
"handler": "cache",
"self": "????",
"peers": [
"???"
],
"max_size": 512
}
]
}
]
},
{
"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-Robots-Tag": [
"index, follow, noarchive, snippet, translate, noimageindex"
],
"X-Xss-Protection": [
"1; mode=block"
]
}
}
},
{
"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": [
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode"
},
{
"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": "shaarli:80"
}
]
}
],
"match": [
{
"host": [
"normco.re"
]
}
],
"terminal": true
}
],
"strict_sni_host": true,
"tls_connection_policies": [
{
"alpn": [
"h2"
],
"curves": [
"x25519",
"p521",
"p384",
"p256"
]
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuer": {
"email": "address@mail.com",
"module": "acme"
},
"key_type": "p384",
"must_staple": true
}
]
}
}
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
}
}
6. Links to relevant resources:
Just the official doc! Not a lot of people are asking about the cache module yet, since it is a WIP.