Skip to content

Instantly share code, notes, and snippets.

@Ocramius
Last active March 11, 2024 22:14
Show Gist options
  • Save Ocramius/d44b9c500d3fd19e863c621500adeec0 to your computer and use it in GitHub Desktop.
Save Ocramius/d44b9c500d3fd19e863c621500adeec0 to your computer and use it in GitHub Desktop.
Example docker + docker-compose + caddy + traefik setup that routes to multiple apps from one exposed HTTP port

Example docker-compose setup that routes to separate hosts while exposing one port

This example runs 4 different docker containers:

  • a traefik reverse proxy
  • 3 caddy instances (simple/modern web server with minimal config)

The 3 applications are completely isolated from the outside network, and are neither accessible nor can access the WAN.

The HTTP routing (and eventual SSL termination) is up to traefik or your reverse proxy of choice.

Running it

Careful: this CLI script will use sudo rights, please audit it before running it!

./run.sh
FROM abiosoft/caddy:0.11.0
ADD ./Caddyfile /etc/Caddyfile
ADD ./app1.html /serve/index.html
<title>app1</title>
FROM abiosoft/caddy:0.11.0
ADD ./Caddyfile /etc/Caddyfile
ADD ./app2.html /serve/index.html
<title>app2</title>
FROM abiosoft/caddy:0.11.0
ADD ./Caddyfile /etc/Caddyfile
ADD ./app3.html /serve/index.html
<title>app3</title>
:80 {
root /serve
}
version: '3.5'
services:
traefik:
image: traefik:1.7
command: --web --docker --docker.domain=app.test --logLevel=DEBUG
depends_on:
# our setup relies on the 3 apps running. Trying to spin up traefik will bring up those too
- "app1"
- "app2"
- "app3"
ports:
# access this with the correct Host header to access the respective container
- "80:80"
# management UI
- "8080:8080"
volumes:
# traefik does its magic by reading information about running containers from the docker socket
- /var/run/docker.sock:/var/run/docker.sock
- /dev/null:/traefik.toml
networks:
outside-world:
internal-network:
# app1, app2 and app3 are Caddy instances listening to port 80 and serving an index.html.
app1:
build:
context: .
dockerfile: app1-Dockerfile
networks:
internal-network:
# the aliases are not required, but are useful if the applications want to internally
# reference each other by host name
aliases:
- "app1.test"
labels:
- "traefik.port=80"
- "traefik.frontend.rule=Host:app1.localhost"
app2:
build:
context: .
dockerfile: app2-Dockerfile
networks:
internal-network:
aliases:
- "app2.test"
labels:
- "traefik.port=80"
- "traefik.frontend.rule=Host:app2.localhost"
app3:
build:
context: .
dockerfile: app3-Dockerfile
networks:
internal-network:
aliases:
- "app3.test"
labels:
- "traefik.port=80"
- "traefik.frontend.rule=Host:app3.localhost"
networks:
# everything that is *only* on "internal network" cannot talk to WAN
internal-network:
internal: true
# add this network to a container to make it talk to the rest of the world
outside-world:
#!/usr/bin/env bash
set -exuo pipefail
IFS=$'\n\t'
docker-compose up -d
curl http://app1.localhost
curl http://app2.localhost
curl http://app3.localhost
@sagoyanfisic
Copy link

ERROR: for app1 dictionary update sequence element #0 has length 15; 2 is required
Traceback (most recent call last):
File "bin/docker-compose", line 6, in
File "compose/cli/main.py", line 68, in main
File "compose/cli/main.py", line 121, in perform_command
File "compose/cli/main.py", line 952, in up
File "compose/project.py", line 455, in up
File "compose/parallel.py", line 70, in parallel_execute
ValueError: dictionary update sequence element #0 has length 15; 2 is required
Failed to execute script docker-compose
quatum@quatum-H81M-H:~/Documents/compose$ nano docker-compose.yml

@lrkwz
Copy link

lrkwz commented Apr 28, 2020

You do not need

sudo bash -c 'echo "127.0.0.1 app1.test" >> /etc/hosts'
sudo bash -c 'echo "127.0.0.1 app2.test" >> /etc/hosts'
sudo bash -c 'echo "127.0.0.1 app3.test" >> /etc/hosts'

as long as you add the "Host" http header

curl -H Host:app1.test http://127.0.0.1
curl -H Host:app2.test http://127.0.0.1
curl -H Host:app3.test http://127.0.0.1

@Ocramius
Copy link
Author

Indeed, didn't think of it 👍

@Ocramius
Copy link
Author

Ocramius commented Apr 28, 2020

@lrkwz adjusted, thanks!

@pwfraley
Copy link

Why does Traefic depend on the app1 thru app3? Should it not be the other way around? Traefic can run even without apps and would be reachable, but running the apps without traefic is kind of useless since you would never be able to reach them.
But besides that, very simple example to understand traefic :) Thanx

@Ocramius
Copy link
Author

Why does Traefic depend on the app1 thru app3?

The system is not reachable until all services are up. It's not a prod-alike setup, obviously, but for dev purposes I'd not want a partial environment to be running, unless I killed services manually.

@edgars
Copy link

edgars commented Jun 4, 2020

Sorry, but I did not get the point. If Traefik is not balancing through the app1,app2,app3 via the default port 80, I believe that it is kind far from reality. If you expose an API, and you have to pass an extra header to define where you will execute the endpoint:

curl -H Host:app1.test http://127.0.0.1

Could you please offer a way to load-balancer the apps through a Traefik port, preferentially the 80?

Many thanks in advance,

@Ocramius
Copy link
Author

Ocramius commented Jun 4, 2020

This is just for serving multiple apps from one entry point: nothing about load balancing in this gist.

@Ocramius
Copy link
Author

Ocramius commented Feb 7, 2022

I simplified the setup: curl http://app1.localhost should suffice, and prevents any:

  • custom host headers (let cURL do that)
  • /etc/hosts entries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment