Skip to content
Go back

Install Caddy reverse proxy via Docker

· Updated:
By SumGuy 5 min read
Install Caddy reverse proxy via Docker

Why caddy?

These are just some of Caddy’s amazing features!

Install Docker and Docker Compose if you haven’t already. View our docker guide herehere & our docker rootless guide herehere.

Scenario: you have a new app you wrote or installed via docker called mycoolapp you want to allow the outside world to connect to this but have a reverse proxy in the middle so you can apply e.g. rate limiting, ssl cert, basic auth etc.

Create a new directory for your Caddy configuration and change to it:

mkdir caddy
cd caddy

I prefer to make these in /opt/docker so for me its:

mkdir -p /opt/docker/caddy
cd /opt/docker/caddy

This is purely personal preference and you are the one to decide on this location.

Create a file called Caddyfile in this directory with the following contents:

example.com {
reverse_proxy mycoolapp:80
}

If you don’t want to get an SSL cert for this domain automatically for free then use this

example.com:80 {
reverse_proxy mycoolapp:80
}

The above configuration tells Caddy to proxy all requests to example.com to a web server running on port 80 at the hostname mycoolapp. Change these values to match your own configuration.

Create a docker-compose.yml file in the same directory with the following contents:

version: '3'
services:
caddy:
image: caddy
container_name: caddy
ports:
- "80:80"
- "443:443"
environment:
- ACME_AGREE=true
restart: unless-stopped
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./caddy_data:/data
- ./caddy_config:/config
networks:
- public
networks:
public:
external: true

This configuration sets up a Docker container running Caddy with the Caddyfile we created earlier mounted as a volume along with two other folders needed by caddy. the env variable of acme_agree lets you accept the letsencrypt EULA.

You need to create a new network for caddy and for any other containers you want accessible via this reverse proxy.

docker network create public
docker network connect public caddy
docker network connect public mycoolapp

The first command will create a new network named public, the second command will connect your caddy to the public network you just created and the third command will connect your app named mycoolapp to this network also so caddy can access it.

Start the Caddy container via the following command which will start the Caddy container in detached mode, meaning it will run in the background. and it will show you the logs from caddy so you can make sure there were no errors. hit CTRL + C to close the logs.

docker compose up -d && docker compose logs -f

Open a web browser and navigate to http://example.com. You should see the content served by the web server running on port 80 at the mycoolapp hostname.

When It Doesn’t Work: Caddy Can’t Reach Your App

The most common thing that trips people up here is Caddy throwing a dial tcp: lookup mycoolapp: no such host error. You’ve got the Caddyfile right, the Compose file looks fine, but Caddy just can’t see your app. Nine times out of ten it’s a Docker network problem.

Here’s what to check:

1. Make sure the network actually exists and both containers are on it.

Terminal window
docker network inspect public

Look for your caddy and mycoolapp containers in the Containers section. If either one is missing, it’s not on the network — connect it:

Terminal window
docker network connect public mycoolapp

2. Use the container name, not the host IP.

In your Caddyfile, mycoolapp:80 works because Docker’s internal DNS resolves container names within the same network. If you put 127.0.0.1:3000 or localhost:3000 there instead, you’re pointing at Caddy’s own loopback, not your app. Container name is the right move.

3. Watch the actual port your app listens on.

If your app’s Compose file exposes port 3000 internally but you’ve written mycoolapp:80 in the Caddyfile, Caddy will get connection refused. Check what port the app actually binds to inside the container — that’s the port you use in the reverse proxy directive, not the host-side port.

Caddyfile
example.com {
# app listens on 3000 inside the container, not 80
reverse_proxy mycoolapp:3000
}

4. Reload Caddy after changes, don’t just restart.

Caddy supports hot-reload — you don’t need to bounce the whole container every time you tweak the Caddyfile:

Terminal window
docker exec caddy caddy reload --config /etc/caddy/Caddyfile

This is faster and doesn’t cause a blip in active connections. Save the full restart for when you’re changing volumes or environment variables.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Immich vs PhotoPrism: Escape Google Photos Without Losing Your Mind
Next Post
Install docker on Ubuntu/Debian

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts