Using Caddyfile's basic_auth with environment variables and Docker

Here is a quick guide how to set HTTP basic auth username and password using environment variables.

  • Hardcoding password, even hashed one, in Caddyfile, or any file, is a dangerous practice and should be avoided

  • HTTP Basic Auth is an easy way to protect your development servers, other infrastructure, against scrapers, knocking, etc. even if the content behind the password does not need security otherwise

  • We show how to generate a secrets file from the command line

  • We also show how to use with Docker environment fails and their pitfalls

Caddyfile supports expanding of environment variables. Syntax is somewhat unique, so pay attention on it.

We are going to store our username and password in a file called secrets.env. This file should not be part of any source code control and only reside on the server. You should add the file to your .gitignore if you are using git.

We use Caddy’s docker version to the command to generate the hashed password. This environment file is Docker format as all tools have their own unique environment file format. Note that it is important to wrap variable values to ‘’ as otherwise Docker attempts to expand dollar sign.

Replace “myuser” / “mypassword” with your own username and password combination:


echo "HTTP_BASIC_AUTH_USER='myuser'" >> secrets.env

HASHED_PASSWORD=$(docker run caddy caddy hash-password --plaintext "mypassword")

echo "HTTP_BASIC_AUTH_PASSWORD='$HASHED_PASSWORD'" >> secrets.env

Now you can view the file:

cat secrets.env
HTTP_BASIC_AUTH_USER='myuser'
HTTP_BASIC_AUTH_PASSWORD='...'

Then we create a Caddyfile that uses this configuration. We create a static file web server that runs in port 9997 and outputs index.html from a local folder. This is a good way to test that HTTP Basic Auth works, as when using file_server you eliminate other sources of problems. We also turn on various logging file that help to troubleshoot the problems.

#
# Caddy HTTP basic auth example
#
{
    email no-reply@example.com

    # Use the log file mapped to the host FS
    log {
        output file /var/log/caddy/error.log.json
        format json
    }
}


#
# Plain index.html server to test Caddy and basic auth.
# Listens to all IP addresses and any domain name at port 9997
#

http://:9997 {

    handle {
        basicauth {
            {$HTTP_BASIC_AUTH_USER} {$HTTP_BASIC_AUTH_PASSWORD}
        }           

        # Mapped in docker-compose.yml
        root * /var/static
        file_server browse
    }

    # Set the default error page
    # https://caddyserver.com/docs/caddyfile/directives/handle_errors
    handle_errors {
        respond "{http.error.status_code} {http.error.status_text}"
    }    

    log {
        output file /var/log/caddy/access-test.log.json
        format json
    }      
}

Then we create docker-compose.yml that will read secrets.env and pass it to Caddy.

version: "3"
services:
  caddy:
    container_name: caddy
    image: caddy:2.6.4-alpine   
    restart: unless-stopped    
    # Contains BASIC_AUTH_USER and  BASIC_AUTH_PASSWORD
    env_file:
      - secrets.env
    # We directly bind to the host ports and there is no Docker network here
    network_mode: host
    volumes:
      - $PWD/Caddyfile:/etc/caddy/Caddyfile
      - $PWD/logs:/var/log/caddy # shared volume with caddy
      - $PWD/static:/var/static # shared volume with caddy

Let’s also create host-mapped folders and content:

mkdir logs  # Logs go here
mkdir static
echo "Hello world" > static/index.html

Now you can run this with docker-compose:

docker-compose up caddy

Visit localhost:9997 and you get the web browser prompt for the password:

1 Like

I tried this and keep getting the following error when trying to login:

crypto/bcrypt: hashedSecret too short to be a bcrypted password

This does not happen when I put the username and hashed password directly into the Caddyfile, but does when I use them via the environment variables.

I do not have network_mode: host set, because I need caddy to be in a network for my other containers. Is this a problem?

Edit:

I fixed it by replacing all $ in my hashed password with $$, because Docker tries to expand them.

2 Likes