Api-platform docker deployment behind nginx-proxy - docker

I'm trying to deploy a quick demo of api-platform.
In advance, my apologize if I missed something in a discssion or a documentation, I'm not used to work on deployment and maybe not looking at the right places.
I use a server where I already have some docker containers running, for that I use nginxproxy/nginx-proxy docker container as reverse proxy.
I looked at the api-platofrm documentation on how to deploy with docker-compose : https://api-platform.com/docs/deployment/docker-compose/#deploying but since I'm working on this subject I evolve between "502 bad gateway" or "The page is not redirected correctly" errors.
Actually I've got this docker-compose.yml :
version: "3.4"
services:
php:
build:
context: ./api
target: api_platform_php
depends_on:
- database
restart: unless-stopped
volumes:
- php_socket:/var/run/php
healthcheck:
interval: 10s
timeout: 3s
retries: 3
start_period: 30s
networks:
- 'cloud'
caddy:
build:
context: api/
target: api_platform_caddy
depends_on:
- php
environment:
PWA_UPSTREAM: pwa:3000
SERVER_NAME: ${SERVER_NAME:-localhost, caddy:80}
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_PUBLISHER_JWT_KEY:-!ChangeMe!}
MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_SUBSCRIBER_JWT_KEY:-!ChangeMe!}
restart: unless-stopped
volumes:
- php_socket:/var/run/php
- caddy_data:/data
- caddy_config:/config
ports:
# HTTP
- target: 80
published: 7000
protocol: tcp
# HTTPS
- target: 443
published: 7001
protocol: tcp
# HTTP/3
- target: 443
published: 7001
protocol: udp
networks:
- 'cloud'
database:
image: postgres:13-alpine
environment:
- POSTGRES_DB=api
- POSTGRES_PASSWORD=!ChangeMe!
- POSTGRES_USER=api-platform
volumes:
- db_data:/var/lib/postgresql/data:rw
# you may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
# - ./api/docker/db/data:/var/lib/postgresql/data:rw
networks:
- 'cloud'
volumes:
php_socket:
db_data:
caddy_data:
caddy_config:
networks:
cloud:
external: true
and this docker-compose.preprod.yml file :
version: "3.4"
# Preproduction environment override
services:
php:
environment:
APP_ENV: prod
APP_SECRET: ${APP_SECRET}
caddy:
environment:
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_PUBLISHER_JWT_KEY:-!ChangeMe!}
MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_SUBSCRIBER_JWT_KEY:-!ChangeMe!}
VIRTUAL_HOST: api-preprod.melofeel.com
VIRTUAL_PORT: 80
LETSENCRYPT_HOST: api-preprod.melofeel.com
I'm deploying it with gitlab-ci and launching it with this command :
SERVER_NAME=******.*****.com APP_SECRET=testdeploy POSTGRES_PASSWORD=testdeploy CADDY_MERCURE_JWT_SECRET=testdeploy docker-compose -f api_preprod/docker-compose.yml -f api_preprod/docker-compose.preprod.yml up -d
I've tried to run it with and without Caddy, without I always get "502 bad gateway".
The 3 containers are running, but when I look on Caddy logs I've got this messages :
{"level":"error","ts":1648201680.3190682,"logger":"tls.issuance.acme.acme_client","msg":"challenge failed","identifier":"*****.*****.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"Invalid response from http://*****.*****.com/.well-known/acme-challenge/O9zJRdytI8vlf7yZLRcV9pzUlmI73ysCqQJTHg8XWTw [188.165.218.39]: 404","instance":"","subproblems":[]}}
I've tried to deactivate the automatic https from caddy, nginx-proxy is already responsible for doing it, but it's seems to not work.
My Caddyfile :
{
# Debug
{$DEBUG}
# HTTP/3 support
servers {
protocol {
experimental_http3
},
auto_https disable_redirects
}
}
{$SERVER_NAME}
log
# Matches requests for HTML documents, for static files and for Next.js files,
# except for known API paths and paths with extensions handled by API Platform
#pwa expression `(
{header.Accept}.matches("\\btext/html\\b")
&& !{path}.matches("(?i)(?:^/docs|^/graphql|^/bundles/|^/_profiler|^/_wdt|\\.(?:json|html$|csv$|ya?ml$|xml$))")
)
|| {path} == "/favicon.ico"
|| {path} == "/manifest.json"
|| {path} == "/robots.txt"
|| {path}.startsWith("/_next")
|| {path}.startsWith("/sitemap")`
route {
root * /srv/api/public
mercure {
# Transport to use (default to Bolt)
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
# Publisher JWT key
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
# Subscriber JWT key
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
# Allow anonymous subscribers (double-check that it's what you want)
anonymous
# Enable the subscription API (double-check that it's what you want)
subscriptions
# Extra directives
{$MERCURE_EXTRA_DIRECTIVES}
}
vulcain
push
# Add links to the API docs and to the Mercure Hub if not set explicitly (e.g. the PWA)
header ?Link `</docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", </.well-known/mercure>; rel="mercure"`
# Comment the following line if you don't want Next.js to catch requests for HTML documents.
# In this case, they will be handled by the PHP app.
reverse_proxy #pwa http://{$PWA_UPSTREAM}
php_fastcgi unix//var/run/php/php-fpm.sock
encode zstd gzip
file_server
}
Thanks in advance for any help and explanation that allow me to understand what the problem is.

I managed to get it working by changing the caddy server name to
SERVER_NAME: ${SERVER_NAME:-localhost, caddy}:80
Then in Nginx Proxy redirect it to the IP over http to port 80 with a HTTPS certificate etc but I've found this breaks Vulcain for preloading fetch requests with the following error
A preload for '<URL>' is found, but is not used because the request credentials mode does not match. Consider taking a look at crossorigin attribute.
I haven't manage to fix it yet and can confirm my setup works when I point my DNS record directly to the Server running the docker as opposed to Nginx-Proxy

Related

DDEV not serving Symfony Mercure-Hub over HTTPS

I am running a Symfony Project via drud/ddev (nginx) for local development.
I did this many times before and had no issues whatsoever.
In my recent project I have to use the Mercure-Hub to push Notifications from the server to the client.
I required the symfony/mercure-bundle via composer and copied the generated docker-compose content into a docker-compose.mercure.yaml (.ddev/docker-compose.mercure.yaml)
After starting the container the Mercure-Hub works seamlessly but is only reachable over http.
My problem: I only have beginner knowledge in the field of nginx and docker-compose.
I am thankful for every bit of advice! :)
Steps to reproduce
Setup basic Symfony Project and run it via DDEV.
Require symfony/mercure-bundle.
Copy docker-compose.yaml and docker-compose.override.yaml content to a docker-compose.mercure.yaml in the .ddev folder (change the port).
Configure Mercure-Hub URL in .env.
Start the container and visit [DDEV-URL]:[MERCURE-PORT] / subscribe a Mercure topic.
My problem
Mercure-Hub only reachable via http.
HTTPS call gets an 'ERR_SSL_PROTOCOL_ERROR'
My wish
Access the Mercure-Hub URL / subscribe to Mercure topics via HTTPS.
What I've tried
Reading the Mercure-Hub Docs and trying to adapt the Docker SSL / HTTPS instructions to my local drud/ddev environment
Adding another server to the nginx configuration as in the Mercure-Cookbook "Using NGINX as an HTTP/2 Reverse Proxy in Front of the Hub"
Googling a bunch
Hours of trial and error
Files
ddev config.yaml
name: project-name
type: php
docroot: public
php_version: "8.1"
webserver_type: nginx-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: true
additional_hostnames: []
additional_fqdns: []
database:
type: mariadb
version: "10.4"
nfs_mount_enabled: true
mutagen_enabled: false
use_dns_when_possible: true
composer_version: "2"
web_environment: []
nodejs_version: "16"
docker-compose.mercure.yaml
version: '3'
services:
###> symfony/mercure-bundle ###
mercure:
image: dunglas/mercure
restart: unless-stopped
environment:
SERVER_NAME: ':3000'
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
# Set the URL of your Symfony project (without trailing slash!) as value of the cors_origins directive
MERCURE_EXTRA_DIRECTIVES: |
cors_origins http://127.0.0.1:8000
# Comment the following line to disable the development mode
command: /usr/bin/caddy run -config /etc/caddy/Caddyfile.dev
volumes:
- mercure_data:/data
- mercure_config:/config
ports:
- "3000:3000"
###< symfony/mercure-bundle ###
volumes:
###> symfony/mercure-bundle ###
mercure_data:
mercure_config:
###< symfony/mercure-bundle ###
.env
###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
MERCURE_URL=http://ddev-pnp-master-mercure-1:3000/.well-known/mercure
# The public URL of the Mercure hub, used by the browser to connect
MERCURE_PUBLIC_URL=http://ddev-pnp-master-mercure-1:3000/.well-known/mercure
# The secret used to sign the JWTs
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
###< symfony/mercure-bundle ###
Edit 1
I changed my docker-compose thanks to the advice from rfay.
(only showing the relevant part below)
[...]
services:
mercure:
image: dunglas/mercure
restart: unless-stopped
expose:
- "3000"
environment:
- SERVER_NAME=":3000"
- HTTP_EXPOSE=9998:3000
- HTTPS_EXPOSE=9999:3000
[...]
replaced ports with expose
added HTTP_EXPOSE & HTTPS_EXPOSE
Problem with this
Now my problem is that the container doesn't expose any ports (see docker desktop screenshot below).
docker desktop port screenshot
Solution
With the help of rfay I found the solution (which consisted of reading the ddev documentation properly lol).
What I did
replacing ports with expose
adding VIRTUAL_HOST, HTTP_EXPOSE and HTTPS_EXPOSE under environment
adding container_name & labels (see code below)
My final docker-compose.mercure.yaml
version: '3'
services:
mercure:
image: dunglas/mercure
restart: unless-stopped
container_name: "ddev-${DDEV_SITENAME}-mercure-hub"
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
expose:
- "3000"
environment:
VIRTUAL_HOST: $DDEV_HOSTNAME
SERVER_NAME: ":3000"
HTTP_EXPOSE: "9998:3000"
HTTPS_EXPOSE: "9999:3000"
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
MERCURE_EXTRA_DIRECTIVES: |
cors_origins https://project-name.ddev.site
# Comment the following line to disable the development mode
command: /usr/bin/caddy run -config /etc/caddy/Caddyfile.dev
volumes:
- mercure_data:/data
- mercure_config:/config
volumes:
mercure_data:
mercure_config:
With this docker-compose in place my mercure container is available via HTTPS over the port 9999.
For further information see the ddev documentation: https://ddev.readthedocs.io/en/latest/users/extend/custom-compose-files/#docker-composeyaml-examples
The solution in https://stackoverflow.com/a/74735903/21252828 does not work until you add a minus before the config option at the command:
...
command: /usr/bin/caddy run --config /etc/caddy/Caddyfile.dev
...
Otherwise the container fails (and restarts endless).
Maybe you can edit your post Christian Neugebauer?

Docker Swarm - Requests fail to reach a service on a different node

I've setup a Docker Swarm with Traefik v2 as the reverse proxy, and have been able to access the dashboard with no issues.
I am having an issue where I cannot get a response from any service that runs on a different node to the node Traefik is running on. I'm been testing and researching and presuming it's a network issue of some type.
I've done some quick testing with a empty Nginx image and was able to deploy another stack and get a response if the image was on the same node. Other stacks on the swarm which deploy across multiple nodes (but not including the Traefik node) are able to communicate to each other without issues).
Here is the test stack to provide some context of what I was using.
version: '3.8'
services:
test:
image: nginx:latest
deploy:
replicas: 1
placement:
constraints:
- node.role==worker
labels:
- "traefik.enable=true"
- "traefik.docker.network=uccser-dev-public"
- "traefik.http.services.test.loadbalancer.server.port=80"
- "traefik.http.routers.test.service=test"
- "traefik.http.routers.test.rule=Host(`TEST DOMAIN`) && PathPrefix(`/test`)"
- "traefik.http.routers.test.entryPoints=web"
networks:
- uccser-dev-public
networks:
uccser-dev-public:
external: true
The uccser-dev-public network is an overlay network across all nodes, with no encryption.
If I added a constraint to specify the Traefik node, then the requests worked with no issues. However, if I switched it to a different node, I get the Traefik 404 page.
The Traefik dashboard is showing it sees the service.
However the access logs show the following:
proxy_traefik.1.6fbx58k4n3fj#SWARM_NODE | IP_ADDRESS - - [21/Jul/2021:09:03:02 +0000] "GET / HTTP/2.0" - - "-" "-" 1430 "-" "-" 0ms
It's just blank, and I don't know where to proceed from here. The normal log shows no errors that I can see.
Traefik stack file:
version: '3.8'
x-default-opts:
&default-opts
logging:
options:
max-size: '1m'
max-file: '3'
services:
# Custom proxy to secure docker socket for Traefik
docker-socket:
<<: *default-opts
image: tecnativa/docker-socket-proxy
networks:
- traefik-docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
NETWORKS: 1
SERVICES: 1
SWARM: 1
TASKS: 1
deploy:
placement:
constraints:
- node.role == manager
# Reverse proxy for handling requests
traefik:
<<: *default-opts
image: traefik:2.4.11
networks:
- uccser-dev-public
- traefik-docker
volumes:
- traefik-public-certificates:/etc/traefik/acme/
ports:
- target: 80 # HTTP
published: 80
protocol: tcp
mode: host
- target: 443 # HTTPS
published: 443
protocol: tcp
mode: host
command:
# Docker
- --providers.docker
- --providers.docker.swarmmode
- --providers.docker.endpoint=tcp://docker-socket:2375
- --providers.docker.exposedByDefault=false
- --providers.docker.network=uccser-dev-public
- --providers.docker.watch
- --api
- --api.dashboard
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --log.level=DEBUG
- --global.sendAnonymousUsage=false
deploy:
placement:
constraints:
- node.role==worker
# Dynamic Configuration
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`SWARM_NODE`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
- "traefik.http.routers.dashboard.service=api#internal"
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999" # Dummy service for Swarm port detection. The port can be any valid integer value.
volumes:
traefik-public-certificates: {}
networks:
# This network is used by other services
# to connect to the proxy.
uccser-dev-public:
external: true
# This network is used for Traefik to talk to
# the Docker socket.
traefik-docker:
driver: overlay
driver_opts:
encrypted: 'true'
Any ideas?
Further testing showed other services were working on different nodes, so figured it must be an issue with my application. Turns out my Django application still had a bunch of settings configured for it's previous hosting location regarding HTTPS. As it wasn't passing the required settings it had denied the requests before the were processed. I needed to have the logging level for gunicorn (WSGI) lower to see more information too.
In summary, Traefik and Swarm were fine.
Another reason for this can be that Docker Swarm ports haven't been opened on all of the nodes. If you're using UFW that means running the following on every machine participating in the swarm:
ufw allow 2377/tcp
ufw allow 7946/tcp
ufw allow 7946/udp
ufw allow 4789/udp

How to properly call another docker container via axios?

So I'm currently building a docker setup with a REST API and a separate frontend. My backend consists of Symfony 5.2.6 as REST API and my frontend is a simple Vue application.
When I try to call my API from the vue application via localhost or 127.0.0.1, I get a "Connection refused" error. When I try to call the API via the external IP of my server, I run into CORS issues. This is my first setup like this, so I'm kind of at a loss.
This is my docker setup:
version: "3.8"
services:
# VUE-JS Instance
client:
build: client
restart: always
logging:
driver: none
volumes:
- ./client:/app
- /app/node_modules
environment:
- CHOKIDAR_USEPOLLING=true
- NODE_ENV=development
ports:
- 8080:8080
# SERVER
php:
build: php-fpm
restart: always
ports:
- "9002:9000"
volumes:
- ./server:/var/www/:cached
- ./logs/symfony:/var/www/var/logs:cached
# WEBSERVER
nginx:
build: nginx
restart: always
ports:
- "80:80"
volumes_from:
- php
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./logs/nginx/:/var/log/nginx:cached
So what is the correct way to establish the connection between those two containers?
The client app runs on port 8080 but nginx on 80 is a different URL and it should be a CORS error.
To avoid it, in the PHP app, you have to add response header:
Access-Control-Allow-Origin: http://localhost:8080 or
Access-Control-Allow-Origin: *.
Another solution is to configure all in one domain on this same port.

Traefik 2.0 behind docker swarm not working

So, I'm trying to use Traefik to load-balance my web apps via docker swarm.
However, I already tried many configurations but somehow not works. I already read the documentation and read some articles in the internet. Unfortunately, many articles references traefik 1.x instead traefik 2.0.
Here is my docker-stack.yml for traefik
version: '3.7'
services:
traefik:
image: traefik:2.0
deploy:
mode: global
placement:
constraints:
- node.role == manager
restart_policy:
condition: on-failure
labels:
- traefik.docker.network=load_balancer
configs:
- source: traefik
target: /etc/traefik/traefik.yml
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- load_balancer
configs:
traefik:
file: ./traefik.yml
networks:
load_balancer:
external: true
name: load_balancer
whoami.yml (for testing purpose)
version: '3.7'
services:
whoami:
image: containous/whoami
deploy:
labels:
- traefik.enable=true
- traefik.docker.network=load_balancer
- traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)
networks:
- load_balancer
networks:
load_balancer:
external: true
name: load_balancer
My traefik.yml
log:
level: DEBUG
api:
insecure: true
providers:
docker:
exposedByDefault: false
swarmMode: true
watch: true
docker network ls
hxjw9rytw3od load_balancer overlay swarm
curl -H Host:whoami.docker.localhost http://127.0.0.1
404 page not found
Maybe you have to add entrypoints declaration in your config: https://docs.traefik.io/routing/entrypoints/
I hope is not too late to answer this. I've found the solution.
First, you can either use traefik.yml for configuration or use traefik cli flags in docker-compose command field.
Here is the working approach if you use traefik.yml as your main configuration.
It is recommended to use directory over file.
traefik.yml
log:
level: DEBUG
api:
insecure: true
# This is required
entryPoints:
web:
address: ':80'
websecure:
address: ':443'
providers:
file: # Required, if you use traefik.yml as your main configuration
directory: /etc/traefik
watch: true
docker:
exposedByDefault: false
swarmMode: true
watch: true
Working approach if you use traefik cli flags
docker-compose.stack.yml
version: '3.7'
services:
traefik:
image: traefik:2.0
deploy:
mode: global
placement:
constraints:
- node.role == manager
restart_policy:
condition: on-failure
command:
- --log.level=DEBUG
- --api.insecure=true
- --ping=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --providers.docker.swarmmode=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=load_balancer
- --providers.docker.watch=true
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- load_balancer
networks:
load_balancer:
external: true
name: load_balancer
whoami.yml (for testing purpose)
version: '3.7'
services:
whoami:
image: containous/whoami
deploy:
labels:
- traefik.enable=true
- traefik.docker.network=load_balancer
- traefik.http.routers.whoami.entrypoints=web
- traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)
- traefik.http.services.whoami.loadbalancer.server.port=80
networks:
- load_balancer
networks:
load_balancer:
external: true
name: load_balancer
Test via curl
curl -H Host:whoami.docker.localhost 127.0.0.1
I can suggest you to Not use traefik.yml but to use the cli args to configure your instance.
You can make it like this :
version: "3.7"
services:
ingress:
image: traefik:v2.0
networks:
- ingress-net
ports:
- "80:80"
- "443:443"
# TCP Port if needed for any service you have
- "60000:60000"
command:
### ###
# Traefik Global Configuration #
### ###
# Enable DEBUG logs
- "--log.level=DEBUG" # DEBUG, INFO, etc...
- "--ping=true"
# Enable api access without authentification (only GET route so it only possible to get IPs)
- "--api.insecure=true" # You can insecure here, because It accessible only in the container if you didn't open the port.
# Set the provider to Docker
- "--providers.docker=true"
# Set the docker network
- "--providers.docker.network=ingress-net"
# Set to docker swarm cluster
- "--providers.docker.swarmMode=true"
# If False : Do not expose containers to the web by default
- "--providers.docker.exposedByDefault=false"
# Default rule to service-name.example.com
- "--providers.docker.defaultRule=Host(`{{ trimPrefix `/` .Name }}.example.com`)"
# Default http port
- "--entrypoints.http.address=:80"
# Default https port
- "--entrypoints.https.address=:443"
# Enable let's encrypt
- "--certificatesResolvers.certbot=true"
- "--certificatesResolvers.certbot.acme.httpChallenge=true"
- "--certificatesResolvers.certbot.acme.httpChallenge.entrypoint=http"
- "--certificatesResolvers.certbot.acme.email=admin#example.com"
- "--certificatesResolvers.certbot.acme.storage=/letsencrypt/acme.json"
# TCP Entrypoint if needed
- "--entrypoints.tcpendpointname.address=:60000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme.json:/letsencrypt/acme.json
deploy:
replicas: 1
labels:
### ###
# Traefik Dashboard #
### ###
# Enable this endpoint
- traefik.enable=true
##
# Http
#
# Set the service route
- traefik.http.routers.ingress_http.rule=Host(`ingress.example.com`)
# Set the entrypoint (http or https)
- traefik.http.routers.ingress_http.entrypoints=http
# Rule to redirect to http to https
- traefik.http.middlewares.ingress-https-redirect.redirectscheme.scheme=https
# Enable Https redirection
- traefik.http.routers.ingress_http.middlewares=ingress-https-redirect#docker
#
##
##
# Https
#
- traefik.http.routers.ingress_https.rule=Host(`ingress.example.com`)
# Set the entrypoint (http or https)
- traefik.http.routers.ingress_https.entrypoints=https
# Enable Let's encrypt auto certificat creation
- traefik.http.routers.ingress_https.tls.certresolver=certbot
# Enable authentification
- traefik.http.routers.ingress_https.middlewares=ingress-auth#
# Uncommant this to enable basic authentification
# - traefik.http.middlewares.ingress-auth.basicauth.users=admin:$$this$$is$$encrypted$$password
#
##
##
# TCP Endpoint
#
# Set the service route
- "traefik.tcp.routers.tcpendpointname.rule=HostSNI(`*`)"
# Here you can set the host uri if you use tls only.
# - "traefik.tcp.routers.tcpendpointname.rule=HostSNI(`tcp.example.com`)"
# - "traefik.tcp.routers.tcpendpointname.tls=true"
# Set the entrypoin
- "traefik.tcp.routers.tcpendpointname.entrypoints=tcpendpointname"
#
##
##
# Service
#
# Set the service port
- traefik.http.services.ingress.loadbalancer.server.port=8080
#
##
placement:
constraints:
- node.role == manager
networks:
ingress-net:
external: true
I hope this will Help you.
You can use the same labels for any other containers, that work with the same logic.
Since you've got a 404 page not found Traefik seems to be available.
However, when using curl to fetch http://127.0.0.1 this IP address is going to be content of request's Host header field. This in turn is used by Traefik for routing the request. Since your whoami service is meant to match requests for Host whoami.docker.localhost this given response of Traefik is just fine.
Have you tried fetching http://whoami.docker.localhost instead? You might need to inject this hostname into hosts /etc/hosts files prior to testing with curl.
127.0.0.1 whoami.docker.localhost
Optionally, you can try manual HTTP request with a tool like netcat (sometimes available as nc):
# netcat 127.0.0.1 80
GET / HTTP/1.0
Host: whoami.docker.localhost
You need to press Enter twice after entering second line of request as required by HTTP.
Port Detection
Docker Swarm does not provide any port detection information to Traefik.
Therefore you must specify the port to use for communication by using the label traefik.http.services.<service_name>.loadbalancer.server.port (Check the reference for this label in the routing section for Docker).
Traefik's service discovery with Docker requires container labels instead of image labels (check traefik's quickstart).
The following minimal working example works on docker swarm:
version: '3.7'
services:
traefik:
image: traefik:2.0
command: --providers.docker
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
whoami:
image: containous/whoami
labels: # defining a container label instead of an image label
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"

