I’ve recently moved a bunch of containers off my Raspberry Pi to an HP EliteDesk. For extra challenge points, I also decided to drop the venerable combination of Portainer and Nginx Proxy Manager for something else. Principally, all I care about is being able to easily self host various web apps on my home server, make them accessible to the outside world, and in some cases gate them behind a consistent login page - so here is what I did!

I’ve chosen Dockge for container management, caddy-docker-proxy to forward requests from my router to the respective container, and nforwardauth to provide the login gate.

In my case, Dockge was deployed using Docker into an LXC host running Debian. Once running, the remaining containers were created directly via the Dockge UI.

Firstly, we have caddy-docker-proxy, which spins up Caddy and sets up forwarding rules based on container service labels. I expose ports 80 and 443 here, which receive HTTP(S) requests forwarded from my home router:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: "3.7"
services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    ports:
      - 80:80
      - 443:443
    environment:
      - CADDY_INGRESS_NETWORKS=caddy
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
    labels:
      # set email address for LetsEncrypt certificates
      caddy.email: my.email@example.com
    restart: unless-stopped
networks:
  caddy:
    external: true
volumes:
  caddy_data: {}

Secondly, the nforwardauth instance, which handles authenticating users. It’s important to set the domain names correctly, and even more important to use a good, long and secure token secret string.

It’s also necessary to generate a passwd file specifying the permitted usernames and passwords. In my case, I connected to my Dockge host with SSH and used mkpasswd to populate /opt/dockge/stacks/nforwardauth/passwd.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  nforwardauth:
    image: nosduco/nforwardauth:v1
    labels:
      caddy: auth.example.com # use your own domain
      caddy.reverse_proxy: "{{upstreams 3000}}"
    environment:
      - TOKEN_SECRET=r4nD0Mv41u3 # generate a random string for this!
      - COOKIE_SECURE=true
      - AUTH_HOST=auth.example.com # should match the domain above
      - PORT=3000
    volumes:
      - ./passwd:/passwd:ro
    networks:
      - caddy
      - default
    ports:
      - 3000:3000
networks:
  default: null
  caddy:
    external: true

Finally, an example demonstrating how to bind it all together. Note the caddy labels, which are used to set up the subdomain forwarding and require user authentication:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
version: "3.7"
services:
  whoami:
    image: traefik/whoami
    networks:
      - caddy
    labels:
      caddy: whoami.example.com
      caddy.reverse_proxy: "{{upstreams 80}}"
      caddy.forward_auth: nforwardauth:3000
      caddy.forward_auth.uri: /
      caddy.forward_auth.header_up: Host {upstream_hostport}
networks:
  caddy:
    external: true

With this all set up and running, accessing whoami.example.com will redirect you to auth.example.com and require you to use a username/password combination from the passwd file configured earlier. The really neat thing is that you only need to do this once - you won’t be re-prompted for any additional services you create.

Additionally, the username of the ‘current’ user is provided within a signed cookie value, so a service can theoretically use these data to bypass their own login system. (Just make sure to verify it first!)

I’ve been using this setup for a few weeks now, and it’s been working great so far.