Expose containers to public without domain name (with pure IP + port)

As a disclaimer, my question may be similar to some already discussed on this forum, but I just haven't found the exact solution.

That being said, I've got a bunch of containers in Docker Compose that must run under Traefik. I want to expose some of them to be accessible by the great world from a browser. What I don't currently have (and don't have the possibility to get) is a public domain name. I want containers to be accessible by mere IPv4 + port, like: "http(s)://80.X.X.X:1234" or "http(s)://80.X.X.X:9000/some/path". Currently I can't make it happen.

So my Docker Compose is like this:

services:

  traefik:
    image: "traefik:v3.0"
    container_name: "traefik"
    restart: always
    command:
      - --providers.docker=true
      - --providers.docker.watch=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.https.address=:443
      - --entrypoints.http.address=:80
      - --entrypoints.http.http.redirections.entryPoint.to=https
      - --entrypoints.http.http.redirections.entryPoint.scheme=https
      - --entrypoints.http.http.redirections.entrypoint.permanent=true
      - --entrypoints.https.http.tls=true
      - --entrypoints.https.http.tls.certresolver=le
      - --certificatesresolvers.le.acme.tlschallenge=true
      - --certificatesresolvers.le.acme.email=example@example.com
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --log
      - --api
    ports:
      - 80:80
      - 443:443
    volumes:
      - traefik:/letsencrypt:rw
      - /run/docker.sock:/var/run/docker.sock:ro
    networks:
      - my_net
  
  pgadmin: # make this accessible from http(s)://11.11.11.11:5050
    container_name: pgadmin
    image: dpage/pgadmin4:latest
    restart: unless-stopped
    env_file:
      - .env
    expose:
      - ${PGADMIN_PORT-5050}
    environment:
      - PGADMIN_CONFIG_SERVER_MODE=False
      - SCRIPT_NAME=/pgadmin
    volumes:
      - pgadmin:/var/lib/pgadmin/
    networks:
      - my_net
    labels:
      - traefik.enable=true
      - traefik.port=${PGADMIN_PORT-5050}
      - traefik.http.routers.pgadmin.rule=Host(`11.11.11.11`)
      - traefik.http.routers.pgadmin.entrypoints=https
      - traefik.http.routers.pgadmin.tls.certresolver=le
      - traefik.http.middlewares.my_net-compress.compress=true
      - traefik.http.routers.pgadmin.middlewares=my_net-compress
      
  webapp: # make this accessible from http(s)://11.11.11.11:8090
    container_name: webapp
    build:
      context: ./front
      dockerfile: Dockerfile
    restart: unless-stopped
    expose:
      - ${FRONT_PORT-8090}
    volumes:
      - ./front:/app
      - /app/node_modules
    networks:
      - my_net
    labels:
      - traefik.enable=true
      - traefik.port=${FRONT_PORT-8090}
      - traefik.http.routers.qmwebapp.rule=Host(`11.11.11.11`)
      - traefik.http.routers.qmwebapp.entrypoints=https
      - traefik.http.routers.qmwebapp.tls.certresolver=le
      - traefik.http.middlewares.my_net-compress.compress=true
      - traefik.http.routers.qmwebapp.middlewares=my_net-compress

I've replaced the real IPv4 address here with "11.11.11.11" just for illustration (assume my server is run on that address). Can anyone tell me where I may be wrong or inattentive?

That’s a sub-optimal problem description. What is happening or not happening, you get an error message?

Enable and check Traefik debug log (doc) and Traefik access log in JSON format (doc)

TLS/SSL works with domain names. You can not get a LetsEncrypt TLS cert for an IP address. Instead Traefik will create and use a default TLS cert which is not trusted by the browser/client. So you will see a warning.

Note that security through obscurity is not really accepted best practice in IT :wink: Also note that you can get free DNS from services like DuckDNS.

Hello! Thanks for getting back.

What is happening or not happening, you get an error message?

Error 404: The server can't serve the apps, the browser page is empty.

Enable and check Traefik debug log

As you suggested, Traefik is angry with ACME certs for the domain-less endpoints:

traefik  | 2024-07-07T13:13:02Z ERR Unable to obtain ACME certificate for domains error="unable to generate a certificate for the domains [11.11.11.11]: acme: error: 400 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order :: urn:ietf:params:acme:error:unsupportedIdentifier :: NewOrder request included invalid non-DNS type identifier: type \"ip\", value \"11.11.11.11\"" ACME CA=https://acme-v02.api.letsencrypt.org/directory acmeCA=https://acme-v02.api.letsencrypt.org/directory domains=["11.11.11.11"] providerName=le.acme routerName=qmapi@docker rule="(Host(`11.11.11.11`) && PathPrefix(`/api`))"

But no other error messages occur in the logs.

Note that security through obscurity is not really accepted best practice in IT

Noted, but no obscurity was intended on my side. I just hoped someone here would be familiar with similar issues / requests. I realize the problem sits somewhere in my docker-compose config, but just can't zero in on it.

What do you want to achieve?

It seems you have set up two target services with the same .rule=Host(), that won’t really work.

And before you now start to use && PathPrefix(), please be aware that not all web GUI apps like to be place on a path. They usually expect to be /, except when you can configure something like "base path". Best practice is using sub-domains.

I just want my containers to be accessible on the same IP address but with different ports - the ones that are exposed in the docker compose config.

Say, container pgadmin above must be viewable from http://11.11.11.11:5050 and container webapp - from http://11.11.11.11:8090

Would this be possible in Traefik?

PS. While I wrote this, one container DID actually become accessible! Here's its setup:

qmapi:
  expose:
      - ${BACK_PORT-8000}
  labels:
      - traefik.enable=true
      - traefik.port=${BACK_PORT-8000}
      - traefik.http.routers.qmapi.rule=(Host(`11.11.11.11`) && PathPrefix(`/api`))
      - traefik.http.routers.qmapi.entrypoints=http

Expose does nothing but indicate the port, it’s almost useless. To open the port for listening, you would need ports:.

If you want to run it through Traefik, you need to open the port on the Traefik container and declare an additional entrypoint:

      - --entrypoints.port5050.address=:5050

Thanks for that! It really helped!

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.