GET http://api:1337/games net::ERR_NAME_NOT_RESOLVED for nuxt.js pages using asyncData

I have somewhat complicated setup with docker. Everything's working as expected except I have this weird problem.
Visiting index page or /pages/_id pages I have no errors. But when I try to open /other-page it crashes. All are using the same API url.
Error found in the console when opening /other-page:
GET http://api:1337/games net::ERR_NAME_NOT_RESOLVED
Not sure what to do, any suggestions?
nuxt.config.js
axios: {
baseURL: 'http://api:1337'
},
docker-compose.yml
version: '3'
services:
api:
build: .
image: strapi/strapi
environment:
- APP_NAME=strapi-app
- DATABASE_CLIENT=mongo
- DATABASE_HOST=db
- DATABASE_PORT=27017
- DATABASE_NAME=strapi
- DATABASE_USERNAME=
- DATABASE_PASSWORD=
- DATABASE_SSL=false
- DATABASE_AUTHENTICATION_DATABASE=strapi
- HOST=api
- NODE_ENV=production
ports:
- 1337:1337
volumes:
- ./strapi-app:/usr/src/api/strapi-app
#- /usr/src/api/strapi-app/node_modules
depends_on:
- db
restart: always
links:
- db
nuxt:
# build: ./app/
image: "registry.gitlab.com/username/package:latest"
container_name: nuxt
restart: always
ports:
- "3000:3000"
links:
- api:api
command:
"npm run start"
nginx:
image: nginx:1.14.2
expose:
- 80
container_name: nginx
restart: always
ports:
- "80:80"
volumes:
- ./nginx:/etc/nginx/conf.d
depends_on:
- nuxt
links:
- nuxt
index.vue
...
async asyncData({ store, $axios }) {
const games = await $axios.$get('/games')
store.commit('games/emptyList')
games.forEach(game => {
store.commit('games/add', {
id: game.id || game._id,
...game
})
})
return { games }
},
...
page.vue
...
async asyncData({ store, $axios }) {
const games = await $axios.$get('/games')
store.commit('games/emptyList')
games.forEach(game => {
store.commit('games/add', {
id: game.id || game._id,
...game
})
})
return { games }
},
...
Nginx conf
upstream webserver {
ip_hash;
server nuxt:3000;
}
server {
listen 80;
access_log off;
connection_pool_size 512k;
large_client_header_buffers 4 512k;
location / {
proxy_pass http://webserver;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_max_temp_file_size 0;
}
UPDATE:
Tried what Thomasleveil suggested. Now I'm receiving following error:
nuxt | [2:09:35 PM] Error: connect ECONNREFUSED 127.0.0.1:80
So, it seems like now /api is being forwarded to 127.0.0.1:80. Not sure why ^^
nuxt.config.js
axios: {
baseURL: '/api'
},
server: {
proxyTable: {
'/api': {
target: 'http://localhost:1337',
changeOrigin: true,
pathRewrite: {
"^/api": ""
}
}
}
}
docker-compose.yml
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 # listen to the Docker events
networks:
- mynet
api:
build: .
image: strapi/strapi
container_name: api
environment:
- APP_NAME=strapi-app
- DATABASE_CLIENT=mongo
- DATABASE_HOST=db
- DATABASE_PORT=27017
- DATABASE_NAME=strapi
- DATABASE_USERNAME=
- DATABASE_PASSWORD=
- DATABASE_SSL=false
- DATABASE_AUTHENTICATION_DATABASE=strapi
- HOST=api
- NODE_ENV=development
ports:
- 1337:1337
volumes:
- ./strapi-app:/usr/src/api/strapi-app
#- /usr/src/api/strapi-app/node_modules
depends_on:
- db
restart: always
networks:
- mynet
labels:
- "traefik.backend=api"
- "traefik.docker.network=mynet"
- "traefik.frontend.rule=Host:example.com;PathPrefixStrip:/api"
- "traefik.port=1337"
db:
image: mongo
environment:
- MONGO_INITDB_DATABASE=strapi
ports:
- 27017:27017
volumes:
- ./db:/data/db
restart: always
networks:
- mynet
nuxt:
# build: ./app/
image: "registry.gitlab.com/username/package:latest"
container_name: nuxt
restart: always
ports:
- "3000:3000"
command:
"npm run start"
networks:
- mynet
labels:
- "traefik.backend=nuxt"
- "traefik.frontend.rule=Host:example.com;PathPrefixStrip:/"
- "traefik.docker.network=web"
- "traefik.port=3000"
networks:
mynet:
external: true
Visiting index page or /pages/_id pages I have no errors. But when I try to open /other-page it crashes.
To reformulate:
I have a main page at / that shows some links targeting pages at /pages/_id (where _id is a valid game id)
When I open / or /pages/_id, the content shows up
But if I click a link from page / targeting /pages/xxx (where xxx is a valid id), I got an error
Furthermore if I refresh the page, I then see the content and not the error
content for those pages comes from an api server. Pages are supposed to fetch the content by calling the api server and then render the page contents with the response.
What's happening here?
AsyncData
The way asyncData works in nuxt.js is the following:
on first page load
the user enters the url http://yourserver/pages/123 in its browser
the nuxt web server handles the request, resolve the route and mount the vue component for that page
the asyncData method from the vue component is called from the nuxt.js server side
the nuxt.js server (not the user browser) then fetch the content by making different call to http://api:1337/games/123, receive the response and the content renders.
when the user clicks a link for another page
Something a bit different happens now.
The user is still on the page http://api:1337/games/123 which has a link to the main page listing all the games (http://yourserver/) and click it.
the browser does not load any new page. Instead, the user browser makes an ajax call to http://api:1337/games to try to fetch the new content. And fails due to a name resolution error
Why?
This is a feature brought to you by nuxt.js to speed up page content loading time. from the documentation, the important bit of information is:
asyncData is called every time before loading the page component. It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes.
server-side means the call is made from the nuxt server to the api server
client-side means the call is made from the user browser to the api server
Now the fun part:
the nuxt server is running in a first container
the api server is running in a second container and is listening on port 1337
from the nuxt container, the url for calling the api server is http://api:1337/, and this works fine
from the user browser, calling http://api:1337 fails (net::ERR_NAME_NOT_RESOLVED) because the user computer does not know how to translate the domain name api to an IP address. And even if it could, this IP Address would be unreachable anyway.
What can be done?
You need to set up a reverse proxy that will forward requests made by the user browsers to url starting with http://yourserver/api/ to the api container on port 1337.
And you need to configure nuxt.js so that links to the api made client-side (from the user browser) use the url http://yourserver/api instead of http://api:1337/
And you need to configure nuxt.js so that it keeps calling http://api:1337 for calls made server-side.
adding a reverse proxy for calls made from nuxt (server-side)
Since you are using the nuxt.js Axios module to make calls to the api container, you are half way there.
The Axios module has a proxy option that can be set to true in nuxtjs.config.js
Bellow is an example of setting up a reverse proxy for your project using Traefik, but the documentation state that the proxy is incompatible with the usage of the baseURL option. The prefix option must be used instead.
Your nuxt.config.js should then look like this:
axios: {
prefix: '/api',
proxy: true
},
proxy: {
'/api/': {
target: 'http://localhost:1337',
pathRewrite: {
'^/api/': ''
}
}
},
This works fine from your development computer, where if strapi is running and responding at http://localhost:1337. But this won't work in a container because we there need to replace http://localhost:1337 with http://api:1337.
To do so, we can introduce an environment variable (STRAPI_URL):
axios: {
prefix: '/api',
proxy: true
},
proxy: {
'/api/': {
target: process.env.STRAPI_URL || 'http://localhost:1337',
pathRewrite: {
'^/api/': ''
}
}
},
We will later set the STRAPI_URL in the docker-compose.yml file.
adding a reverse proxy for calls made from the user browser (client-side)
Since I gave up on implementing reverse proxies with nginx when using docker, here's an example with Traefik:
docker-compose.yml:
version: '3'
services:
reverseproxy: # see https://docs.traefik.io/#the-traefik-quickstart-using-docker
image: traefik:1.7
command: --docker
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
api:
image: strapi/strapi
environment:
- ...
expose:
- 1337
labels:
traefik.frontend.rule: PathPrefixStrip:/api
traefik.port: 1337
nuxt:
image: ...
expose:
- 3000
command:
"npm run start"
labels:
traefik.frontend.rule: PathPrefixStrip:/
traefik.port: 3000
Now all HTTP requests made by the user browser to http://yourserver will be handled by the Traefik reverse proxy.
Traefik will configure forwarding rules by looking at labels starting with traefik. on the nuxt and api containers.
What changed?
You now have 2 reverse proxies:
one for server-side requests (the nuxt.js Proxy module)
one for client-side requests (Traefik)
It's not done yet
We now need to instruct the nuxt.js Proxy module that it must forward requests to http://api:1337/. We are going to use the STRAPI_URL environment variable for that.
And we need to instruct nuxt Axios module that the user browser must call the api on http://yourserver/api. This is done with the API_URL_BROWSER environment variable.
All together
nuxt.config.js
axios: {
prefix: '/api',
proxy: true
},
proxy: {
'/api/': {
target: process.env.STRAPI_URL || 'http://localhost:1337',
pathRewrite: {
'^/api/': ''
}
}
},
docker-compose.yml
version: '3'
services:
reverseproxy: # see https://docs.traefik.io/#the-traefik-quickstart-using-docker
image: traefik:1.7
command: --docker
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
api:
image: strapi/strapi
environment:
- ...
expose:
- 1337
labels:
traefik.frontend.rule: PathPrefixStrip:/api
traefik.port: 1337
nuxt:
image: ...
expose:
- 3000
command:
"npm run start"
environment:
NUXT_HOST: 0.0.0.0
STRAPI_URL: http://api:1337/
API_URL_BROWSER: /api
labels:
traefik.frontend.rule: PathPrefixStrip:/
traefik.port: 3000

Resources