Dockerfile: Replacing file contents with sed replaces value with nothing

Viewed 627

I have this Dockerfile:

FROM mongo:4.4.6

# setup the environment variables
ARG MONGO_INITDB_ROOT_USERNAME
ARG MONGO_INITDB_ROOT_PASSWORD
ARG MONGO_INITDB_DATABASE

# copy the initalisation file to the mongo db entrypoint sothat it gets excecuted on startup
COPY /mongo-init/init.js /docker-entrypoint-initdb.d/

RUN sed -i "s|database_user|${MONGO_INITDB_ROOT_USERNAME}|g" /docker-entrypoint-initdb.d/init.js
RUN sed -i "s/database_password/${MONGO_INITDB_ROOT_PASSWORD}/g" /docker-entrypoint-initdb.d/init.js
RUN sed -i "s;database_db;${MONGO_INITDB_DATABASE};g" /docker-entrypoint-initdb.d/init.js

CMD cat /docker-entrypoint-initdb.d/init.js && echo ${MONGO_INITDB_DATABASE}

EXPOSE 27017

And this file that gets copied into the container:

db.createUser({
  user: "database_user",
  pwd: "database_password",
  roles: [
    {
      role: "readWrite",
      db: "database_db",
    },
  ],
});

db.createCollection("checklists");
db.createCollection("user");

When the container gets created with docker compose, the files contents are like this:

db.createUser({
  user: "",
  pwd: "",
  roles: [
    {
      role: "readWrite",
      db: "",
    },
  ],
});

db.createCollection("checklists");
db.createCollection("user");

Is there anything I'm missing that puts literally nothing into the file? I already made sure, that when hardcoding the value instead of ${MONGO_INITDB_ROOT_USERNAME} the values gets correctly inserted.

Edit:

docker-compose file:

version: "3.7"
services:
 database:
  image: "jupiter/database"
  container_name: "jupiter-stack-database"
  build:
   context: ./database
   dockerfile: ./Dockerfile
  ports:
   - "7040:27017"
  environment:
   MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
   MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
   MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}

backend:
 image: "jupiter/backend"
 container_name: "jupiter-stack-backend"
 build:
   context: ./backend
   dockerfile: ./Dockerfile
 ports:
   - "7020:3000"
 depends_on:
   - database
 environment:
   WAIT_HOSTS: database:27017
   DB_HOST: database
   DB_PORT: 27017
   DB_DB: ${MONGO_INITDB_DATABASE}
   DB_USER: ${MONGO_INITDB_ROOT_USERNAME}
   DB_PASS: ${MONGO_INITDB_ROOT_PASSWORD}

The variables are taken from the .env file in the same directory. The same values are used in the backend and contain the correct values.

Running RUN sed -i "s|database_user|without variable|g" /docker-entrypoint-initdb.d/init.js and RUN echo "${MONGO_INITDB_ROOT_USERNAME}" > /tmp/my_var.txt results in the init.js file containing without variable in the correct place (instead of database_user) and the output to my_var.txt remains empty.

1 Answers

When Compose runs an image, it runs in two stages. First it builds the image if required; this only uses the settings in the build: block, but not any of the others. Then it runs the built image with the remaining settings. In this sequence, that means first the image is built with the configuration files, but the environment: settings are only considered afterwards.

Since details like the database credentials are runtime settings, you probably don't want to rebuild the image when they change. That means you need to write a script to rewrite the file when the container starts, then run the main container CMD.

#!/bin/sh

# Rewrite the config file
sed -i.bak \
    -e "s|database_user|${MONGO_INITDB_ROOT_USERNAME}|g" \
    -e "s/database_password/${MONGO_INITDB_ROOT_PASSWORD}/g" \
    -e "s;database_db;${MONGO_INITDB_DATABASE};g" \
    /docker-entrypoint-initdb.d/init.js

# Run the main container CMD (but see below)
exec "$@"

In your Dockerfile, you would typically make this script be the ENTRYPOINT; leave the CMD as it is. You don't need to mention any of the environment variables.

However, there's one further complication. Since you're extending a Docker Hub image, it comes with its own ENTRYPOINT and CMD. Setting ENTRYPOINT resets CMD. Look up the image's page on Docker Hub, click "the full list of tags" link, then click the link for your specific tag; that takes you to the image's Dockerfile which ends with

# from the standard mongo:4.4.6 Dockerfile
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mongod"]

That means your entrypoint script needs to re-run the original entrypoint script

# (instead of "run the main container CMD")
# Run the original image's ENTRYPOINT
exec docker-entrypoint.sh "$@"

In your Dockerfile, you need to COPY your custom entrypoint script in, and repeat the original image's CMD.

FROM mongo:4.4.6

COPY /mongo-init/init.js /docker-entrypoint-initdb.d/
COPY custom-entrypoint.sh /usr/local/bin

ENTRYPOINT ["custom-entrypoint.sh"] # must be JSON-array form
CMD ["mongod"]

You don't need to change the docker-compose.yml file at all. You can double-check that this works by launching a temporary container; the override command replaces CMD but not ENTRYPOINT, so it it runs after the setup in the entrypoint script.

docker-compose run --rm database \
  cat /docker-entrypoint-initdb.d/init.js
Related