Search code examples
dockerdocker-compose

Are environment variables in containers created with Docker-compose files stored on the volume?


I have a very simple docker-compose file like the one below.

version: '3.8'
services:
  mysql:
    image: mysql:latest
    container_name: mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: yes
      MYSQL_ROOT_PASSWORD: 'hello'
      MYSQL_USER: myuser
      MYSQL_PASSWORD:  'hello'
    ports:
      - 3306:3306
    volumes:
      - mysql-data:/var/lib/mysql
      # - new-mysql-data:/var/lib/mysql
      
volumes:
  mysql-data:
  new-mysql-data:

As you can see in the file, it is set to MYSQL_ROOT_PASSWORD: 'hello'. I'm mapping this file to the mysql-data volume. After creating mysql with this file, changing the MYSQL_ROOT_PASSWORD Changing it to password does not take effect.

I did some research and found an article on the official mysql image on docker hub.

Environment Variables When you start the mysql image, you can adjust the configuration of the MySQL instance by passing one or more environment variables on the docker run command line. Do note that none of the variables below will have any effect if you start the container with a data directory that already contains a database: any pre-existing database will always be left untouched on container startup.

This seems to imply that once a container has been attached to a volume and created, changes to environment variables will not be applied.

So I ran a test where I mapped a new volume and let it run.

version: '3.8'
services:
  mysql:
    image: mysql:latest
    container_name: mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: yes
      MYSQL_ROOT_PASSWORD: 'password'  # changed.
      MYSQL_USER: myuser
      MYSQL_PASSWORD:  'password' # changed.
    ports:
      - 3306:3306
    volumes: 
      # - mysql-data:/var/lib/mysql
      - new-mysql-data:/var/lib/mysql # changed.
      
volumes:
  mysql-data:
  new-mysql-data:

The result is that MYSQL_ROOT_PASSWORD: 'password' was applied correctly. in this situation, if I change volume from the new-mysql-data to the mysql-data, the MYSQL_ROOT_PASSWORD that applies then will be the hello that we registered before this.

The conclusion I came to was this: mysql's environment variables are stored on a volume.

Am I correct in my conclusion? If environment variables are stored in a volume, there may come a situation where you have to remove the volume to change the environment variables. I don't think this is efficient, and I'm confused if I'm understanding this correctly.

Are mysql's environment variables stored on a volume?

Thank you.


Solution

  • At a technical level, no. Volumes only store files, and environment variables are in-memory-only properties of processes.

    The other important technical detail is that MySQL-the-database-server doesn't take settings like credentials from environment variables either. The actual database password is in the opaque database storage, and you need to use administrative tools like mysqladmin or run an SQL ALTER USER statement to set or change it. This can be done from outside Docker space by any user who can connect to the database and has the right privileges; even without restarting the database, the database credentials will not necessarily match what's in the environment variables.

    So: the database credentials are stored in the on-disk database data. That data is in the volume.

    The environment variables are used by a Docker-specific initialization script that only runs the first time the database is created. The script just checks to see if the database files exist, and if not, it runs its first-time initialization scripts. These include some SQL CREATE statements derived from environment variables and the /docker-entrypoint-initdb.d directory.

    You could simulate this (without Docker) using a shell script. The actual value you're checking for comes from a file, and the file is initialized from environment variables, but only if it doesn't exist yet.

    #!/bin/sh
    
    # Create the settings file if it doesn't exist
    if [ ! -f settings.txt ]; then
      if [ "$SETTING" ]; then echo "SETTING=$SETTING" >> settings.txt
    fi
    
    # Getting the actual setting from the file and not the environment variable
    grep SETTING settings.txt
    

    If you run this once SETTING=foo ./get-setting it will print out foo as you expect, but changing the environment variable won't change the output, only editing the on-disk file. In Docker, that persistent file would need to be stored in a volume.

    ... a situation where you have to remove the volume to change the environment variables

    If you want to change a database user's password, use an SQL ALTER USER statement, the same way you would with any other database, Docker or otherwise. You don't need to delete anything in particular.

    These same "only the first time" rules apply to the /docker-entrypoint-initdb.d initialization scripts. If you need to, say, create a database table, again, you need to use an SQL CREATE TABLE statement. You should generally do this via your application's database-migration framework, which won't be tied to Docker and can be re-run without destroying the database.

    Yes, it is possible to change the database user's password by destroying the database and re-running its initialization script, but this loses absolutely all of the data in the database. You'll see recommendations to do this out there, and with that caveat it does in fact work. But it's not necessary, and you can use normal database tools to do normal database administration without doing anything Docker-specific.