First Traefik config PART 2 - Domain name needs at least one dot

I can not get a subdomain working looks like my config might work but lets encrypt seems to be receiving the yaml.

LE Errors:

level=error msg="Unable to obtain ACME certificate for domains \"portainer.\": unable to generate a certificate for the domains [portainer]: acme: error: 400 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order :: urn:ietf:params:acme:error:rejectedIdentifier :: Error creating new order :: Cannot issue for \"portainer\": Domain name needs at least one dot, url: " providerName=letsencrypt.acme routerName=portainer@docker rule="Host(`portainer.`)"
level=error msg="Unable to obtain ACME certificate for domains \"portainer.\": unable to generate a certificate for the domains [portainer]: acme: error: 400 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order :: urn:ietf:params:acme:error:rejectedIdentifier :: Error creating new order :: Cannot issue for \"portainer\": Domain name needs at least one dot, url: " rule="Host(`portainer.`)" providerName=letsencrypt.acme routerName=portainer@docker
level=error msg="Unable to obtain ACME certificate for domains \"portainer.\": unable to generate a certificate for the domains [portainer]: acme: error: 400 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order :: urn:ietf:params:acme:error:rejectedIdentifier :: Error creating new order :: Cannot issue for \"portainer\": Domain name needs at least one dot, url: " providerName=letsencrypt.acme routerName=portainer@docker rule="Host(`portainer.`)"

Traefik yml:

version: '3.7'
services:
  traefik:
    image: traefik:chevrotin
    container_name: traefik
    ports:
      - 80:80
      - 443:443
    command:
        - --api=true
        - --api.debug=true
        - --providers.docker=true
        - --providers.docker.network=reverse_proxy
        - --providers.docker.exposedbydefault=false
        - --entrypoints.web.address=:80
        - --entrypoints.websecure.address=:443
        - --certificatesresolvers.letsencrypt.acme.email=me@example.com
        - --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json
        - --certificatesresolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare
        - --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
        #- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
    labels:
        - traefik.enable=true
        - traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN_NAME}`)
        - traefik.http.routers.traefik.entrypoints=websecure
        - traefik.http.routers.traefik.service=api@internal
        - traefik.http.routers.traefik.middlewares=admin
        - traefik.http.routers.traefik.tls.certresolver=letsencrypt
        - traefik.http.routers.traefik.tls.domains[0].main=example.com
        - traefik.http.routers.traefik.tls.domains[0].sans=*.example.com

        - traefik.http.middlewares.custom.headers.browserXSSFilter=true
        - traefik.http.middlewares.custom.headers.contentTypeNosniff=true
        - traefik.http.middlewares.custom.headers.forceSTSHeader=true
        - traefik.http.middlewares.custom.headers.frameDeny=true
        - traefik.http.middlewares.custom.headers.sslredirect=true
        - traefik.http.middlewares.custom.headers.stsIncludeSubdomains=true
        - traefik.http.middlewares.custom.headers.stsPreload=true
        - traefik.http.middlewares.custom.headers.stsSeconds=315360000
        - traefik.http.middlewares.admin.basicauth.usersfile=/etc/traefik/config/usersfile
        
    networks:
      - reverse_proxy
    restart: unless-stopped
    volumes:
      - ./config:/etc/traefik/config:ro
      - ./letsencrypt:/etc/traefik/acme:rw
      - ./log:/etc/traefik/log:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /etc/localtime:/etc/localtime:ro
    secrets:
      - cf_key
    environment:
      - CF_API_KEY=/run/secrets/cf_key
      - CF_API_EMAIL=me@example.com
      - DOMAIN_NAME=example.com

secrets:
    cf_key:
        external: true
networks:
    default:
        driver: bridge
    reverse_proxy:
        driver: overlay

Portainer yml (using as a test service):

version: "3"

services:
  portainer:
    container_name: portainer
    environment:
      - DOMAIN_NAME='example.com'
      - TZ='US'
    image: portainer/portainer:1.23.2
    labels:
      - traefik.enable=true
      - traefik.http.middlewares.custom.headers.browserXSSFilter=true
      - traefik.http.middlewares.custom.headers.contentTypeNosniff=true
      - traefik.http.middlewares.custom.headers.forceSTSHeader=true
      - traefik.http.middlewares.custom.headers.frameDeny=true
      - traefik.http.middlewares.custom.headers.sslredirect=true
      - traefik.http.middlewares.custom.headers.stsIncludeSubdomains=true
      - traefik.http.middlewares.custom.headers.stsPreload=true
      - traefik.http.middlewares.custom.headers.stsSeconds=315360000
      - traefik.http.routers.portainer.entrypoints=websecure
      - traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN_NAME}`)
      - traefik.http.routers.portainer.tls.certresolver=letsencrypt
    networks:
      - traefik_reverse_proxy
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      #- ./data:/data
      - /path/to/data

volumes:
  data:
    
networks:
  traefik_reverse_proxy:
    external: true

acme.json

{
  "letsencrypt": {
    "Account": {
      "Email": "me@example.com",
      "Registration": {
        "body": {
          "status": "valid",
          "contact": [
            "mailto:me@example.com"
          ]
        },
        "uri": "https://acme-v02.api.letsencrypt.org/acme/acct/85460202"
      },
      "PrivateKey": "VALID KEY",
      "KeyType": "4096"
    },
    "Certificates": [
      {
        "domain": {
          "main": "example.com",
          "sans": [
            "*.example.com"
          ]
        },
        "certificate": "VALID CERT",
        "key": "VALID KEY",
        "Store": "default"
      }
    ]
  }
}

Your ${DOMAIN} is not set when you are deploying. If you inspect a service you will see that.

You have to set it in the shell executing docker stack deploy. You can prevent this from running without being set by changing it to ${DOMAIN:?}.

The environment in your yaml is setting environment inside the service container.

1 Like

That's right! If you expect DOMAIN_NAME from here:

environment: 
  - DOMAIN_NAME='example.com'

To be used here:

labels:
      - traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN_NAME}`)

Then it's not gonna happen. Read docker-compose documentation on what the environment block is and how environment variables substitution works in docker-compose.

Thank you for clarifying and pointing me in the right direction.
I just tried:

labels:
      - traefik.http.routers.portainer.rule=Host(`portainer.example.com`)

But I still can not connect to the service that way.

Trying to find another app that uses regular ports like this:

docker create \
  --name=nextcloud \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=US \
  --label traefik.enable=true \
  --label traefik.http.middlewares.custom.headers.browserXSSFilter=true \
  --label traefik.http.middlewares.custom.headers.contentTypeNosniff=true \
  --label traefik.http.middlewares.custom.headers.forceSTSHeader=true \
  --label traefik.http.middlewares.custom.headers.frameDeny=true \
  --label traefik.http.middlewares.custom.headers.sslredirect=true \
  --label traefik.http.middlewares.custom.headers.stsIncludeSubdomains=true \
  --label traefik.http.middlewares.custom.headers.stsPreload=true \
  --label traefik.http.middlewares.custom.headers.stsSeconds=315360000 \
  --label traefik.http.routers.nxtcloud.entrypoints=websecure \
  --label traefik.http.routers.nxtcloud.rule="Host(`nxtcloud.example.com`)" \
  --label traefik.http.routers.nxtcloud.tls.certresolver=letsencrypt \
  -v ./nxt_config \
  -v ./nxt_data \
  --restart unless-stopped \
  linuxserver/nextcloud

Still not working from what I understand. What might I do to make it work?