2020-04-27 21:04:28 +00:00
|
|
|
# Caddy v2 Reverse Proxy
|
|
|
|
|
2020-05-18 22:49:18 +00:00
|
|
|
###### guide-by-example
|
2020-04-27 21:04:28 +00:00
|
|
|
|
|
|
|
![logo](https://i.imgur.com/xmSY5qu.png)
|
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
|
2020-05-14 22:10:24 +00:00
|
|
|
1. [Purpose & Overview](#Purpose--Overview)
|
2020-05-14 22:08:37 +00:00
|
|
|
2. [Caddy as a reverse proxy in docker](#Caddy-as-a-reverse-proxy-in-docker)
|
|
|
|
3. [Caddy more info and various configurations](#Caddy-more-info-and-various-configurations)
|
|
|
|
4. [Caddy DNS challenge](#Caddy-DNS-challenge)
|
2020-05-14 22:07:29 +00:00
|
|
|
|
|
|
|
# Purpose & Overview
|
|
|
|
|
2022-10-22 10:48:08 +00:00
|
|
|
Reverse proxy is needed if one wants access to services based on the hostname.<br>
|
|
|
|
For example `nextcloud.example.com` points traffic to nextcloud docker container,
|
|
|
|
while `jellyfin.example.com` points to the media server on the network.
|
2020-05-14 22:07:29 +00:00
|
|
|
|
2020-05-14 06:36:21 +00:00
|
|
|
* [Official site](https://caddyserver.com/v2)
|
|
|
|
* [Official documentation](https://caddyserver.com/docs/)
|
|
|
|
* [Forum](https://caddy.community/)
|
|
|
|
* [Github](https://github.com/caddyserver/caddy)
|
|
|
|
|
2022-10-22 10:48:08 +00:00
|
|
|
Caddy is a pretty damn good web server with automatic HTTPS. Written in Go.<br>
|
2020-05-14 22:07:29 +00:00
|
|
|
Web servers are build to deal with http traffic, so they are an obvious choice
|
2022-10-22 10:48:08 +00:00
|
|
|
for the function of reverse proxy.<br>
|
2020-05-14 22:07:29 +00:00
|
|
|
In this setup Caddy is used mostly as
|
2022-10-22 10:48:08 +00:00
|
|
|
[a TLS termination proxy](https://www.youtube.com/watch?v=H0bkLsUe3no).
|
2020-05-14 22:07:29 +00:00
|
|
|
Https encrypted tunel ends with it, so that the traffic can be analyzed
|
2021-03-02 22:44:33 +00:00
|
|
|
and send to a correct webserver based on the settings in `Caddyfile`.
|
2020-05-14 22:07:29 +00:00
|
|
|
|
2021-02-22 22:25:12 +00:00
|
|
|
Caddy with its build-in https and and simple config approach
|
|
|
|
allows even most trivial configs to just work:
|
2020-05-14 22:07:29 +00:00
|
|
|
|
|
|
|
```
|
2022-10-22 10:48:08 +00:00
|
|
|
nextcloud.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy nextcloud-web:80
|
2020-05-14 22:07:29 +00:00
|
|
|
}
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2022-10-22 10:48:08 +00:00
|
|
|
jellyfin.{$MY_DOMAIN} {
|
2020-05-14 22:07:29 +00:00
|
|
|
reverse_proxy 192.168.1.20:80
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-10-22 10:59:22 +00:00
|
|
|
And **just works** means fully works. No additional configuration needed
|
2022-10-22 10:48:08 +00:00
|
|
|
for https redirect, or special services if target is not a container,
|
|
|
|
or need to deal with load balancer, or need to add boilerplate headers
|
|
|
|
for x-forward, or other extra work.<br>
|
2022-10-22 11:01:07 +00:00
|
|
|
In short, it has great out of the box defaults, fitting majority of uses
|
|
|
|
and only some special casess with extra functionality need extra work.
|
2022-10-22 10:48:08 +00:00
|
|
|
|
|
|
|
![url](https://i.imgur.com/iTjzPc0.png)
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
# Caddy as a reverse proxy in docker
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
Caddy will be running as a docker container and will route traffic to other containers,
|
2020-04-30 18:59:06 +00:00
|
|
|
or machines on the network.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Requirements
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-23 11:48:14 +00:00
|
|
|
* have some basic linux knowledge, create folders, create files, edit files, run scripts,...
|
2020-04-28 23:50:40 +00:00
|
|
|
* have a docker host and some vague docker knowledge
|
|
|
|
* have port 80 and 443 forwarded on the router/firewall to the docker host
|
2022-10-22 10:48:08 +00:00
|
|
|
* have a domain, `example.com`, you can buy one for 2€ annually on namecheap
|
|
|
|
* have correctly set type-A DNS records pointing at your public IP address,
|
|
|
|
[switching to Cloudflare](https://youtu.be/XQKkb84EjNQ) for DNS managment is recommended
|
2020-04-27 21:04:28 +00:00
|
|
|
|
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Files and directory structure
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
2020-04-30 21:48:24 +00:00
|
|
|
/home/
|
|
|
|
└── ~/
|
|
|
|
└── docker/
|
|
|
|
└── caddy/
|
|
|
|
├── config/
|
|
|
|
├── data/
|
|
|
|
├── .env
|
|
|
|
├── Caddyfile
|
|
|
|
└── docker-compose.yml
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-05 15:39:05 +00:00
|
|
|
* `config/` - a directory containing configs that Caddy generates,
|
2020-05-14 05:37:03 +00:00
|
|
|
most notably `autosave.json` which is a backup of the last loaded config
|
2020-05-05 15:39:05 +00:00
|
|
|
* `data/` - a directory storing TLS certificates
|
2020-05-14 05:37:03 +00:00
|
|
|
* `.env` - a file containing environment variables for docker compose
|
|
|
|
* `Caddyfile` - the Caddy configuration file
|
|
|
|
* `docker-compose.yml` - a docker compose file, telling docker how to run containers
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
You only need to provide the three files.<br>
|
2020-05-05 15:39:05 +00:00
|
|
|
The directories are created by docker compose on the first run,
|
|
|
|
the content of these is visible only as root of the docker host.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Create a new docker network
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
`docker network create caddy_net`
|
|
|
|
|
|
|
|
All the containers and Caddy must be on the same network.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-30 18:59:06 +00:00
|
|
|
### - Create .env file
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2022-10-22 10:59:22 +00:00
|
|
|
You want to change `example.com` to your domain.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
`.env`
|
|
|
|
```bash
|
2022-10-22 10:59:22 +00:00
|
|
|
MY_DOMAIN=example.com
|
2020-05-20 18:29:12 +00:00
|
|
|
DOCKER_MY_NETWORK=caddy_net
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
Domain names, api keys, email settings, ip addresses, database credentials, ...
|
|
|
|
whatever is specific for one deployment and different for another,
|
|
|
|
all of that ideally goes in to the `.env` file.
|
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
If `.env` file is present in the directory with the compose file,
|
|
|
|
it is automatically loaded and these variables will be available
|
|
|
|
for docker-compose when building the container with `docker-compose up`.
|
2020-04-28 23:50:40 +00:00
|
|
|
This allows compose files to be moved from system to system more freely
|
|
|
|
and changes are done to the `.env` file.
|
|
|
|
|
|
|
|
Often variable should be available also inside the running container.
|
|
|
|
For that it must be declared in the `environment` section of the compose file,
|
2020-04-30 18:59:06 +00:00
|
|
|
as can be seen next in Caddie's `docker-compose.yml`
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
*extra info:*<br>
|
2020-04-28 23:50:40 +00:00
|
|
|
`docker-compose config` shows how compose will look
|
|
|
|
with the variables filled in.
|
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Create docker-compose.yml
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
`docker-compose.yml`
|
|
|
|
```yml
|
|
|
|
services:
|
|
|
|
|
|
|
|
caddy:
|
2020-05-14 05:37:03 +00:00
|
|
|
image: caddy
|
2020-05-14 15:53:53 +00:00
|
|
|
container_name: caddy
|
|
|
|
hostname: caddy
|
2020-04-28 23:50:40 +00:00
|
|
|
restart: unless-stopped
|
|
|
|
ports:
|
|
|
|
- "80:80"
|
|
|
|
- "443:443"
|
|
|
|
environment:
|
|
|
|
- MY_DOMAIN
|
|
|
|
volumes:
|
2022-08-13 11:37:52 +00:00
|
|
|
- ./Caddyfile:/etc/caddy/Caddyfile
|
2020-04-28 23:50:40 +00:00
|
|
|
- ./data:/data
|
|
|
|
- ./config:/config
|
|
|
|
|
|
|
|
networks:
|
|
|
|
default:
|
2022-05-14 10:46:06 +00:00
|
|
|
name: $DOCKER_MY_NETWORK
|
|
|
|
external: true
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
|
|
|
|
2020-05-23 11:48:14 +00:00
|
|
|
* port 80 and 443 are pusblished for http and https
|
2020-04-30 18:59:06 +00:00
|
|
|
* MY_DOMAIN variable is passed in to the container so that it can be used
|
|
|
|
in `Caddyfile`
|
2022-09-13 07:02:53 +00:00
|
|
|
* the `Caddyfile` is bind-mounted from the docker host
|
2020-04-30 18:59:06 +00:00
|
|
|
* directories `data` and `config` are bind mounted so that their content persists
|
|
|
|
* the same network is joined as for all other containers
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Create Caddyfile
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
`Caddyfile`
|
|
|
|
```
|
|
|
|
{
|
|
|
|
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
|
|
|
}
|
|
|
|
|
|
|
|
a.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy whoami:80
|
|
|
|
}
|
|
|
|
|
|
|
|
b.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy nginx:80
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-10-22 10:48:08 +00:00
|
|
|
`a` and `b` are the subdomains `a.example.com` and `b.example.com`,
|
|
|
|
can be named whatever.
|
2020-04-28 23:50:40 +00:00
|
|
|
For them to work they must have type-A DNS record
|
2020-05-28 23:06:45 +00:00
|
|
|
pointing at your public ip set on Cloudflare, or wherever the domains DNS is managed.<br>
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
The value of `{$MY_DOMAIN}` is provided by the compose and the `.env` file.<br>
|
2020-04-29 00:16:57 +00:00
|
|
|
The subdomains point at docker containers by their **hostname** and **exposed port**.
|
2020-05-28 23:06:45 +00:00
|
|
|
So every docker container you spin should have hostname definied.<br>
|
2022-09-13 07:02:53 +00:00
|
|
|
Commented out is the staging url for let's encrypt.
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Setup some docker containers
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
Something light and easy to setup to route to.<br>
|
2020-04-28 23:50:40 +00:00
|
|
|
Assuming for this testing these compose files are in the same directory with Caddy,
|
|
|
|
so they make use of the same `.env` file and so be on the same network.
|
|
|
|
|
2020-04-30 18:59:06 +00:00
|
|
|
Note the lack of published/mapped ports in the compose,
|
2020-05-28 23:06:45 +00:00
|
|
|
as they will be accessed only through Caddy, which has it's ports published.<br>
|
2020-04-30 18:59:06 +00:00
|
|
|
And since the containers and Caddy are all on the same bridge docker network,
|
2020-05-28 23:06:45 +00:00
|
|
|
they can access each other on any port.<br>
|
2020-04-30 18:59:06 +00:00
|
|
|
Exposed ports are just documentation,
|
|
|
|
[don't confuse expose and publish](https://maximorlov.com/exposing-a-port-in-docker-what-does-it-do/).
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
*extra info:*<br>
|
2020-05-20 17:20:01 +00:00
|
|
|
To know which ports containers have exposed - `docker ps`, or
|
|
|
|
`docker port <container-name>`, or use [ctop](https://github.com/bcicen/ctop).
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
`whoami-compose.yml`
|
|
|
|
```yaml
|
|
|
|
version: "3.7"
|
|
|
|
services:
|
|
|
|
|
|
|
|
whoami:
|
|
|
|
image: "containous/whoami"
|
|
|
|
container_name: "whoami"
|
|
|
|
hostname: "whoami"
|
|
|
|
|
|
|
|
networks:
|
|
|
|
default:
|
2022-05-14 10:46:06 +00:00
|
|
|
name: $DOCKER_MY_NETWORK
|
|
|
|
external: true
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
`nginx-compose.yml`
|
|
|
|
```yaml
|
|
|
|
version: "3.7"
|
|
|
|
services:
|
|
|
|
|
|
|
|
nginx:
|
|
|
|
image: nginx:latest
|
|
|
|
container_name: nginx
|
|
|
|
hostname: nginx
|
|
|
|
|
|
|
|
networks:
|
|
|
|
default:
|
2022-05-14 10:46:06 +00:00
|
|
|
name: $DOCKER_MY_NETWORK
|
|
|
|
external: true
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
2020-04-29 00:16:57 +00:00
|
|
|
### - editing hosts file
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-05-16 20:11:36 +00:00
|
|
|
You are on your local network and you are likely running the docker host
|
2020-05-28 23:06:45 +00:00
|
|
|
inside the same network.<br>
|
|
|
|
If that's the case then shit will not work without editing the hosts file.<br>
|
2022-10-22 03:48:26 +00:00
|
|
|
Reason being that when you write that `a.{$MY_DOMAIN}` in to your browser,
|
|
|
|
you are asking google's DNS for `a.{$MY_DOMAIN}` IP address.
|
2020-05-16 20:11:36 +00:00
|
|
|
It will give you your own public IP, and most routers/firewalls wont allow
|
|
|
|
this loopback, where your requests should go out and then right back.
|
|
|
|
|
|
|
|
So just [edit](https://support.rackspace.com/how-to/modify-your-hosts-file/)
|
|
|
|
`hosts` as root/administrator,
|
2020-04-28 23:50:40 +00:00
|
|
|
adding whatever is the local IP of the docker host and the hostname:
|
|
|
|
|
2020-05-16 20:11:36 +00:00
|
|
|
```
|
2022-10-22 03:48:26 +00:00
|
|
|
192.168.1.222 a.{$MY_DOMAIN}
|
|
|
|
192.168.1.222 b.{$MY_DOMAIN}
|
2020-05-16 20:11:36 +00:00
|
|
|
```
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-16 20:11:36 +00:00
|
|
|
If it is just quick testing one can use Opera browser
|
2020-05-28 23:06:45 +00:00
|
|
|
and enable the build in VPN.<br>
|
2020-05-14 06:36:21 +00:00
|
|
|
|
2020-05-16 20:11:36 +00:00
|
|
|
One can also run a dns/dhcp server on the network, to solve this for all
|
2020-05-28 23:06:45 +00:00
|
|
|
devices.<br>
|
2020-05-18 22:50:11 +00:00
|
|
|
Here's a [guide-by-example for dnsmasq](
|
2020-05-16 20:19:27 +00:00
|
|
|
https://github.com/DoTheEvo/selfhosted-apps-docker/tree/master/dnsmasq).
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### - Run it all
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
Caddy
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
* `docker-compose up -d`
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
Services
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
* `docker-compose -f whoami-compose.yml up -d`
|
|
|
|
* `docker-compose -f nginx-compose.yml up -d`
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-14 15:53:53 +00:00
|
|
|
Give it time to get certificates, checking `docker logs caddy` as it goes,
|
2020-04-28 23:50:40 +00:00
|
|
|
then visit the urls. It should lead to the services with https working.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
If something is fucky use `docker logs caddy` to see what is happening.<br>
|
2020-05-14 15:53:53 +00:00
|
|
|
Restarting the container `docker container restart caddy` can help.
|
|
|
|
Or investigate inside `docker exec -it caddy /bin/sh`.
|
2020-04-28 23:50:40 +00:00
|
|
|
For example trying to ping hosts that are suppose to be reachable,
|
|
|
|
`ping nginx` should work.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
There's also other possible issues, like bad port forwarding towards docker host.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
*extra info:*<br>
|
2020-05-14 15:53:53 +00:00
|
|
|
`docker exec -w /etc/caddy caddy caddy reload` reloads config
|
2020-05-14 06:36:21 +00:00
|
|
|
if you made changes and want them to take effect.
|
2020-05-05 15:39:05 +00:00
|
|
|
|
2022-08-13 11:37:52 +00:00
|
|
|
*extra info2:*<br>
|
|
|
|
caddy can complain about formatting of the `Caddyfile`<br>
|
|
|
|
this executed on the host will let caddy overwrite the Caddyfile with
|
|
|
|
correct formatting
|
|
|
|
`docker exec -w /etc/caddy caddy caddy fmt -overwrite`
|
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
# Caddy more info and various configurations
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-21 22:06:39 +00:00
|
|
|
##### Caddyfile structure:
|
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
![caddyfile-diagram-pic](https://i.imgur.com/c0ycNal.png)
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-21 22:06:39 +00:00
|
|
|
Worth having a look at the official documentation, especially these short pages
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-21 22:06:39 +00:00
|
|
|
* [concept](https://caddyserver.com/docs/caddyfile/concepts)
|
2020-04-28 23:50:40 +00:00
|
|
|
* [conventions](https://caddyserver.com/docs/conventions)
|
2020-05-21 22:06:39 +00:00
|
|
|
* [reverse_proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy)
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-25 21:49:04 +00:00
|
|
|
Maybe checking out
|
|
|
|
[mozzila's - overview of HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview)
|
|
|
|
would also not hurt, it is very well written.
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-04-30 23:51:39 +00:00
|
|
|
### Routing traffic to other machines on the LAN
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
If not targeting a docker container but a dedicated machine on the network.<br>
|
2020-04-30 23:51:39 +00:00
|
|
|
Nothing really changes, if you can ping the machine from Caddy container
|
|
|
|
by its hostname or its IP, it will work.
|
|
|
|
|
|
|
|
```
|
|
|
|
blue.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy server-blue:80
|
|
|
|
}
|
|
|
|
|
|
|
|
violet.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy 192.168.1.100:80
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Reverse proxy without domain and https
|
|
|
|
|
|
|
|
You can always just use localhost, which will translates in to docker hosts IP address.
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
localhost:55414 {
|
|
|
|
reverse_proxy urbackup:55414
|
|
|
|
}
|
|
|
|
|
|
|
|
:9090 {
|
|
|
|
reverse_proxy prometheus:9090
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
Prometheus entry uses short-hand notation.<br>
|
2020-04-29 00:16:57 +00:00
|
|
|
TLS is automatically disabled in localhost use.
|
2020-04-28 23:50:40 +00:00
|
|
|
|
2020-04-30 18:59:06 +00:00
|
|
|
But for this to work Caddy's compose file needs to have those ports **published** too.
|
|
|
|
|
|
|
|
`docker-compose.yml`
|
|
|
|
```yml
|
|
|
|
services:
|
|
|
|
|
|
|
|
caddy:
|
2020-05-14 05:37:03 +00:00
|
|
|
image: caddy
|
2020-05-14 15:53:53 +00:00
|
|
|
container_name: caddy
|
|
|
|
hostname: caddy
|
2020-04-30 18:59:06 +00:00
|
|
|
restart: unless-stopped
|
|
|
|
ports:
|
|
|
|
- "80:80"
|
|
|
|
- "443:443"
|
|
|
|
- "55414:55414"
|
|
|
|
- "9090:9090"
|
|
|
|
environment:
|
|
|
|
- MY_DOMAIN
|
|
|
|
volumes:
|
|
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
|
|
- ./data:/data
|
|
|
|
- ./config:/config
|
|
|
|
|
|
|
|
networks:
|
|
|
|
default:
|
2022-05-14 10:46:06 +00:00
|
|
|
name: $DOCKER_MY_NETWORK
|
|
|
|
external: true
|
2020-04-30 18:59:06 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
With this setup, and assuming docker host at: `192.168.1.222`,
|
2020-04-28 23:50:40 +00:00
|
|
|
writing `192.168.1.222:55414` in to browser will go to to urbackup,
|
|
|
|
and `192.168.1.222:9090` gets to prometheus.
|
|
|
|
|
2020-05-25 21:49:04 +00:00
|
|
|
### Named matchers and IP filtering
|
|
|
|
|
|
|
|
Caddy has [matchers](https://caddyserver.com/docs/caddyfile/matchers)
|
2020-05-28 23:06:45 +00:00
|
|
|
which allow you to define how to deal with incoming requests.<br>
|
2020-05-25 21:49:04 +00:00
|
|
|
`reverse_proxy server-blue:80` is a matcher that matches all requests
|
2020-05-28 23:06:45 +00:00
|
|
|
and sends them somewhere.<br>
|
2020-05-25 21:55:17 +00:00
|
|
|
But if more control is desired, path matchers and named matchers come to play.
|
2020-05-25 21:49:04 +00:00
|
|
|
|
2020-05-25 21:55:17 +00:00
|
|
|
What if you want to block all traffic coming from the outside world,
|
2020-05-28 23:06:45 +00:00
|
|
|
but local network be allowed through?<br>
|
2020-05-25 21:49:04 +00:00
|
|
|
Well, the [remote_ip](https://caddyserver.com/docs/caddyfile/matchers#remote-ip)
|
2020-05-28 23:06:45 +00:00
|
|
|
matcher comes to play, which enables you to filter requests by their IP.<br>
|
2020-05-25 21:49:04 +00:00
|
|
|
|
|
|
|
Named matchers are defined by `@` and can be named whatever you like.
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
|
|
|
}
|
|
|
|
|
|
|
|
a.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy whoami:80
|
|
|
|
}
|
|
|
|
|
|
|
|
b.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy nginx:80
|
|
|
|
|
|
|
|
@fuck_off_world {
|
|
|
|
not remote_ip 192.168.1.0/24
|
|
|
|
}
|
|
|
|
respond @fuck_off_world 403
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
`@fuck_off_world` matches all IPs except the local network IP range.<br>
|
2020-05-25 21:49:04 +00:00
|
|
|
Requests matching that rule get the response 403 - forbidden.
|
|
|
|
|
|
|
|
### Snippets
|
|
|
|
|
|
|
|
What if you need to have the same matcher in several site-blocks and
|
|
|
|
would prefer for config to look cleaner?
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
Here comes the [snippets](https://caddyserver.com/docs/caddyfile/concepts#snippets).<br>
|
2020-05-25 21:55:17 +00:00
|
|
|
Snippets are defined under the global options block,
|
2020-05-28 23:06:45 +00:00
|
|
|
using parentheses, named whatever you like.<br>
|
2020-05-25 21:55:17 +00:00
|
|
|
They then can be used inside any site-block with simple `import <snippet name>`
|
2020-05-25 21:49:04 +00:00
|
|
|
|
2020-05-25 21:55:17 +00:00
|
|
|
Now would be a good time to look again at that concept picture above.
|
2020-05-25 21:49:04 +00:00
|
|
|
|
2020-05-25 21:55:17 +00:00
|
|
|
Here is above example of IP filtering named matcher done using a snippet.
|
2020-05-25 21:49:04 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
|
|
|
}
|
|
|
|
|
|
|
|
(LAN_only) {
|
|
|
|
@fuck_off_world {
|
|
|
|
not remote_ip 192.168.1.0/24
|
|
|
|
}
|
|
|
|
respond @fuck_off_world 403
|
|
|
|
}
|
|
|
|
|
|
|
|
a.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy whoami:80
|
|
|
|
}
|
|
|
|
|
|
|
|
b.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy nginx:80
|
|
|
|
import LAN_only
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-04-29 00:16:57 +00:00
|
|
|
### Backend communication
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
Some containers might be set to communicate only through https 443 port.
|
|
|
|
But since they are behind proxy, their certificates wont be singed, wont be trusted.
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
Caddies sub-directive `transport` sets how to communicate with the backend.<br>
|
2020-05-14 15:53:53 +00:00
|
|
|
Setting the upstream's scheme to `https://`
|
|
|
|
or declaring the `tls` transport subdirective makes it use https.
|
|
|
|
Setting `tls_insecure_skip_verify` makes Caddy ignore errors due to
|
|
|
|
untrusted certificates coming from the backend.
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
```
|
2020-05-14 22:07:29 +00:00
|
|
|
whatever.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy https://server-blue:443 {
|
2020-04-28 23:50:40 +00:00
|
|
|
transport http {
|
2020-05-31 08:27:59 +00:00
|
|
|
tls
|
2020-04-28 23:50:40 +00:00
|
|
|
tls_insecure_skip_verify
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-04-30 18:59:06 +00:00
|
|
|
### HSTS and redirects
|
|
|
|
|
2021-06-11 17:47:18 +00:00
|
|
|
Here is an example of a redirect when wanting the common case of
|
2021-06-11 17:47:48 +00:00
|
|
|
switching anyone that comes to a `www` subdomain to the naked domain.
|
2021-06-11 17:37:31 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
www.{$MY_DOMAIN} {
|
|
|
|
redir https://{$MY_DOMAIN}{uri}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-06-11 17:47:18 +00:00
|
|
|
Another example is running NextCloud behind proxy,
|
2021-06-11 17:37:31 +00:00
|
|
|
which likely shows few warning on its status page.
|
2020-04-30 18:59:06 +00:00
|
|
|
It requires some redirects for service discovery to work and would like
|
2022-05-04 22:40:46 +00:00
|
|
|
if [HSTS](https://www.youtube.com/watch?v=kYhMnw4aJTw)
|
|
|
|
[2](https://www.youtube.com/watch?v=-MWqSD2_37E) would be set.<br>
|
2020-04-28 23:50:40 +00:00
|
|
|
Like so:
|
|
|
|
|
|
|
|
```
|
|
|
|
nextcloud.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy nextcloud:80
|
|
|
|
header Strict-Transport-Security max-age=31536000;
|
|
|
|
redir /.well-known/carddav /remote.php/carddav 301
|
|
|
|
redir /.well-known/caldav /remote.php/caldav 301
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
### Headers and gzip
|
2020-04-30 18:59:06 +00:00
|
|
|
|
|
|
|
This example is with bitwarden_rs password manager, which comes with its reverse proxy
|
2020-04-28 23:50:40 +00:00
|
|
|
[recommendations](https://github.com/dani-garcia/bitwarden_rs/wiki/Proxy-examples).
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
`encode gzip` enables compression.<br>
|
2020-04-28 23:50:40 +00:00
|
|
|
This lowers the bandwith use and speeds up loading of the sites.
|
|
|
|
It is often set on the webserver running inside the docker container,
|
|
|
|
but if not it can be enabled on caddy.
|
|
|
|
You can check if your stuff has it enabled by using one of
|
|
|
|
[many online tools](https://varvy.com/tools/gzip/)
|
|
|
|
|
2020-05-14 23:12:32 +00:00
|
|
|
By default, Caddy passes through Host header and adds X-Forwarded-For
|
|
|
|
for the client IP. This means that 90% of the time a simple config
|
|
|
|
is all that is needed but sometimes some extra headers might be desired.
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
Here we see bitwarden make use of some extra headers.<br>
|
2020-05-14 23:12:32 +00:00
|
|
|
We can also see its use of websocket protocol for notifications at port 3012.
|
2020-04-28 23:50:40 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
bitwarden.{$MY_DOMAIN} {
|
|
|
|
encode gzip
|
|
|
|
|
2020-05-14 05:37:03 +00:00
|
|
|
header {
|
2020-04-28 23:50:40 +00:00
|
|
|
# Enable cross-site filter (XSS) and tell browser to block detected attacks
|
|
|
|
X-XSS-Protection "1; mode=block"
|
|
|
|
# Disallow the site to be rendered within a frame (clickjacking protection)
|
|
|
|
X-Frame-Options "DENY"
|
|
|
|
# Prevent search engines from indexing (optional)
|
|
|
|
X-Robots-Tag "none"
|
|
|
|
# Server name removing
|
|
|
|
-Server
|
|
|
|
}
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
# Notifications redirected to the websockets server
|
|
|
|
reverse_proxy /notifications/hub bitwarden:3012
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
# Proxy the Root directory to Rocket
|
|
|
|
reverse_proxy bitwarden:80
|
|
|
|
}
|
|
|
|
```
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
### Basic authentication
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
[Official documentation.](https://caddyserver.com/docs/caddyfile/directives/basicauth)<br>
|
2020-05-14 22:07:29 +00:00
|
|
|
Directive `basicauth` can be used when one needs to add
|
|
|
|
a username/password check before accessing a service.
|
|
|
|
|
|
|
|
Password is [bcrypt](https://www.devglan.com/online-tools/bcrypt-hash-generator) hashed
|
2020-05-28 23:06:45 +00:00
|
|
|
and then [base64](https://www.base64encode.org/) encoded.<br>
|
2020-05-14 22:07:29 +00:00
|
|
|
You can use the [`caddy hash-password`](https://caddyserver.com/docs/command-line#caddy-hash-password)
|
|
|
|
command to hash passwords for use in the config.
|
|
|
|
|
|
|
|
Config bellow has login/password : `bastard`/`bastard`
|
|
|
|
|
|
|
|
`Caddyfile`
|
|
|
|
```
|
|
|
|
b.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy whoami:80
|
|
|
|
basicauth {
|
|
|
|
bastard JDJhJDA0JDVkeTFJa1VjS3pHU3VHQ2ZSZ0pGMU9FeWdNcUd0Wk9RdWdzSzdXUXNhWFFLWW5pYkxXVEU2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-04-30 18:59:06 +00:00
|
|
|
### Logging
|
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
[Official documentation.](https://caddyserver.com/docs/caddyfile/directives/log)<br>
|
2020-04-28 23:50:40 +00:00
|
|
|
If access logs for specific site are desired
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-28 23:50:40 +00:00
|
|
|
```
|
|
|
|
bookstack.{$MY_DOMAIN} {
|
|
|
|
log {
|
|
|
|
output file /data/logs/bookstack_access.log {
|
|
|
|
roll_size 20mb
|
|
|
|
roll_keep 5
|
|
|
|
}
|
|
|
|
}
|
2020-05-14 05:37:03 +00:00
|
|
|
reverse_proxy bookstack:80
|
2020-04-28 23:50:40 +00:00
|
|
|
}
|
|
|
|
```
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-14 05:37:03 +00:00
|
|
|
# Caddy DNS challenge
|
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
This setup only works for Cloudflare.
|
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
DNS challenge authenticates ownership of the domain by requesting that the owner
|
|
|
|
puts a specific TXT record in to the domains DNS zone.<br>
|
|
|
|
Benefit of using DNS challenge is that there is no need for your server
|
|
|
|
to be reachable by the letsencrypt servers. Cant open ports or want to exclude
|
|
|
|
entire world except your own country from being able to reach your server?
|
|
|
|
DNS challange is what you want to use for https then.<br>
|
2022-10-22 04:14:33 +00:00
|
|
|
It also allows for issuance of wildcard certificates.<br>
|
2022-10-22 03:48:26 +00:00
|
|
|
The drawback is a potential security issue, since you are creating a token
|
|
|
|
that allows full control over your domain's DNS. You store this token somewhere,
|
|
|
|
you are giving it to some application from dockerhub...
|
2020-05-14 23:12:32 +00:00
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
*note*: caddy uses a new [libdns](https://github.com/libdns/libdns/)
|
|
|
|
golang library with [cloudflare package](https://github.com/libdns/cloudflare)
|
2020-05-14 23:12:32 +00:00
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
### - Create API token on Cloudflare
|
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
[On Cloudflare](https://dash.cloudflare.com/profile/api-tokens)
|
|
|
|
create a new API Token with two permsisions,
|
2020-05-14 23:29:30 +00:00
|
|
|
[pic of it here](https://i.imgur.com/YWxgUiO.png)
|
2020-05-14 22:07:29 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
* zone/zone/read<br>
|
|
|
|
* zone/dns/edit<br>
|
2020-05-14 23:31:09 +00:00
|
|
|
|
|
|
|
Include all zones needs to be set.
|
2020-05-14 22:07:29 +00:00
|
|
|
|
2022-10-22 04:14:33 +00:00
|
|
|
### - Edit .env file
|
|
|
|
|
|
|
|
Add `CLOUDFLARE_API_TOKEN` variable with the value of the newly created token.
|
|
|
|
|
|
|
|
`.env`
|
|
|
|
```
|
|
|
|
MY_DOMAIN={$MY_DOMAIN}
|
|
|
|
DOCKER_MY_NETWORK=caddy_net
|
|
|
|
|
|
|
|
CLOUDFLARE_API_TOKEN=<cloudflare api token goes here>
|
|
|
|
```
|
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
### - Create Dockerfile
|
|
|
|
|
2020-05-14 23:26:58 +00:00
|
|
|
To add support, Caddy needs to be compiled with
|
2020-05-28 23:06:45 +00:00
|
|
|
[Cloudflare DNS plugin](https://github.com/caddy-dns/cloudflare).<br>
|
2020-05-14 23:26:58 +00:00
|
|
|
This is done by using your own Dockerfile, using the `builder` image.
|
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
Create a directory `dockerfile-caddy` in the caddy directory.<br>
|
2020-05-14 22:07:29 +00:00
|
|
|
Inside create a file named `Dockerfile`.
|
|
|
|
|
2020-05-14 23:12:32 +00:00
|
|
|
`Dockerfile`
|
2020-05-14 05:37:03 +00:00
|
|
|
```Dockerfile
|
2022-10-22 03:48:26 +00:00
|
|
|
FROM caddy:2.6.2-builder AS builder
|
2020-05-14 05:37:03 +00:00
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
RUN xcaddy build \
|
|
|
|
--with github.com/caddy-dns/cloudflare
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
FROM caddy:2.6.2
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-05-14 05:37:03 +00:00
|
|
|
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
|
|
|
```
|
|
|
|
|
2020-05-14 22:07:29 +00:00
|
|
|
### - Edit docker-compose.yml
|
2020-05-14 05:37:03 +00:00
|
|
|
|
2020-05-28 23:06:45 +00:00
|
|
|
`image` replaced with `build` option pointing at the `Dockerfile` location<br>
|
2020-05-14 22:07:29 +00:00
|
|
|
and `CLOUDFLARE_API_TOKEN` variable added.
|
|
|
|
|
|
|
|
`docker-compose.yml`
|
|
|
|
```yml
|
|
|
|
services:
|
|
|
|
|
|
|
|
caddy:
|
2022-10-22 03:48:26 +00:00
|
|
|
build: ./dockerfile-caddy
|
2020-05-14 22:07:29 +00:00
|
|
|
container_name: caddy
|
|
|
|
hostname: caddy
|
|
|
|
restart: unless-stopped
|
|
|
|
ports:
|
|
|
|
- "80:80"
|
|
|
|
- "443:443"
|
|
|
|
environment:
|
|
|
|
- MY_DOMAIN
|
|
|
|
- CLOUDFLARE_API_TOKEN
|
|
|
|
volumes:
|
|
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
|
|
- ./data:/data
|
|
|
|
- ./config:/config
|
|
|
|
|
|
|
|
networks:
|
|
|
|
default:
|
2022-05-14 10:46:06 +00:00
|
|
|
name: $DOCKER_MY_NETWORK
|
|
|
|
external: true
|
2020-05-14 22:07:29 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### - Edit Caddyfile
|
|
|
|
|
2022-10-22 04:14:33 +00:00
|
|
|
Add global option `acme_dns`<br>
|
2022-10-22 03:48:26 +00:00
|
|
|
or add `tls` directive to the site-blocks.
|
2020-04-27 21:04:28 +00:00
|
|
|
|
2020-04-30 18:59:06 +00:00
|
|
|
`Caddyfile`
|
|
|
|
```
|
2020-05-14 22:07:29 +00:00
|
|
|
{
|
2022-10-22 03:48:26 +00:00
|
|
|
acme_dns cloudflare {$CLOUDFLARE_API_TOKEN}
|
2020-05-14 22:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
a.{$MY_DOMAIN} {
|
2020-04-30 18:59:06 +00:00
|
|
|
reverse_proxy whoami:80
|
2020-05-14 22:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
b.{$MY_DOMAIN} {
|
|
|
|
reverse_proxy nginx:80
|
|
|
|
tls {
|
2022-10-22 03:48:26 +00:00
|
|
|
dns cloudflare {$CLOUDFLARE_API_TOKEN}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### - Wildcard certificate
|
|
|
|
|
2022-10-22 04:14:33 +00:00
|
|
|
A one certificate to rule all subdomains. But not apex/naked domain, thats separate.<br>
|
2022-10-22 03:48:26 +00:00
|
|
|
As shown in [the documentation](https://caddyserver.com/docs/caddyfile/patterns#wildcard-certificates),
|
2022-10-22 04:14:33 +00:00
|
|
|
the subdomains must be moved under the wildcard site block and make use
|
|
|
|
of host matching and handles.
|
2022-10-22 03:48:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
`Caddyfile`
|
|
|
|
```
|
|
|
|
{
|
|
|
|
acme_dns cloudflare {$CLOUDFLARE_API_TOKEN}
|
|
|
|
}
|
|
|
|
|
|
|
|
{$MY_DOMAIN} {
|
|
|
|
reverse_proxy homer:8080
|
|
|
|
}
|
|
|
|
|
|
|
|
*.{$MY_DOMAIN} {
|
|
|
|
@a host a.{$MY_DOMAIN}
|
|
|
|
handle @a {
|
|
|
|
reverse_proxy whoami:80
|
|
|
|
}
|
|
|
|
|
|
|
|
@b host b.{$MY_DOMAIN}
|
|
|
|
handle @b {
|
|
|
|
reverse_proxy nginx:80
|
|
|
|
}
|
|
|
|
|
|
|
|
handle {
|
|
|
|
abort
|
2020-04-27 21:04:28 +00:00
|
|
|
}
|
2020-04-30 18:59:06 +00:00
|
|
|
}
|
|
|
|
```
|
2020-05-14 22:07:29 +00:00
|
|
|
|
2022-10-22 03:48:26 +00:00
|
|
|
[Here's](https://github.com/caddyserver/caddy/issues/3200) some discussion
|
|
|
|
on this and a simple, elegant way we could have had it without the need to
|
|
|
|
dick with the Caddyfile this much. Just one global line declaration.<br>
|
|
|
|
But the effort went sideways.
|