76

I want to add a volume to my service, but only if the final user gave a folder for it. Otherwise, no volume should be mounted, for the already-prepared image has valid data in a default folder.

That is, I want to do something like (pseudocode):

services:

  my_awesome_service:
  
    volumes:
      if ${VARIABLE} => ${VARIABLE}:/app/folder

Are such conditional statements definable in a docker-compose file?

The only way I see to make this possible is to first define a base docker-compose file, which does not have the volume mount, and the call on a second docker-compose file only if the $VARIABLE is defined. This is fine for a single or few conditions, but gets nasty if there are many.

Any solution?

juanmirocks
  • 4,786
  • 5
  • 46
  • 46
  • 1
    I don't think it's possible directly in the docker-compose file (never heard of conditional statement in docker-compose), but you could run a script that would take a basic docker-compose and add whatever is necessary before running `docker-compose`. – Holt May 17 '18 at 08:55
  • `yq` is a linux command line tool (and python module) that has an option for editing YAML files in place. Change your file in one line https://kislyuk.github.io/yq/ – grofte May 11 '23 at 10:44

7 Answers7

63

Poor man's solution:

    volumes:
      ${VARIABLE:-/dev/null}:/app/folder

Or:

    volumes:
      ${VARIABLE:-/dev/null}:${VARIABLE:-/tmp}/app/folder
