lucaslorentz / caddy-docker-proxy

Caddy as a reverse proxy for Docker
MIT License
2.61k stars 163 forks source link

How to make requests to proxy service inside another proxy service? #590

Closed annas-atchia-bocasay closed 3 months ago

annas-atchia-bocasay commented 4 months ago

I have two API Platform projects (project 1 and 2) running through a Caddy docker proxy server. Requests to both projects inside the proxy server container works perfectly.

However when I'm making requests to let's say for e.g. my project 1 container inside the 2nd project container, I'm getting a 200 OK response but the body is always empty.

My question is how do I make request to another service from inside the container of another service ?

Below is a request and response for the above.

$ curl -v poc-api-platform-1-php-1
* Host poc-api-platform-1-php-1:80 was resolved.
* IPv6: (none)
* IPv4: 172.26.0.3
*   Trying 172.26.0.3:80...
* Connected to poc-api-platform-1-php-1 (172.26.0.3) port 80
> GET / HTTP/1.1
> Host: poc-api-platform-1-php-1
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Thu, 07 Mar 2024 10:43:43 GMT
< Content-Length: 0
< 
* Connection #0 to host poc-api-platform-1-php-1 left intact

For testing purposes, I added a whoami service and I was able to make a successful request towards the whoami container inside the 2nd project container.

$ curl -v whoami1-whoami1-1
* Host whoami1-whoami1-1:80 was resolved.
* IPv6: (none)
* IPv4: 172.26.0.4
*   Trying 172.26.0.4:80...
* Connected to whoami1-whoami1-1 (172.26.0.4) port 80
> GET / HTTP/1.1
> Host: whoami1-whoami1-1
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Thu, 07 Mar 2024 10:48:17 GMT
< Content-Length: 161
< Content-Type: text/plain; charset=utf-8
< 
Hostname: a3afb7eed803
IP: 127.0.0.1
IP: 172.26.0.4
RemoteAddr: 172.26.0.5:38460
GET / HTTP/1.1
Host: whoami1-whoami1-1
User-Agent: curl/8.5.0
Accept: */*

* Connection #0 to host whoami1-whoami1-1 left intact

Below are my docker configurations:

  1. Caddy docker proxy server
version: "3.7"

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    ports:
      - 80:80
      - 443:443
    environment:
      - CADDY_INGRESS_NETWORKS=caddy_proxy_test
    networks:
      - caddy_proxy_test
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
    restart: unless-stopped

networks:
  caddy_proxy_test:
    external: true
  default:

volumes:
  caddy_data: {}
  1. Project 1
# Development environment override
services:
  php:
    build:
      context: ./api
      target: frankenphp_dev
    depends_on:
      - database
    volumes:
      - ./api:/app
      - /app/var
      - ./api/frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./api/frankenphp/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro
      # If you develop on Mac or Windows you can remove the vendor/ directory
      #  from the bind-mount for better performance by enabling the next line:
      #- /app/vendor
    environment:
      MERCURE_EXTRA_DIRECTIVES: demo
      SERVER_NAME: http://${SERVER_NAME:-localhost}, php:80
      # See https://xdebug.org/docs/all_settings#mode
      XDEBUG_MODE: "${XDEBUG_MODE:-off}"
    extra_hosts:
      # Ensure that host.docker.internal is correctly defined on Linux
      - host.docker.internal:host-gateway
    tty: true
    labels:
      caddy: poc-api-platform-1.localhost
      caddy.reverse_proxy: "{{upstreams 80}}"
      caddy.reverse_proxy.header_up: Custom-Forwarded-Proto {scheme}
    networks:
      - default
      - caddy_proxy_test

  pwa:
    build:
      context: ./pwa
      target: dev
    volumes:
      - ./pwa:/srv/app
    environment:
      API_PLATFORM_CREATE_CLIENT_ENTRYPOINT: http://php
      API_PLATFORM_CREATE_CLIENT_OUTPUT: .
      # On Linux, you may want to comment the following line for improved performance
      WATCHPACK_POLLING: "true"

###> doctrine/doctrine-bundle ###
  database:
    ports:
      - target: 5432
        published: ${MYSQL_PORT:-5432}
        protocol: tcp
###< doctrine/doctrine-bundle ###

###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###

networks:
  caddy_proxy_test:
    external: true
  1. Project 2
# Development environment override
services:
  php:
    build:
      context: ./api
      target: frankenphp_dev
    depends_on:
      - database
    volumes:
      - ./api:/app
      - /app/var
      - ./api/frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./api/frankenphp/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro
      # If you develop on Mac or Windows you can remove the vendor/ directory
      #  from the bind-mount for better performance by enabling the next line:
      #- /app/vendor
    environment:
      MERCURE_EXTRA_DIRECTIVES: demo
      SERVER_NAME: http://${SERVER_NAME:-localhost}, php:80
      # See https://xdebug.org/docs/all_settings#mode
      XDEBUG_MODE: "${XDEBUG_MODE:-off}"
    extra_hosts:
      # Ensure that host.docker.internal is correctly defined on Linux
      - host.docker.internal:host-gateway
    tty: true
    labels:
      caddy: poc-api-platform-2.localhost
      caddy.reverse_proxy: "{{upstreams 80}}"
      caddy.reverse_proxy.header_up: Custom-Forwarded-Proto {scheme}
    networks:
      - default
      - caddy_proxy_test

  pwa:
    build:
      context: ./pwa
      target: dev
    volumes:
      - ./pwa:/srv/app
    environment:
      API_PLATFORM_CREATE_CLIENT_ENTRYPOINT: http://php
      API_PLATFORM_CREATE_CLIENT_OUTPUT: .
      # On Linux, you may want to comment the following line for improved performance
      WATCHPACK_POLLING: "true"

###> doctrine/doctrine-bundle ###
  database:
    ports:
      - target: 5432
        published: ${MYSQL_PORT:-5432}
        protocol: tcp
###< doctrine/doctrine-bundle ###

###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###

networks:
  caddy_proxy_test:
    external: true
  1. Whoami
version: '3.7'
services:
  whoami1:
    image: traefik/whoami
    networks:
      - caddy_proxy_test
    labels:
      caddy: whoami1.localhost
      caddy.reverse_proxy: "{{upstreams 80}}"

networks:
  caddy_proxy_test:
    external: true

This is the generated in-memory Caddyfile inside the proxy server container:

poc-api-platform-1.localhost {
        reverse_proxy 172.26.0.3:80 {
                header_up Custom-Forwarded-Proto {scheme}
        }
}
poc-api-platform-2.localhost {
        reverse_proxy 172.26.0.5:80 {
                header_up Custom-Forwarded-Proto {scheme}
        }
}
whoami1.localhost {
        reverse_proxy 172.26.0.4:80
}
francislavoie commented 4 months ago

The key is here:

SERVER_NAME: http://${SERVER_NAME:-localhost}, php:80

You can use http://php/some/path to connect to your PHP container from another container.

But it seems like both your projects are using the same service names so this might be in conflict. You would need to use the full container name (prefixed by the project name, suffixed by a number) but you'd need to also update SERVER_NAME to match. Simpler would be to just use different service names for each, like php-foo and php-bar or whatever.

sowinski commented 4 months ago

This is the generated in-memory Caddyfile inside the proxy server container:

poc-api-platform-1.localhost {
        reverse_proxy 172.26.0.3:80 {
                header_up Custom-Forwarded-Proto {scheme}
        }
}
poc-api-platform-2.localhost {
        reverse_proxy 172.26.0.5:80 {
                header_up Custom-Forwarded-Proto {scheme}
        }
}
whoami1.localhost {
        reverse_proxy 172.26.0.4:80
}

How did you find the generated file?

annas-atchia-bocasay commented 4 months ago

This is the generated in-memory Caddyfile inside the proxy server container:

poc-api-platform-1.localhost {
        reverse_proxy 172.26.0.3:80 {
                header_up Custom-Forwarded-Proto {scheme}
        }
}
poc-api-platform-2.localhost {
        reverse_proxy 172.26.0.5:80 {
                header_up Custom-Forwarded-Proto {scheme}
        }
}
whoami1.localhost {
        reverse_proxy 172.26.0.4:80
}

How did you find the generated file?

Inside the proxy server container and the following file /config/caddy/Caddyfile.autosave.

annas-atchia-bocasay commented 3 months ago

The key is here:

SERVER_NAME: http://${SERVER_NAME:-localhost}, php:80

You can use http://php/some/path to connect to your PHP container from another container.

But it seems like both your projects are using the same service names so this might be in conflict. You would need to use the full container name (prefixed by the project name, suffixed by a number) but you'd need to also update SERVER_NAME to match. Simpler would be to just use different service names for each, like php-foo and php-bar or whatever.

@francislavoie thanks for your reply.

I updated my service names and tried making a request using my SERVER_NAME: http://${SERVER_NAME:-localhost}, php:80 var from the 1st container inside the 2nd container and I'm still getting the empty body.

Please note in the request below, it is trying to connect to 127.0.0.1 instead of the container IP Address.

$ curl -v http://poc-api-platform-1.localhost
* Host poc-api-platform-1.localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Immediate connect fail for ::1: Address not available
*   Trying 127.0.0.1:80...
* Connected to poc-api-platform-1.localhost (127.0.0.1) port 80
> GET / HTTP/1.1
> Host: poc-api-platform-1.localhost
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Thu, 07 Mar 2024 13:23:56 GMT
< Content-Length: 0
< 
* Connection #0 to host poc-api-platform-1.localhost left intact
francislavoie commented 3 months ago

curl -v http://poc-api-platform-1.localhost/

That hostname doesn't match your Caddy config. Caddy is configured to accept requests from the hostname php, not from poc-api-platform-1.localhost.

You need to either use php when you make the request to match the Caddy config (if that's possible and resolves to your container's IP address), or change your Caddy config (i.e. SERVER_NAME) to include http://poc-api-platform-1.localhost (though using *.localhost would be wrong because it always resolves to 127.0.0.1, and "localhost" inside a container means "this same container").

annas-atchia-bocasay commented 3 months ago

@francislavoie below is a request I made by using my service name (I've renamed my service name). I'm still getting an empty response. For info it was run inside the proxy container.

$ curl -v http://php-foo
* Host php-foo:80 was resolved.
* IPv6: (none)
* IPv4: 172.26.0.4
*   Trying 172.26.0.4:80...
* Connected to php-foo (172.26.0.4) port 80
> GET / HTTP/1.1
> Host: php-foo
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Fri, 08 Mar 2024 06:51:13 GMT
< Content-Length: 0
< 
* Connection #0 to host php-foo left intact

This is the Caddyfile in my API Platform project:

{
    {$CADDY_GLOBAL_OPTIONS}

    frankenphp {
        {$FRANKENPHP_CONFIG}
    }

    # https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
    order mercure after encode
    order vulcain after reverse_proxy
    order php_server before file_server
}

{$CADDY_EXTRA_CONFIG}

{$SERVER_NAME:localhost} {
    log {
        # Redact the authorization query parameter that can be set by Mercure
        format filter {
            wrap console
            fields {
                uri query {
                    replace authorization REDACTED
                }
            }
        }
    }

    root * /app/public
    encode {
        zstd
        br
        gzip

        match {
            header Content-Type text/*
            header Content-Type application/json*
            header Content-Type application/javascript*
            header Content-Type application/xhtml+xml*
            header Content-Type application/atom+xml*
            header Content-Type application/rss+xml*
            header Content-Type image/svg+xml*
            # Custom formats supported
            header Content-Type application/ld+json*
        }
    }

    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

    # 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"`
    # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
    header ?Permissions-Policy "browsing-topics=()"

    # 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': '*text/html*'})
            && !path(
                '/docs*', '/graphql*', '/bundles*', '/contexts*', '/_profiler*', '/_wdt*',
                '*.json*', '*.html', '*.csv', '*.yml', '*.yaml', '*.xml'
            )
        )
        || path('/favicon.ico', '/manifest.json', '/robots.txt', '/_next*', '/sitemap*')`

    # 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_server
}

However when I make a request to the whoami1 service, it's working:

$ curl -v http://whoami1
* Host whoami1:80 was resolved.
* IPv6: (none)
* IPv4: 172.26.0.3
*   Trying 172.26.0.3:80...
* Connected to whoami1 (172.26.0.3) port 80
> GET / HTTP/1.1
> Host: whoami1
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Fri, 08 Mar 2024 06:55:09 GMT
< Content-Length: 151
< Content-Type: text/plain; charset=utf-8
< 
Hostname: a3afb7eed803
IP: 127.0.0.1
IP: 172.26.0.3
RemoteAddr: 172.26.0.2:57304
GET / HTTP/1.1
Host: whoami1
User-Agent: curl/8.5.0
Accept: */*

* Connection #0 to host whoami1 left intact

Any idea as to why I can query properly the whoami service and not my API Platform service?

francislavoie commented 3 months ago

Did you actually change your SERVER_NAME variable? The hostname in the request needs to match your site address (i.e. SERVER_NAME).

annas-atchia-bocasay commented 3 months ago

My SERVER_NAME variable is set to poc-api-platform-1.localhost. What should be the expected value?

annas-atchia-bocasay commented 3 months ago

@francislavoie I managed to make it work. Had to change the service name php to php-foo in the following variable: http://${SERVER_NAME:-localhost}, php-foo:80. Thank you for pointing me in the right direction.