lucaslorentz / caddy-docker-proxy

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

Is it possible to automate reverse proxy entries? #551

Open alber70g opened 7 months ago

alber70g commented 7 months ago

I'd like to have a default reverse proxy entry for any container on the {containername}.local.co, with the port that's exposed, if there's only one (if multiple it's to be added manually). Is this possible?

This would make my life so much more easy when installing new containers for local use only. Then I could make the public ones available by manually adding labels on the domains that I'd like to use.

francislavoie commented 7 months ago

I don't understand. Can't you just add labels to do that?

alber70g commented 7 months ago

Yes, but, I don't want to add labels. I want to implicitly give EVERY container a locally proxied domain name as a default

francislavoie commented 7 months ago

That's not supported (and I'm not sure it would be possible anyway).

The problem is the port number that a container is exposing it not known. So automatically proxying is not possible without some configuration.

Mikle-Bond commented 7 months ago

Depends on how much "automatic" you want it to be. And what sacrifices you're willing to make.

With docker-compose you can use YAML anchors . Add this on top of your configuration (before services):

networks:
    ingress:

x-constants:
    - &default_restart unless-stopped

x-snippets:
    env: &with_env
        environment: &env
            PUID: 
            PGID:
            TZ:

    net: &with_net
        networks: &net
            ingress:

x-labels:
- &caddy_auto_name
    caddy: &auto_name '{{ index .Labels "com.docker.compose.service" }}.${BASE_DOMAIN}'

- &simple_expose
    caddy: *auto_name
    caddy.reverse_proxy: 'http://{{ index .Labels "com.docker.compose.service" }}:{{ (index .Ports 0).PrivatePort }}'

- &tls_strip
    caddy.reverse_proxy.transport: http
    caddy.reverse_proxy.transport.tls_insecure_skip_verify:

x-templates:
    default: &default
        environment: *env
        networks: *net
        restart: *default_restart

    dummy: &dummy
        restart: *default_restart
        environment: *env
        init: true
        network_mode: none
        read_only: true
        image: alpine
        command: sleep infinity

This is part of my config, which I stripped down to your use-case.

After that you can use it like this:

services:
    qr:
        <<: *default
        build: ./build/qr
        environment:
            PORT: 8080
        labels: *simple_expose
    prometheus:
        <<: *default
        image: prom/prometheus:latest
        command:
            - --config.file=/etc/prometheus/config.yml
            - --web.external-url=https://prometheus.${BASE_DOMAIN}
            - --log.format=json
        volumes:
            - /config/prometheus:/etc/prometheus/
            - data_prometheus:/prometheus
        expose:
            - "9090/tcp"
        labels: *simple_expose

    graph:
        <<: *default
        image: grafana/grafana
        environment:
            <<: *env
            GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: jasonlashua-prtg-datasource,natel-discrete-panel,fatcloud-windrose-panel
            GF_SERVER_ROOT_URL: https://graf.${BASE_DOMAIN}
            GF_LOG_CONSOLE_FORMAT: json

        volumes:
            - data_grafana:/var/lib/grafana
            - conf_grafana:/etc/grafana/provisioning
        expose:
            - "3000/tcp"
        labels: *simple_expose

    # ....

It will use the service name, $BASE_DOMAIN from .env file, and first exposed/forwarded port of your container as parameters for reverse_proxy statement.

yet another approach

You can use a snippet. Define it in global configuration file:

# enable global config in caddy service
environment:
    CADDY_DOCKER_CADDYFILE_PATH: /config/Caddyfile.globals
    CADDY_DOCKER_PROCESS_CADDYFILE: "true"
    BASE_DOMAIN: # null means "pick from the .env file"
(rproxy) {
        {args[0]}.{env.BASE_DOMAIN} {
                reverse_proxy {args[1:]}
        }
}
services:
    foo:
        <<: *default
        # ....
        labels:
            caddy: import rproxy foo "{{upstreams 8080}}"

I used this in the past. Pros:

This approach is better if you have to throw more labels at your containers (for example if you use pawelmalak/flame, benphelps/homepage or something similar.

yet another another approach

You can combine snippets and cryptic go-templates.

Disclaimer: I haven't tried this one. I use what I described in the first part, because I like autogenerated config to be more verbose

(rproxy) {
        {args[0]}.{env.BASE_DOMAIN} {
                reverse_proxy {args[1:]}
        }
}
x-labels:
- &simple_expose
    caddy: import rproxy {{ index .Labels "com.docker.compose.service" }} {{ (index .Ports 0).PrivatePort | upstreams }}

It should work like this, or maybe with some quotes thrown here or there.

kiwina commented 6 months ago

i think hes trying to archive something similar to OrbStack witch would be awesome as a plugin, this is how i landed here

alber70g commented 6 months ago

That's not supported (and I'm not sure it would be possible anyway).

The problem is the port number that a container is exposing it not known. So automatically proxying is not possible without some configuration.

@francislavoie We can retrieve the exposed port numbers and use the first one, right? Of course, unexposed ports won't be found. I'd like to filter on "network" as well. In my case, I add all my proxied containers to the network "proxynetwork".

docker container ls --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}\t{{.Networks}}" -a

i think hes trying to archive something similar to OrbStack witch would be awesome as a plugin, this is how i landed here

@kiwina I just looked at this, and this is exactly what I want.

In the end, I could make a custom "docker monitor" and have it add the labels manually, but ideally, this would be done by this service since it's already doing something similar.