Skip to content

docs: add Caddy+LetsEncrypt TLS example #4585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/admin/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ subdomain that resolves to Coder (e.g. `*.coder.example.com`).
> If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the
> root and wildcard domains. Multi-certificate support [is planned](https://github.com/coder/coder/pull/4150).

## TLS Certificates

The Coder server can directly use TLS certificates with `CODER_TLS_ENABLE` and accompanying configuration flags. However, Coder can also run behind a reverse-proxy to terminate TLS certificates from LetsEncrypt, for example.

- Example: [Run Coder with Caddy and LetsEncrypt](https://github.com/coder/coder/tree/main/examples/web-server/caddy)

## PostgreSQL Database

Coder uses a PostgreSQL database to store users, workspace metadata, and other deployment information.
Expand Down
9 changes: 9 additions & 0 deletions examples/web-server/caddy/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
coder.example.com, *.coder.example.com {
reverse_proxy localhost:3000
tls {
on_demand
issuer acme {
email email@example.com
}
}
}
127 changes: 127 additions & 0 deletions examples/web-server/caddy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Caddy

This is an example configuration of how to use Coder with [caddy](https://caddyserver.com/docs). To use Caddy to generate TLS certificates, you'll need a domain name that resolves to your Caddy server.

## Getting started

### With docker-compose

1. [Install Docker](https://docs.docker.com/engine/install/) and [Docker Compose](https://docs.docker.com/compose/install/)

1. Start with our example configuration

```sh
# Create a project folder
cd $HOME
mkdir coder-with-caddy
cd coder-with-caddy

# Clone coder/coder and copy the Caddy example
git clone https://github.com/coder/coder /tmp/coder
mv /tmp/coder/examples/web-server/caddy $(pwd)
```

1. Modify the [Caddyfile](./Caddyfile) and change the following values:

- `localhost:3000`: Change to `coder:7080` (Coder container on Docker network)
- `email@example.com`: Email to request certificates from LetsEncrypt/ZeroSSL (does not have to be Coder admin email)
- `coder.example.com`: Domain name you're using for Coder.
- `*.coder.example.com`: Domain name for wildcard apps, commonly used for [dashboard port forwarding](https://coder.com/docs/coder-oss/latest/networking/port-forwarding#dashboard). This is optional and can be removed.

1. Start Coder. Set `CODER_ACCESS_URL` and `CODER_WILDCARD_ACCESS_URL` to the domain you're using in your Caddyfile.

```sh
export CODER_ACCESS_URL=https://coder.example.com
export CODER_WILDCARD_ACCESS_URL=*.coder.example.com
docker compose up -d # Run on startup
```

### Standalone

1. If you haven't already, [install Coder](https://coder.com/docs/coder-oss/latest/install)

1. Install [Caddy Server](https://caddyserver.com/docs/install)

1. Copy our sample [Caddyfile](./Caddyfile) and change the following values:

> If you're installed Caddy as a system package, update the default Caddyfile with `vim /etc/caddy/Caddyfile`

- `email@example.com`: Email to request certificates from LetsEncrypt/ZeroSSL (does not have to be Coder admin email)
- `coder.example.com`: Domain name you're using for Coder.
- `*.coder.example.com`: Domain name for wildcard apps, commonly used for [dashboard port forwarding](https://coder.com/docs/coder-oss/latest/networking/port-forwarding#dashboard). This is optional and can be removed.
- `localhost:3000`: Address Coder is running on. Modify this if you changed `CODER_ADDRESS` in the Coder configuration.

1. [Configure Coder](https://coder.com/docs/coder-oss/latest/admin/configure) and change the following values:

- `CODER_ACCESS_URL`: root domain (e.g. `https://coder.example.com`)
- `CODER_WILDCARD_ACCESS_URL`: wildcard domain (e.g. `*.example.com`).

1. Start the Caddy server:

If you're [keeping Caddy running](https://caddyserver.com/docs/running) via a system service:

```sh
sudo systemctl restart caddy
```

Or run a standalone server:

```sh
caddy run
```

1. Optionally, use [ufw](https://wiki.ubuntu.com/UncomplicatedFirewall) or another firewall to disable external traffic outside of Caddy.

```sh
# Check status of UncomplicatedFirewall
sudo ufw status

# Allow SSH
sudo ufw allow 22

# Allow HTTP, HTTPS (Caddy)
sudo ufw allow 80
sudo ufw allow 443

# Deny direct access to Coder server
sudo ufw deny 3000

# Enable UncomplicatedFirewall
sudo ufw enable
```

1. Navigate to your Coder URL! A TLS certificate should be auto-generated on your first visit.

## Generating wildcard certificates

By default, this configuration uses Caddy's [on-demand TLS](https://caddyserver.com/docs/caddyfile/options#on-demand-tls) to generate a certificate for each subdomain (e.g. `app1.coder.example.com`, `app2.coder.example.com`). When users visit new subdomains, such as accessing [ports on a workspace](../../networking/port-forwarding.md), the request will take an additional 5-30 seconds since a new certificate is being generated.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the request will take an additional 5-30 seconds

I'm fairly certain this is the only downside of using Caddy's on-demand TLS. While LetsEncrypt's rate limit (50 certificates/domain/week) can quickly be exhausted, Caddy will silently fall back to ZeroSSL which has no rate limit. ZeroSSL is significantly slower though. If you think this needs further explanation in the docs, I can expand.

With that being said, the extra effort for a wildcard is worth it if a Coder deployment is being actively being used in production.


For production deployments, we recommend configuring Caddy to generate a wildcard certificate, which requires an explicit DNS challenge and additional Caddy modules.

1. Install a custom Caddy build that includes the [caddy-dns](https://github.com/caddy-dns) module for your DNS provider (e.g. CloudFlare, Route53).

- Docker: [Build an custom Caddy image](https://github.com/docker-library/docs/tree/master/caddy#adding-custom-caddy-modules) with the module for your DNS provider. Be sure to reference the new image in the `docker-compose.yaml`.

- Standalone: [Download a custom Caddy build](https://caddyserver.com/download) with the module for your DNS provider. If you're using Debian/Ubuntu, you [can configure the Caddy package](https://caddyserver.com/docs/build#package-support-files-for-custom-builds-for-debianubunturaspbian) to use the new build.

1. Edit your `Caddyfile` and add the necessary credentials/API tokens to solve the DNS challenge for wildcard certificates.

```diff
tls {
- on_demand
issuer acme {
email email@example.com
}

+ dns route53 {
+ max_retries 10
+ aws_profile "real-profile"
+ access_key_id "AKI..."
+ secret_access_key "wJa..."
+ token "TOKEN..."
+ region "us-east-1"
+ }
}
```

> Configuration reference from [caddy-dns/route53](https://github.com/caddy-dns/route53).
57 changes: 57 additions & 0 deletions examples/web-server/caddy/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
version: "3.9"
services:
coder:
image: ghcr.io/coder/coder:${CODER_VERSION:-latest}
environment:
CODER_PG_CONNECTION_URL: "postgresql://${POSTGRES_USER:-username}:${POSTGRES_PASSWORD:-password}@database/${POSTGRES_DB:-coder}?sslmode=disable"
CODER_ADDRESS: "0.0.0.0:7080"
# You'll need to set CODER_ACCESS_URL to an IP or domain
# that workspaces can reach. This cannot be localhost
# or 127.0.0.1 for non-Docker templates!
CODER_ACCESS_URL: "${CODER_ACCESS_URL}"
# Optional) Enable wildcard apps/dashboard port forwarding
CODER_WILDCARD_ACCESS_URL: "${CODER_WILDCARD_ACCESS_URL}"
# If the coder user does not have write permissions on
# the docker socket, you can uncomment the following
# lines and set the group ID to one that has write
# permissions on the docker socket.
#group_add:
# - "998" # docker group on host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
database:
condition: service_healthy
database:
image: "postgres:14.2"
ports:
- "5432:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER:-username} # The PostgreSQL user (useful to connect to the database)
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} # The PostgreSQL password (useful to connect to the database)
POSTGRES_DB: ${POSTGRES_DB:-coder} # The PostgreSQL default database (automatically created at first launch)
volumes:
- coder_data:/var/lib/postgresql/data # Use "docker volume rm coder_coder_data" to reset Coder
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-username} -d ${POSTGRES_DB:-coder}",
]
interval: 5s
timeout: 5s
retries: 5
caddy:
image: caddy:2.6.2
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- $PWD/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
volumes:
coder_data:
caddy_data:
caddy_config: