201

I have a service that I am bringing up through Rancher via docker-compose. The issue I am running into is that I need to set a password after the container has been deployed.

The way rancher secrets work, is that I set my secret in and rancher will mount a volume on my container with a file containing my secret. I was hoping to be able to execute a script to grab that secret, and set it as a password on my config file.

I don't believe I have a way to get that secret in through the Dockerfile as I don't want the secret to be in git, so I'm left looking at doing it via docker-compose.

Does anyone know if this is possible?

3
  • Absolutely, that's a fairly normal way of setting secrets. Just add the relevant shell script as (or to) your CMD or ENTRYPOINT. Commented Dec 3, 2017 at 6:06
  • 1
    to have access to secret without expose in Dockerfile, you can use .env file with docker-compose : docs.docker.com/compose/environment-variables Commented Aug 3, 2021 at 16:33
  • 1
    Hi. This is specific problem. The way to use a separate service as a set UP service is working solution. But often images provide you some sort of the 'hooks' that can be used. Like here for kafka. You need just put a scripts to the "docker-entrypoint-initdb.d" to run some sort of set up. Commented Apr 7, 2022 at 17:42

6 Answers 6

118

This is the way I use for calling a script after a container is started without overriding the entrypoint.

In my example, I used it for initializing the replicaset of my local MongoDB

services:
  mongo:
    image: mongo:4.2.8
    hostname: mongo
    container_name: mongodb
    entrypoint: ["/usr/bin/mongod","--bind_ip_all","--replSet","rs0"]
    ports:
      - 27017:27017
  mongosetup:
    image: mongo:4.2.8
    depends_on:
      - mongo
    restart: "no"
    entrypoint: [ "bash", "-c", "sleep 10 && mongo --host mongo:27017 --eval 'rs.initiate()'"]      
  • In the first part, I simply launch my service (mongo)
  • The second service use a "bash" entry point AND a restart: no <= important

I also use a depends_on between service and setup service for manage the launch order.

Sign up to request clarification or add additional context in comments.

5 Comments

This approach seems interesting but for me raises some questions. First, why use the mongo image for the mongosetup container? Will just any image do? Or is it important that it should be the mongo one? Second, you say that setting restart: no is important, but why is that? So that setup does not get executed multiple times? Last, I see sleep 10? Is that to give mongo time to initialize? Seems tricky to use sleep for that...
@StijndeWitt 1. "Why use mongo image?" because the command mongo --host mongo:27017 --eval 'rs.initiate()' which is the 4th element of mongosetup.entrypoint property in the above docker-compose, uses mongo which is a cli program (mongosh also can be used as a newer alternative) and this program is pre-installed on mongo images.
@StijndeWitt 2. "you say that setting restart: no is important, but why is that?" because as the name for this container suggests, this container is for setting up replica set for mongo, which means just running the mongo --host mongo:27017 --eval 'rs.initiate()' either from a container that can access the mongo container using mongo:27017(both container must be on the same network, like mongosetup and mongo containers are, in this
@StijndeWitt setup, the string mongo in mongosetup container, maps to the IP of mongo container) or from the same container you can run mongo --eval 'rs.initiate()' since in this example the first approach is taken, and as explained the mongo --host mongo:27017 --eval 'rs.initiate()' must get executed at least once(preferably only once). hence restart: "no" is set, to let the container shut down after it has run the command successfully.
@StijndeWitt 3. "I see sleep 10? Is that to give mongo time to initialize?" yes, it waits for 10 seconds before running the command, because the mongo daemon in the mongo container, must be up and running in order for the command to run successfully
77

The trick is to overwrite the compose COMMAND to perform whatever init action you need before calling the original command.

  1. Add a script in your image that will perform the init work that you want like set password, change internal config files, etc. Let's call it init.sh. You add it to your image.

Dockerfile:

FROM: sourceimage:tag
COPY init.sh /usr/local/bin/
ENTRYPOINT []

The above overrides whatever ENTRYPOINT is defined in the sourceimage. That's to make this example simpler. Make sure you understand what the ENTRYPOINT is doing in the Dockerfile from the sourceimage and call it in the command: of the docker-compose.yml file.

docker-compose.yml:

services:
  myservice:
    image: something:tag
    ...
    command: sh -c "/usr/local/bin/init.sh && exec myexecutable"

It's important to use exec before calling the main command. That will install the command as the first process (PID1) which will make it receive signals like STOP, KILL (Ctrl-C on keyboard) or HUP.

10 Comments

executing this causes /usr/local/bin/docker-entrypoint.sh: line 172: /usr/local/bin/init.sh: No such file or directory
Additionally, removing the first part of the command causes the error exec: not found
@BurhanAli I've updated the answer to explicitly call the "shell -c". Also, there are many different combinations of ENTRYPOINT/CMD (Dockerfile) and entrypoint:/command:(docker compose) which can override each other. To keep this answer concise, I reset the ENTRYPOINT so that it doesn't override command.
@MurtazaHaji. In your case the line would be command: sh -c "/usr/local/bin/init.sh && exec redid-server --deamonize yes
@aderchox That's correct, command will override the CMD instruction in the image. However, the final startup action is obtained by combining ENTRYPOINT and CMD. So if ENTRYPOINT is for instance ["echo"], you can set CMD to "hello"and this will print "hello". I believe this was done historically to allow different arguments to be specified easily. ENTRYPOINT contains the main executable and CMD contains the arguments passed to the executable. You can also set ENTRYPOINT to an empty array so that CMD or command contains the full startup action line, like we do above.
|
34

You can also use volumes to do this:

services:
  example:
    image: <whatever>
    volume: ./init.sh:/init.sh
    entrypoint: sh -c "/init.sh"

Note that this will mount init.sh to the container, not copy it (if that matters, usually it doesn't). Basically processes within the container can modify init.sh and it would modify the file as it exists in your actual computer.

5 Comments

Got it! I needed entrypoint: sh -c "sh init.sh" for it to work properly
interesting, looks like you mounted it to ./init.sh instead of /init.sh? seeing sh twice feels icky 😅
using entrypoint: sh -c "sh /init.sh" resolve the issue sh: /init.sh: Permission denied
how to solve "Permission denied" problem?
you will need to be more specific, but my guess is that the docker process does not have access to init.sh on your local computer, so you get this error. either copy the init.sh as suggested by stackoverflow.com/a/47629959/4021308 or move init.sh to somewhere docker would be allowed to access. i would NOT recommend running docker in --privileged mode (stackoverflow.com/a/35620590/4021308)
18

Since Compose 2.30 you may use the post_start lifecycle hook.

For your specific usecase, the docker-compose.yml could look like this:

version: '3.8'

services:
  my_service:
    image: my_image
    volumes:
      - /run/secrets:/run/secrets # Mount Rancher secrets
    post_start:
      - command: |
          PASSWORD=$(cat /run/secrets/my_secret_file) &&
          sed -i "s/placeholder_password/$PASSWORD/" /path/to/config.file

1 Comment

Note that "The hook execution timing is not assured during the execution of the container's entrypoint." from docs.docker.com/compose/how-tos/lifecycle/#post-start-hooks
2

docker-compose specify how to launch containers, not how to modify an existing running container.

The Rancher documentation mentions that, for default usage of secrets, you can reference the secret by name in the secrets array in the docker-compose.yml.

The target filename will be the same name as the name of the secret.
By default, the target filename will be created as User ID and Group ID 0, and File Mode of 0444.
Setting external to true in the secrets part will make sure it knows the secret has already been created.

Example of a basic docker-compose.yml:

version: '2'
services:
  web:
    image: sdelements/lets-chat
    stdin_open: true
    secrets:
    - name-of-secret
    labels:
      io.rancher.container.pull_image: always
secrets:
  name-of-secret:
    external: true

As illustrated in "How to Update a Single Running docker-compose Container", updating a container would involve a "build, kill, and up" sequence.

docker-compose up -d --no-deps --build <service_name>

Comments

0

Can we execute a script as root during docker container startup?

COPY <<'DASH' /etc/rc.local
    set -x
    printenv
DASH

ENTRYPOINT ["dash", "-xc", ". /etc/rc.local && exec <the original entrypoint> \"$@\"", "$@"]
CMD [<the original cmd in exec form>]

Explain:

  1. The file /etc/rc.local is a historical filename for putting scripts that will be executed by pre-systemd-era-daemon SysV init during the system boot.

    Another similar path for this purpose is /etc/init.d/*.

    Here we just take this filename for convention as in docker container there's no init/systemd daemon by default and the ENTRYPOINT is the pid 1.

  2. The original value of image ENTRYPOINT can be found in its Dockerfile or get overrided by compose.yaml.

  3. And setting a new ENTRYPOINT will reset the original CMD to empty string:

    If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value.

    so we have to copy the value of CMD from the Dockerfile of original image or compose.yaml if get overrided in it.

  4. sh -xc 'echo "$@"' 1 2 3 is a way to pass shell arguments into sh -c, and this example shall run echo 1 2 3 that can be verified by set -x.

  5. dash is yet another implement of shell that's faster than bash and being used as the default /bin/sh in Debian.

    If you use some bashism features that in /etc/rc.local, feel free to replace it with bash or other shell implements.

  6. $@ is the value of all shell arguments except the first one like $argv[0] or $0 which is the value being passed to execv.

    In the shell env of ENTRYPOINT when a container is created, its $@ will be the value of Dockerfile CMD, so we could pass the value CMD from outer shell into the inner that created by sh -c 'echo "$@"' $@.

  7. The value of ENTRYPOINT and CMD in Dockerfile or compose.yaml must be written in exec form for removing the extra ["sh", "-c"] being prepend to the value when using shell form.

    • Using
      docker image inspect <compose project name>-<compose service name> \
      | jq '.[0].Config | with_entries(select([.key] | inside(["Cmd", "Entrypoint"])))'
      
      to view the value of ENTRYPOINT or CMD of a built image for compose service that can be found in docker images -a.
    • Whereas the <compose project name> should be the value of $COMPOSE_PROJECT_NAME that defaults the dirname(1) of the path of compose.yaml.
    • The jq expression just like _.pick() in lodash.
  8. Double-quoting $@ as "$@" will prevent shell IFS= word splitting for passing the original Dockerfile CMD as is into $1 of sh -c.

    This can be verified by for word in "$@"; do echo "$word"; done in /etc/rc.local "$@" and can fix some issues like nginx: invalid option: "off" with the offical docker image nginx.

  9. Prepend exec(1p) before "$@" will replace the ENTRYPOINT process dash with the first one in $@ array.

    This is a common pattern with docker entrypoint to allow passing UNIX signal to the proper process as only the topmost init process, that either to be the ENTRYPOINT or be replaced by exec in ENTRYPOINT, can recive UNIX signal from docker daemon. Or you will have to write a signal processor with trap in the entrypoint shell or with signal() in the entrypoint process.

    Most ENTRYPOINT shell in well-formed Dockerfile has already an exec "$@" in the end so the pid 1 will be replaced by twice, and this exec can still act as a safety net.

  10. The dot . before /etc/rc.local is the what source alias to in bashism. Comparing to execute the /etc/rc.local directly without prepending ., sourcing it won't require chmod +x and can pass exported enviornment variables into the dash as entrypoint.


Taking the offical docker image php as a example: We can find its original ENTRYPOINT is docker-php-entrypoint and original CMD is php-fpm, so we should fill them with:

ENTRYPOINT ["dash", "-xc", ". /etc/rc.local && exec docker-php-entrypoint \"$@\"", "$@"]
CMD ["php-fpm"]

If the order of executing script before or after the entrypoint get started is not important for you, also try the much simpler post-start lifecycle hook in Docker Compose.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.