It turns out, it's not difficult to remove all passwords from our Docker Compose files
Tags: docker, security, devops, developer
Published: 2024-05-30 15:31:31

I used to hardcode my password in my demos and code samples. I know it's not a good practice, but it's just for demo purposes, it cannot be that dramatic, right? I know there are proper ways to manage sensitive information, but this is only temporary! And it must be complicated to remove all the passwords from a deployment... It turns out, IT IS NOT difficult at all, and that will prevent serious threats.

![a secured container](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzu98wqbs9scp44r8j2z.png)

In this post, I will share how to remove all passwords from a docker-compose file using environment variables. It's quick to setup and easy to remember. For production deployment, it's better to use [secrets](https://docs.docker.com/compose/use-secrets/), because environment variables will be visible in logs. That said, for demos and debugging and testing, it's nice to see those values. The code will be available on [GitHub](https://github.com/FBoucher/startrek-demo). This deployment was used for my talks during Azure Developers .NET Days: [Auto-Generate and Host Data API Builder on Azure Static Web Apps](https://www.youtube.com/watch?v=GO2R7IW6s3k&list=PLI7iePan8aH4cuFgP9YbRODrSEwXNA8Yq&index=13) and [The most minimal API code of all... none](https://www.youtube.com/watch?v=A1H1kVPHs3w&list=PLI7iePan8aH4cuFgP9YbRODrSEwXNA8Yq&index=15)


## The Before Picture

For this deployment, I used a docker-compose file to deploy an SQL Server in a first container and Data API Builder (DAB) in a second one. When the database container starts, I run a script to create the database tables and populate them.

```dockerfile
services:

  dab:
    image: "mcr.microsoft.com/azure-databases/data-api-builder:latest"
    container_name: trekapi
    restart: on-failure
    volumes:
      - "./startrek.json:/App/dab-config.json"
    ports:
      - "5000:5000"
    depends_on:
      - sqlDatabase

  sqlDatabase:
    image: mcr.microsoft.com/mssql/server
    container_name: trekdb
    hostname: sqltrek
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "1rootP@ssword"
    ports:
      - "1433:1433"
    volumes:
      - ./startrek.sql:/startrek.sql
    entrypoint:
      - /bin/bash
      - -c
      - |
        /opt/mssql/bin/sqlservr & sleep 30
        /opt/mssql-tools/bin/sqlcmd -U sa -P "1rootP@ssword" -d master -i /startrek.sql
        sleep infinity

```

As we can see, the password is in clear text twice, in the configuration of the database container and in the parameter for *sqlcmd* when populating the database. Same thing for the DAB configuration file. Here the *data-source* node where the password is in clear text in the connection string.

```json
"data-source": {
 	"database-type": "mssql",
	"connection-string": "Server=localhost;Database=trek;User ID=sa;Password=myPassword!;",
	"options": {
		"set-session-context": false
	}
}
```

## First Pass: Environment Variables

The easiest password instance to remove was in the *sqlcmd* command. When defining the container, an environment variable was used... Why not use it! To refer to an environment variable in a docker-compose file, you use the syntax `$$VAR_NAME`.  I used the name of the environment variable `MSSQL_SA_PASSWORD` to replace the hardcoded password.

```dockerfile
/opt/mssql-tools/bin/sqlcmd -U sa -P $$MSSQL_SA_PASSWORD -d master -i /startrek.sql
```

## Second Pass: .env File

That's great but the value is still hardcoded when we assign the environment variable. Here comes the environment file. They are text files that holds the values in key-value paired style. The file is not committed to the repository, and it's used to store sensitive information. The file is read by the docker-compose and the values are injected. Here is the final docker-compose file:

```dockerfile
services:

  dab:
    image: "mcr.microsoft.com/azure-databases/data-api-builder:latest"
    container_name: trekapi
    restart: on-failure
    env_file:
      - .env
    environment:
      MY_CONN_STRING: "Server=host.docker.internal;Initial Catalog=trek;User ID=sa;Password=${SA_PWD};TrustServerCertificate=True"
    volumes:
      - "./startrek.json:/App/dab-config.json"
    ports:
      - "5000:5000"
    depends_on:
      - sqlDatabase

  sqlDatabase:
    image: mcr.microsoft.com/mssql/server
    container_name: trekdb
    hostname: sqltrek
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: ${SA_PWD}
    env_file:
      - .env
    ports:
      - "1433:1433"
    volumes:
      - ./startrek.sql:/startrek.sql
    entrypoint:
      - /bin/bash
      - -c
      - |
        /opt/mssql/bin/sqlservr & sleep 30
        /opt/mssql-tools/bin/sqlcmd -U sa -P $$MSSQL_SA_PASSWORD -d master -i /startrek.sql
        sleep infinity
```

Note the `env_file` directive in the services definition. The file `.env` is the name of the file used. The `${SA_PWD}` tells docker compose to look for `SA_PWD` in the `.env` file. Here is what the file looks like:

```text
SA_PWD=This!s@very$trongP@ssw0rd
```

## Conclusion

Simple and quick. There are no reasons to still have the password in clear text in the docker compose files anymore. Even for a quick demo! Of course for a production deployment there are stronger ways to manage sensitive information, but for a demo it's perfect and it's secure.

During Microsoft Build Keynote on day 2, [Julia Liuson](https://www.linkedin.com/in/julia-liuson-6703441/) and [John Lambert](https://www.linkedin.com/in/johnjlambert/) talked about how trade actors are not only looking for the big fishes, but also looking at simple demos and old pieces of code, looking for passwords, keys and sensitive information. (it's at [1:20:00](https://youtu.be/2X698yueu7I?t=1047))

{% youtube https://youtu.be/2X698yueu7I?t=1047 %}