gatopeich
  • 3,287
  • 31
  • 26
  • 13
    How did I not knew about env var default values before? This is so helpful, not only in docker-compose files – Leonardo Raele Dec 18 '20 at 00:14
  • 3
    This is a risky solution. 1. Nothing inside the container differentiates `/app/folder` from any regular dir. Mapping `/app/folder` to host's `/dev/null` may cause some problems. For eg. container application writes or moves the data to `/app/folder` expecting the data to be saved but it will be lost. Moreover no error handling will do its job because copying or moving data to `/dev/null` does not cause any error whatsoever. Therefore even the application that has a proper error handling of write/move will happily continue its execution as if everything was saved into `/app/folder` – Jimmix Apr 11 '21 at 00:25
  • 2. Using this solution when the container is run as a root and especially if it is running in a privileged mode is a potential risk of deleting container's `/app/folder` and in consequence host's `/dev/null` that will most likely interfere host operation see [What prevents rm /dev/null](https://superuser.com/questions/275393/what-prevents-rm-dev-null/275394). However since the mapping of a container's `/app/folder` to the host's `/dev/null` is done by docker demon it could prevent from doing that in order to ensure a mount exists but even if so then who promised it would be always this way? – Jimmix Apr 11 '21 at 00:38
  • 3
    There's an answer that [explains what the syntax of `:-` does](https://stackoverflow.com/questions/10390406/usage-of-colon-dash-in-bash) here. TL;DR: it specifies a default value to use when `VARIABLE` is not defined. – Wyck Jun 09 '21 at 15:43
  • @Wyck Not defined _or empty_. The behavior you described corresponds to `-`, which has this difference with `:-`. At least in Bash. Not sure how much Docker Compose sticks to that. `unset -v a; b=''; echo "${a-1} ${a:-2} ${b-3} ${b:-4}"` yields `1 2 4`. – Alice M. Sep 08 '22 at 08:21
  • I get an error on ubuntu 22 when no ENV var is supplied Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/dev/null" to rootfs at "/tmp": mount /dev/null:/tmp (via /proc/self/fd/6), flags: 0x5001: not a directory: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type – Amanda May 06 '23 at 20:26
  • solved via `${STORAGE_SRC_FOLDER:-/tmp}:${STORAGE_DEST_FOLDER:-/tmp/empty:ro}`. YOLO – Amanda May 06 '23 at 20:45
17

Nothing like this currently exists. Options to implement this that I can come up with include:

  1. Make lots of compose file pieces, and merge together the parts you need to build up the final file.

  2. Dynamically generate your compose file. Something like jsonnet may be a good starting point.

  3. Skip compose, and just dynamically generate your docker run command. This starts to lack portability but some use cases are just easier to script yourself.

  4. Submit a PR to the compose and docker/cli github repos to extend the compose functionality. Doing this with a golang template syntax would make the most sense to me.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • close question I found on this subject on docker compose issue https://github.com/docker/compose/issues/5756 ; considering conditional statement out-of-scope :( – boly38 Dec 18 '19 at 09:24
4

So far, docker-compose's format does not support conditional statements.

Two possible workarounds are:

  1. Pass a "complex" (list-like) variable to the docker-compose such as in this example:

docker-compose.yaml:

command: ${COMMAND_PARAMS}

bash:

#!/bin/bash
if test -z $CONDITION; then
  params="-param1 ${MIPARAM1}"
else
  params="-param1 ${MIPARAM1} -param2 ${MIPARAM2}"
fi
COMMAND_PARAMS=$params docker-compose up

(credits goes to original poster on github, @shin-)

  1. Prepare the default folder in the docker image in a folder named something like folder_defaults, then have the volume always defined in docker-compose.yml, but then finally, have an internal script in the docker image that checks whether the volume folder is empty, and if so ln -s to the folder_defaults; otherwise leave it as it is.

Example of the conditional script:

if [ -z "$(ls -A /a/folder)" ]; then
  do something... using /a/folder_defaults
fi
juanmirocks
  • 4,786
  • 5
  • 46
  • 46
  • Using `ls` for checking path this way does not make any distinction between a dir `/a/folder` and a file with the name `folder` inside the `/a` dir. Also `-z` executes `then` if `ls` found no dir and no file. If you wanted to check if a directory or a file existed then `-n` switch would work better but the simplest way would be to use `-d` that checks if a path is an existing directory: `if [ -d "/a/folder" ]; then`. Please also note that your code will fail if the path has a space because the path isn't quoted (sub-shell is but the path inside it is not) – Jimmix Apr 11 '21 at 01:34
1

If you are using Rancher for orchestration, there are escapes {{...}} available you can use for conditional statements in Rancher's version of docker-compose.

Read more about the integrated GO templating system here.

WeSee
  • 3,158
  • 2
  • 30
  • 58
1

Using conditional stmts in docker-compose is somewhat possible. Checkout variable substitution. The documentation is available for just the simplest of if-else. And since I have not tried with complex expressions involving strings, I cannot be sure. But following are the points you might want to keep in mind when trying out conditional variables:

  • Environment variables in docker-compose file (with only a key) are resolved to their values on the machine Compose is running on. So when using ${CHEESE} in docker-compose, one should have CHEESE="cheddar" set in .env file or exported manually in host machine.
  • Alternatively .env file can be set with env_file option. Variables in this file are exported in the docker container before the variables under environment option. That means variables in environment will override variables in env_file .
Paul Hansen
  • 1,167
  • 1
  • 10
  • 23
khari-sing
  • 650
  • 6
  • 16
0

Use a programming language like JavaScript or Python to generate your yaml file

For example in JavaScript you can create the configuration in json and then convert that to yaml and write somewhere in the file system before deploying

Ernest Okot
  • 880
  • 3
  • 8
  • 23
-2

We can use conditional statement in docker-compose.yml file as below:

#jinja2: lstrip_blocks: True
version: "3.2"
services:
  app-name:
    image: url
    deploy:
      replicas: {{replication-num}}
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 1G
      restart_policy:
        condition: any
        max_attempts: 3
      update_config:
        delay: 60s
        failure_action: rollback
        parallelism: 1
        monitor: 30s
      placement:
        constraints:
          - node.labels.node == worker
    ports:
      - "{{url}}:8000"
    environment:
      ps_environment: "{{env}}"      
    {% if env=='sandbox' %}
    extra_hosts:
      - {{ sandbox_fully }}
    {% endif %}
    secrets:
      - source: pwdFile   
    labels:
      - container_name=app-name
    networks:
      - App_External_Overlay
    volumes:
      - {{ dir }}:{{ dir }}
Justin Lessard
  • 10,804
  • 5
  • 49
  • 61
  • 4
    This is really cool, but I think it only exists if you use extra software that is not part of native docker-compose? Can you update this to include the complete solution? – Scott Smith May 29 '20 at 16:35
  • 1
    This looks like it may be for SaltStack – SaintWacko Jun 02 '20 at 15:30
  • 1
    @ScottSmith Apparently [jinja-compose](https://github.com/sinzlab/jinja-compose) can run this syntax. Haven't tested it myself though. – Bor691 Aug 10 '20 at 19:36
  • This looks like Jinja2, which is used by jinja-compose and SaltStack, as well as Ansible. jinja-compose does look like an easy way to apply Jinja2 for Docker Compose. You could also use a more generic Jinja2 based templating tool such as [yasha](https://github.com/kblomqvist/yasha), which is more actively maintained but still much less complex than Ansible or SaltStack. – RichVel Jun 29 '21 at 08:33