163

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?

Blooze
  • 1,987
  • 4
  • 16
  • 19
  • Absolutely, that's a fairly normal way of setting secrets. Just add the relevant shell script as (or to) your CMD or ENTRYPOINT. – Paul Hicks Dec 03 '17 at 06:06
  • to have access to secret without expose in Dockerfile, you can use .env file with docker-compose : https://docs.docker.com/compose/environment-variables/ – bcag2 Aug 03 '21 at 16:33
  • 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](https://github.com/bitnami/bitnami-docker-kafka/blob/master/3.1/debian-10/rootfs/opt/bitnami/scripts/libkafka.sh#L768) for kafka. You need just put a scripts to the "docker-entrypoint-initdb.d" to run some sort of set up. – stopanko Apr 07 '22 at 17:42

4 Answers4

93

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.

redgeoff
  • 3,163
  • 1
  • 25
  • 39
Gibson Lunaziz
  • 953
  • 6
  • 8
  • 9
    through the luck of the draw, I'm trying to do the same thing with mongo and stumbled across this answer in this thread. Thanks! – java-addict301 Dec 02 '21 at 18:27
  • 2
    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... – Stijn de Witt Apr 06 '22 at 07:45
  • @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. – Gandalf Jun 25 '22 at 14:43
  • @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 – Gandalf Jun 25 '22 at 14:44
  • @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. – Gandalf Jun 25 '22 at 14:44
  • @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 – Gandalf Jun 25 '22 at 14:44
71

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.

yuяi
  • 2,617
  • 1
  • 23
  • 46
Bernard
  • 16,149
  • 12
  • 63
  • 66
  • 5
    executing this causes `/usr/local/bin/docker-entrypoint.sh: line 172: /usr/local/bin/init.sh: No such file or directory` – James Jordan Taylor Aug 14 '18 at 00:28
  • 3
    Additionally, removing the first part of the command causes the error `exec: not found` – James Jordan Taylor Aug 14 '18 at 00:31
  • Looks like docker isn't treating the `&&` as command chaining. It's treating it and the rest of the line as additional arguments. – Burhan Ali Feb 20 '20 at 11:58
  • 3
    @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`. – Bernard Feb 20 '20 at 23:31
  • @Bernard Thank you much more elegant that creating another container – SparkleGoat Dec 31 '20 at 08:55
  • What is `myexecutable` in yml file? I am just trying to run this command as script `redis-server --deamonize yes`. Do i need to use exec? – Murtaza Haji Jun 02 '21 at 17:33
  • 1
    @MurtazaHaji. In your case the line would be `command: sh -c "/usr/local/bin/init.sh && exec redid-server --deamonize yes` – Bernard Jun 07 '21 at 11:25
  • I don't understand the relation between the `command:` and ENTRYPOINT in the sourceimage. If we're using `command:`, then __the CMD__ of the sourceimage _might be_ overridden, not the ENTRYPOINT. Am I missing something? – aderchox Oct 31 '21 at 09:32
  • 3
    @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. – Bernard Oct 31 '21 at 11:10
23

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.

Asad-ullah Khan
  • 1,573
  • 18
  • 22
  • 1
    Got it! I needed `entrypoint: sh -c "sh init.sh"` for it to work properly – fullStackChris May 25 '22 at 19:30
  • 1
    interesting, looks like you mounted it to `./init.sh` instead of `/init.sh`? seeing `sh` twice feels icky – Asad-ullah Khan May 25 '22 at 23:50
  • 1
    using `entrypoint: sh -c "sh /init.sh"` resolve the issue `sh: /init.sh: Permission denied` – mag May 16 '23 at 13:06
  • how to solve "Permission denied" problem? – rgaponov Jul 26 '23 at 19:20
  • 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 https://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 (https://stackoverflow.com/a/35620590/4021308) – Asad-ullah Khan Jul 26 '23 at 19:46
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>
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250