I am trying to set up a sample application with the Traefik reverse proxy in Docker.
I am using Traefik v2.2 for this project which has significant differences from Traefik.v1.0.
Here is my docker-compose.yml file:
version: '3'
services:
traefik:
# The official v2 Traefik docker image
image: traefik:v2.2
# Enables the web UI and tells Traefik to listen to docker
command:
- --api.insecure=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
ports:
# The HTTP port
- "89:80"
# The Web UI (enabled by --api.insecure=true)
- "8089:8080"
volumes:
# So that Traefik can listen to the Docker events
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
- "traefik.http.routers.whoami.entrypoints=web"
I can access Traefik's dashboard when I go to localhost:8089 on my web browser, but I cannot access the whoami application when I type in the whoami.localhost address on my web browser. I'm just wondering if there is anything I need to change before I can access it, or do I need to change the host from whoami.localhost to localhost:3000 since that's the port I want to access the application in.
One problem I am spotting is that you exposed container port 80 of the traefik container to the host port 89. If you type in whoami.localhost in your web browser, your browser is going to search for an application on host port 80 at that address (since localhost maps natively to port 80), but it is not going to find anything there, because it can only be found at port 89. From my understanding, you should be able to access the application via the command line with the command curl -H Host:whoami.localhost http://127.0.0.1:89. Unfortunately, I am unsure how the URL whoami.localhost:89 is handled by your browser respectively by your DNS.
You can to modify the docker-compose.yml file this way:
version: "3"
services:
traefik:
# The official v2 Traefik docker image
image: traefik:v2.2
# Enables the web UI and tells Traefik to listen to docker
command:
- --api.insecure=true
- --providers.docker=true
ports:
# The HTTP port
- "89:80"
# The Web UI (enabled by --api.insecure=true)
- "8089:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
labels:
- traefik.http.routers.whoami.rule=Host(`whoami.localhost`)
And then you can access the application on your command terminal by typing in:
curl -H Host:whoami.localhost http://127.0.0.1:89
Note: whoami.localhost can be whoami.docker.localhost or app.localhost or whatever you want. The thing here is that you should localhost attached to the end, except if you're adding a Fully Qualifies Domain name (FQDN).
That's all.
I hope this helps
Related
I have a simple PHP Laravel docker image, created finally with PHP Apache, listening on port 80 (by default).
I have a Docker Traefik installation that works very well, via HTTPS (443 port).
Now, if I use the following docker-compose.yml for the laravel installation:
version: "3.8"
services:
resumecv:
image: sineverba/resumecv-backend:0.1.0-dev
container_name: resumecv
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.resumecv-backend.entrypoints=websecure"
- "traefik.http.routers.resumecv-backend.service=resumecv-backend"
- "traefik.http.routers.resumecv-backend.rule=Host(`resumecvbackend.example.com`)"
- "traefik.http.services.resumecv-backend.loadbalancer.server.port=80"
networks:
proxy:
external: true
It works (mapped against 80 port).
If I would change the listening port:
version: "3.8"
services:
resumecv:
image: sineverba/resumecv-backend:0.1.0-dev
container_name: resumecv
networks:
- proxy
ports:
- "9999:80"
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.resumecv-backend.entrypoints=websecure"
- "traefik.http.routers.resumecv-backend.service=resumecv-backend"
- "traefik.http.routers.resumecv-backend.rule=Host(`resumecvbackend.example.com`)"
- "traefik.http.services.resumecv-backend.loadbalancer.server.port=9999"
networks:
proxy:
external: true
I get a Bad Gateway from Cloudflare (service not reachable).
I know that I could change the Apache port inside the container itself, but I would use the out <-> in mapping with ports definition.
Curl test
From the host, I can curl http://127.0.0.1:9999 with success.
I can also browse website using the IP of the host (192.168.1.100:9999).
Label traefik port
I did try to add traefik.port=9999 label without luck
Removing Label balancer
If I remove "traefik.http.services.resumecv-backend.loadbalancer.server.port=9999" label, I get a laconic 404 not found.
Port publishing...
ports:
- "9999:80"
...doesn't change the port on which your container is listening. It simply establishes a mapping from the host into the container. Your service is still listening on port 80, and that's the port other containers -- including traefik -- will need to use to contact your service.
If you're using a frontend like traefik you don't need the ports entry (because you'll be accessing the service through traefik, rather than directly through a host port).
I have Traefik container (traefik v. 2.8) running as reverse proxy for local development.
I also use docker-compose.yml file to define my services.
Also, I have exposed the services present in docker-compose.yml via host.docker.internal by setting these lines in my /etc/hosts file:
127.0.0.1 host.docker.internal
localhost host.docker.internal
My setup is as such:
host.docker.internal:8443 used for service_a
host.docker.internal:8453 used for service_b
I have setup Traefik route from a.localhost that goes to host.docker.internal:8443.
I can access a.localhost from the host outside the containers just fine, and Traefik does really route the traffic to host.docker.internal:8443 as I want.
Problem is that I have a reason to have service B (host.docker.internal:8453) call service A via the a.localhost hostname.
This does not work, as in service B, I get unknown host when trying to access a.localhost
Here is extract from my docker-compose.yml file:
version: '3'
services:
reverse_proxy:
image: traefik:v2.8
# Enables the web UI and tells Traefik to listen to docker
command: --api.insecure=true --providers.docker
ports:
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "9000:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- ./dev-traefik/traefik.yml:/etc/traefik/traefik.yml
- ./dev-traefik:/configurations
service_a:
ports:
- "8443:8443"
service_b:
ports:
- "8453:8453"
Also I'm using a yml-based configuration for Traefik, present in dynamic-config.yml:
http:
routers:
service-a-router:
service: service-a
rule: "Host(`a.localhost`)"
tls: "true" # using tls
services:
service-a:
loadBalancer:
servers:
- url: "https://host.docker.internal:8443" # service A
tls:
certificates:
- certFile: "/etc/https/tls.crt"
keyFile: "/etc/https/tls.key"
stores:
default:
defaultCertificate:
certFile: "/etc/https/tls.crt"
keyFile: "/etc/https/tls.key"
It seems like Traefik is able to listen to requests made from host network, as accessing https://a.localhost from browser outside the container network works just fine.
On the other hand, requests made by service_a container don't seem to be caught by Traefik.
What I have also tried is to add a.localhost to /etc/hosts in the host machine running the containers like this:
127.0.0.1 a.localhost
localhost a.localhost
And then using curl inside service B container to access service A.
This resulted in getting connection refused as opposed to Could not resolve host: a.localhost. This leads me to suggest that traefik couldn't intercept traffic from service b container
What am I doing wrong?
Is there a way to make such setup work? I do have a legit reason for it, which relates to having as close setup as possible in local development as on other environments which are deployed to cloud.
I wasn't able to have Traefik intercept container -> container traffic the way I originally specified I'd want, but was able to actually nevertheless get similar setup working.
Here's the scenario:
I have two services that are accessed via HTTP & TLS:
service_a
service_b
I want to be able to use host.docker.internal special DNS name to my advantage and actually have Traefik to proxy traffic from https://host.docker.internal/service_a to service_a port 8443 both outside Docker container network (from the host machine running Docker) AND from service_b via the fact that both the host machine running Docker can access host.docker.internal and also hosts inside the Docker network.
Using this fact to my advantage, I just defined a path for service_a as such in Traefik's YML configuration file:
http:
routers:
service-a-router:
service: service-a
rule: "Host(`host.docker.internal`) && PathPrefix(`/service_a`)"
tls: "true" # using tls
services:
service-a:
loadBalancer:
servers:
- url: "https://host.docker.internal:8443" # service A
tls:
certificates:
- certFile: "/etc/https/tls.crt"
keyFile: "/etc/https/tls.key"
stores:
default:
defaultCertificate:
certFile: "/etc/https/tls.crt"
keyFile: "/etc/https/tls.key"
And docker-compose.yml was made to look like this:
version: '3'
services:
reverse_proxy:
image: traefik:v2.8
# Enables the web UI and tells Traefik to listen to docker
command: --api.insecure=true --providers.docker
ports:
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "9000:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- ./dev-traefik/traefik.yml:/etc/traefik/traefik.yml
- ./dev-traefik:/configurations
service_a:
ports:
- "8443:8443"
service_b:
ports:
- "8453:8453"
Now as I use host.docker.internal in Host rule in my Traefik YML config, Traefik is in fact able to intercept both traffic from the Docker host machine and from the Docker container service_b.
service_b just need to configure URL of https://host.docker.internal/service_a to access service_a through Traefik.
I got a basic Traefik 2 setup working for HTTP here...
Now I'm trying to get HTTPS working, and basic auth for the dashboard with TLS and redirects...
docker_compose.yml:
version: '3.8'
networks:
myweb:
external: true
services:
proxy:
image: traefik:v2.3.0-rc4-windowsservercore-1809
container_name: traefik
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
# Mount the certs drive
- ./traefik-ssl-certs/:c:/certs/
# Mount the config folder
- ./traefik-config/:c:/config/
# Mount the host docker engine pipe ("docker volume ls")
- source: '\\.\pipe\docker_engine'
target: '\\.\pipe\docker_engine'
type: npipe
command:
- "--api.insecure=true"
# Register the traefik config directory as per: https://docs.traefik.io/providers/file/#directory
- --providers.file.directory=c:/config/
- --providers.file.watch=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# Redirect http to https
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.web.http.redirections.entrypoint.permanent=true
# Configure Docker provider
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.endpoint=npipe:////./pipe/docker_engine"
- "--providers.docker.network=myweb"
- "--providers.docker.watch=true"
networks:
- myweb
labels:
- traefik.http.routers.api.service=api#internal
- traefik.enable=true
- traefik.docker.network=myweb
remoteling:
image: remoteling:latest
container_name: remoteling
networks:
- myweb
labels:
- traefik.enable=true
- traefik.docker.network=myweb
- traefik.http.middlewares.http2https.redirectscheme.scheme=https
- traefik.http.routers.remoteling-http.middlewares=http2https
- traefik.http.routers.remoteling-http.rule=Host(`example.com`) || Host(`example.example.com`)
- traefik.http.routers.remoteling-http.entrypoints=web
- traefik.http.services.remoteling.loadbalancer.server.port=443
- traefik.http.routers.remoteling-https.rule=Host(`example.com`) || Host(`example.example.com`)
- traefik.http.routers.remoteling-https.entrypoints=websecure
- traefik.http.routers.remoteling-https.tls=true
depends_on:
- proxy
From powershell I'm running:
docker network create -d nat myweb
docker-compose -p myweb up
In my working dir I have the following file structure:
./docker-compose.yml
./traefik-config/traefik.yml
./traefik-ssl-certs/example.com.crt
./traefik-ssl-certs/example.com.key
./traefik-ssl-certs/example.example.com.key
./traefik-ssl-certs/example.example.com.key
And the traefik.yml file looks like this:
tls:
certificates:
- certFile: c:/certs/example.example.com.crt
keyFile: c:/certs/example.example.com.key
- certFile: c:/certs/example.com.crt
keyFile: c:/certs/example.com.key
The traefik dashboard shows my service defined as a loadbalancer, which is good (is there any other type of service other than loadbalancer? I'm not sure how else to define the port other than via a loadbalancer). Clicking on the service gives me the local network IP - and when I visit that IP my site loads just fine (albeit with an SSL cert mismatch warning - expected).
However when I try to visit https://example.com or https://example.example.com - I get a 502 Bad Gateway. Any idea why I'd be getting this?
The browser shows the SSL certs are valid, with no warnings, so I assume my certificates config is fine.
The http to https redirect seems to be working, if I visit http://example.com it forwards me to https://example.com
What is wrong with my routing config to cause these Bad Gateways?
I have solved my problem! There were a few issues:
I had configured a TLS cert in my Dockerfile, back from when I was running the image as a single service on my server. So my image already had port 443 bound with the TLS cert. I think that caused issues when the Traefik router tried to configure TLS for the service. So I had to rebuild my image, removing the TLS. I also removed the code that required HTTPS attributes too, that is now the responsibility of traefik.
Image only needs to expose 1 port: My understanding now is that my web application should only run through port 80 (e.g. don't even need port 443 exposed in the image's firewall) and the Traefik router configures and handles the TLS/443 etc through port 80.
I hadn't actually defined my HTTP and HTTPS services properly. I have shared my docker-compose.yml file below - notice for both traefik and my other service I have separated the labels into sections: defining the service, routing for HTTP, routing for HTTPS, redirect middleware, and basic auth for the traefik dashboard. I couldn't find any good documentation or tutorials that really broke down the necessary aspects for traefik 2.0 and categorized them clearly.
The service definition label / loadbalancer port should point to whatever port the image's service is hosted from, e.g. port 80 in most cases, or in the case of the traefik service 8080. But crucially it doesn't need to also point to 443 for https.
In summary my (verbose) learning notes afterall are, you need to:
Define the service again as a label: Didn't work for me without the service definition label. I had to add a service label pointing to the name of the service under which the image is defined.
Define routers for http and https: Whatever text you put after the traefik.http.routers.YOUR_ROUTER_NAME becomes your router. It wasn't clear to me I needed separate routers for both http and https. You must do this for each service (e.g. traefik, whoami, remoteling, etc.)
Define the routers' entrypoints: AFAIK the name you put after the entrypoint in the traefik command defines a new entrypoint that you can use for other services. So in the traefik service definition you'd have --entrypoints.WEBNAME.address=:80 and --entrypoints.WEBSECURENAME.address=:443 (replacing those caps with your own name to use throughout the docker-compose.yml file.
Define the routers' domain names to capture: Just like you define routers for each http and https you must define the domains to capture for both of these routers, even just the same domain/path.
Add tls for https router: For the https router you need the tls.true label.
Separate middleware redirection definitions for traefik and each of your services: I've read that you can declare a global one, but AFAIK each service must opt-in by assigning the middleware to the http router.
Providing your own SSL certs on Docker for Windows: There was precious little information about running Docker for Windows with your own SSL certs. I have a batch script in my 'traefik' working dir with a subfolder containing the SSL certs (crt and key files). I mount this as the first volume, - ./traefik-ssl-certs/:c:/certs/. Then, in my working dir I have another folder called traefik-config containing my traefik.yml file (details in the question above). In linux everyone seems to just mount the config file directly, but mounting files doesn't work in Windows so I had to mount as a folder instead, then I used the command providers.file.directory=c:/config/, which tells traefik to look for the config file in there. The config file provides the location of the SSL certs for traefik to load. If you enable TLS for a router, Traefik will automatically use any cert that matches the domain you've specified on that router.
Basic Auth: Must define the middleware as one label, and then assign that middleware to your https router. And I suppose if you're not using HTTPS redirect then you could assign it to your http router but it wouldn't be secure obviously.
docker-compose.yml:
version: '3.8'
networks:
myweb:
external: true
services:
proxy:
image: traefik:v2.3.0-rc4-windowsservercore-1809
container_name: traefik
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
# Mount the certs drive
- ./traefik-ssl-certs/:c:/certs/
# Mount the config folder
- ./traefik-config/:c:/config/
# Mount the host docker engine pipe ("docker volume ls")
- source: '\\.\pipe\docker_engine'
target: '\\.\pipe\docker_engine'
type: npipe
command:
- --api=true
- --api.dashboard=true
- --api.insecure=false
# Register the traefik config directory as per: https://docs.traefik.io/providers/file/#directory
- --providers.file.directory=c:/config/
- --providers.file.watch=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# Configure Docker provider
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.endpoint=npipe:////./pipe/docker_engine
- --providers.docker.network=myweb
- --providers.docker.watch=true
networks:
- myweb
labels:
- traefik.enable=true
- traefik.docker.network=myweb
# Define the service
- traefik.http.services.proxy.loadbalancer.server.port=8080
# Routing for dashboard HTTP
- traefik.http.routers.dash-http.service=api#internal
- traefik.http.routers.dash-http.rule=Host(`example.com`)
- traefik.http.routers.dash-http.entrypoints=web
# Routing for dashboard HTTPS
- traefik.http.routers.dash-https.service=api#internal
- traefik.http.routers.dash-https.rule=Host(`example.com`)
- traefik.http.routers.dash-https.entrypoints=websecure
- traefik.http.routers.dash-https.tls=true
# Http-to-Https redirect Middleware
- traefik.http.middlewares.dash-http2https.redirectscheme.scheme=https
- traefik.http.middlewares.dash-http2https.redirectscheme.permanent=true
- traefik.http.routers.dash-http.middlewares=dash-http2https
# BasicAuth for dashboard
# Windows doesn't have htpasswd command so I generated one here: https://hostingcanada.org/htpasswd-generator/
# As per Traefik documentation, escaped single $ char with $$ for the yml parser
# user/pass = admin/testpassword
- traefik.http.middlewares.api-auth.basicauth.users=admin:$$2y$$10$$mfWQ11K16V6gVK.8Y6q1Eeh765NZscmjCrjJlAtaWubEsjU8HLYOO
- traefik.http.routers.dash-https.middlewares=api-auth
remoteling:
image: remoteling:latest
container_name: remoteling
networks:
- myweb
labels:
- traefik.enable=true
- traefik.docker.network=myweb
# Define the service
- traefik.http.services.remoteling.loadbalancer.server.port=80
# Routing for remoteling HTTP
- traefik.http.routers.remoteling-http.service=remoteling
- traefik.http.routers.remoteling-http.entrypoints=web
- traefik.http.routers.remoteling-http.rule=Host(`services.example.com`)
# Routing for remoteling HTTPS
- traefik.http.routers.remoteling-https.service=remoteling
- traefik.http.routers.remoteling-https.entrypoints=websecure
- traefik.http.routers.remoteling-https.rule=Host(`services.example.com`)
- traefik.http.routers.remoteling-https.tls=true
# Http-to-Https redirect Middleware
- traefik.http.middlewares.remoteling-http2https.redirectscheme.scheme=https
- traefik.http.middlewares.remoteling-http2https.redirectscheme.permanent=true
- traefik.http.routers.remoteling-http.middlewares=remoteling-http2https
depends_on:
- proxy
Hopefully someone else finds that useful.
From what I can see it goes like this:
docker-traefik.yml:
version: '3'
services:
traefik:
image: traefik
command: --docker # enable Docker Provider
# use Docker Swarm Mode as data provider
--docker.swarmmode
ports:
- "80:80"
volumes:
# for it to be able to listen to Docker events
- /var/run/docker.sock:/var/run/docker.sock
docker-whoami.yml:
version: '3'
networks:
traefik_default:
external: true
services:
whoami:
image: containous/whoami
networks:
# add to traefik network
- traefik_default
deploy:
labels:
# whoami is on port 80
- "traefik.port=80"
# whoami is on traefik_default network
- "traefik.docker.network=traefik_default"
# when to forward requests to whoami
- "traefik.frontend.rule=Host:example.com"
Let me quote the documentation here:
Required labels:
traefik.frontend.rule
traefik.port - Without this the debug logs will show this service is deliberately filtered out.
traefik.docker.network - Without this a 504 may occur.
...
traefik.docker.network Overrides the default docker network to use for connections to the container. [1]
traefik.port=80 Registers this port. Useful when the container exposes multiples ports.
But why can't it just take the exposed ports for a default value of traefik.port? And from what I can see it works without traefik.docker.network (that is, if traefik_default is the first service's network). When do I get 504's?
But why can't it just take the exposed ports for a default value of traefik.port?
If ur container has 3 or 4 exposed ports, which should traefik use? So who saying to traefik, which of these ports the right one? So you do - with traefik.port. Where is the problem to use the default port of your configured service?
U should expose 80, 443 and 8080 - so 80 and 443 for http/https webpages and 8080 for traefik dashboard. If u dont wanna use the dashboard, u dont need to expose 8080.
And i dont see any network configured # traefik in your composer file - should this have no network? Ur service and traefik need to be in the same network. Otherwise traefik cant reach ur service and forward.
Also where are the endpoints?
I am trying to run Traefik as an API gateway and want to trigger ForwardAuth middleware by using the following docker compose file but the middleware the auth endpoint is not being hit. I am using it with localhost.
version: '3'
services:
reverse-proxy:
image: traefik # The official Traefik docker image
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
ports:
- "80:80" # The HTTP port
- "8080:8080" # The Web UI (enabled by --api)
volumes:
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
- $PWD/traefik.toml:/traefik.toml
whoami:
image: emilevauge/whoami
labels:
- traefik.enable=true
- "traefik.frontend.rule=Host:whoami.docker.localhost"
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
- "traefik.http.middlewares.test-replacepath.replacepath.path=/foo"
- "traefik.http.middlewares.testauth.ForwardAuth.Address=http://localhost:55391/api/Auth"
I was struggling with this for a while as well, and couldn't find an answer anywhere other than fairly hidden in the Traefik docs. The ForwardAuth docs don't actually mention this, but looking at the middlewares overview configuration example I suddenly noticed that you not only have to specify the middleware, you also have to apply it to the router.
Adding this label to whoami service should do the trick:
- "traefik.http.routers.whoami.middlewares=testauth"
Note that you can also specify multiple middlewares here, by comma-separating them, so you could add the other middlewares you defined like so:
- "traefik.http.routers.whoami.middlewares=testauth,test-redirectscheme,test-replacepath"