Search code examples
dockerdocker-composedocker-secrets

Secrets in Compose: Environment variable has file path as value instead of its content


I'm trying to use Secrets with Docker Compose

Here is my Docker Compose (only the relevant parts):

version: "3.8"
services:
  My-Service:
    build:
      context: ./..
      dockerfile: ./docker/Dockerfile
    image: my-app/api
    container_name: my-app-api
    environment:
      ASPNETCORE_ENVIRONMENT: Docker
      ASPNETCORE_URLS: http://+:8080
      Swagger__Authentication__ClientSecret_FILE: /run/secrets/my_secret
    secrets:
      - my_secret
    ports:
      - "50002:8080"

secrets:
  my_secret:
    file: ./my_secret.txt

I have a my_secret.txt file containing a secret.

Then I create the image and run it

docker compose -f ./docker/compose.yaml -p my-app up -d --build

Inside the container, checking the /run/secrets/my_secret file is here and has the correct value

sh-5.1$ cat /run/secrets/my_secret
My super secret

The only problem is that my Swagger__Authentication__ClientSecret_FILE environment variable has the value of my file path instead of its content

sh-5.1$ echo $Swagger__Authentication__ClientSecret_FILE
/run/secrets/my_secret

So what am I doing wrong here? I think I'm doing exactly like the Docker documentation says, yet it doesn't work as intended. I honestly can't wrap my head around this.

Thanks for your answer(s).


Solution

  • Ok I finally got something working even if it's not as clean as I'd like.

    My mistake was assuming that my environment variable Swagger__Authentication__ClientSecret_FILE would "magically" have its value set to the content of my /run/secrets/my_secret file but there's no such kind of automatic magic, it has to be manually set.

    Here is my new Docker Compose file

    version: "3.8"
    services:
      My-Service:
        build:
          context: ./..
          dockerfile: ./docker/Dockerfile
        image: my-app/api
        container_name: my-app-api
        environment:
          ASPNETCORE_ENVIRONMENT: Docker
          ASPNETCORE_URLS: http://+:8080
          Swagger__Authentication__ClientSecret_FILE: /run/secrets/my_secret
        secrets:
          - my_secret
        ports:
          - "50002:8080"
        entrypoint: ./compose-entrypoint.sh
        volumes:
          # $FINAL_WORKDIR is an environment variable set in the .env file with the path of the final WORKDIR set in the Dockerfile before executing the ENTRYPOINT
          - "./compose-entrypoint.sh:$FINAL_WORKDIR/compose-entrypoint.sh:ro"
    
    secrets:
      my_secret:
        file: ./my_secret.txt
    

    The changes are:

    • It now overrides the ENTRYPOINT directive from the Dockerfile and execute a compose-entrypoint.sh file instead
    • It mounts the compose-entrypoint.sh file in a volume (as read-only with :ro) so it can be executed as the entrypoint (make sure the file is executable on your host machine so it will be executable as well in your container)

    The logic to set the Swagger__Authentication__ClientSecret environement variable I need happens in this compose-entrypoint.sh file:

    #!/bin/sh
    
    export Swagger__Authentication__ClientSecret=$(cat $(echo $Swagger__Authentication__ClientSecret_FILE))
    source ./entrypoint.sh
    

    Note: I kept this file as simple as possible to better demonstrate the basis principle. However a better way is simply to copy how MySQL is doing (thanks to @xerx593 who shared the link in a comment)

    This script is:

    1. Getting the value of the Swagger__Authentication__ClientSecret_FILE environment variable which is actually a file path
    2. Read the content of this file
    3. Set a Swagger__Authentication__ClientSecret environment variable with the content read from the file
    4. Call the entrypoint.sh file that is the original file called in the Dockerfile

    At the end of my Dockerfile I just had to copy the entrypoint-sh bash file and make it executable

    COPY ./docker/entrypoint.sh ./entrypoint.sh
    RUN chmod +x ./entrypoint.sh
    
    ENTRYPOINT ["./entrypoint.sh"]
    

    Note that by default it is executing the entrypoint.sh file which is NOT setting any additional environment variable. The compose-entrypoint.sh file is executed only when using Docker Compose.

    Hope this could clarify some obscure parts.