Skip to content

Instantly share code, notes, and snippets.

@Gems
Last active October 20, 2023 07:38
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save Gems/a40d7eb45f46c82f990aa7e8845d7e7a to your computer and use it in GitHub Desktop.
Save Gems/a40d7eb45f46c82f990aa7e8845d7e7a to your computer and use it in GitHub Desktop.
A `docker-compose` wrapper for multiple configuration files with relative paths
#!/usr/bin/env bash
TMP_FILE=/tmp/docker-compose.$$.yaml
finish() {
rm ${TMP_FILE} ${TMP_FILE}.tmp 2>/dev/null
}
trap finish EXIT
compose-config() {
mv -f ${TMP_FILE} ${TMP_FILE}.tmp
docker-compose -f ${1} -f ${TMP_FILE}.tmp config >${TMP_FILE}
rm -f ${TMP_FILE}.tmp 2>/dev/null
}
args=()
files=()
while [ -n "$1" ]; do
case "$1" in
-f)
shift; files+=($1)
;;
*)
args+=($1)
;;
esac
shift
done
echo 'version: "3"' >${TMP_FILE}
for f in ${files[@]}; do
compose-config ${f}
done
docker-compose -f ${TMP_FILE} ${args[@]}
exit $?
@Enteee
Copy link

Enteee commented Jun 30, 2020

I adapted this idea and I am now using a similar script to start all of my personal infrastructure.

@stijnpieters
Copy link

Great script, provides a great workaround for docker/compose#3874

@bimlas
Copy link

bimlas commented Jul 6, 2022

For me, it did not accepting --scale my-service=2, so I modified it:

#!/usr/bin/env bash

TMP_FILE=/tmp/docker-compose.$$.yaml

finish() {
  rm ${TMP_FILE} ${TMP_FILE}.tmp 2>/dev/null
}

trap finish EXIT

compose-config() {
  mv -f ${TMP_FILE} ${TMP_FILE}.tmp
  docker-compose -f ${1} -f ${TMP_FILE}.tmp config > ${TMP_FILE}

  rm -f ${TMP_FILE}.tmp 2>/dev/null
}

args=''
files=()

while :; do
  getopts ":" opt
  case $OPTARG in
    f) files+=(${!OPTIND})
    ;;
  esac

  ((OPTIND++))
  [ $OPTIND -gt $# ] && break
done

echo 'version: "3"' > ${TMP_FILE}

args=$@
for f in ${files[@]}; do
  compose-config ${f}
  args=`echo $args | sed -E "s#-f +${f}##"`
done

docker-compose -f ${TMP_FILE} $args
exit $?

@Gems
Copy link
Author

Gems commented Jul 6, 2022

Thanks for the heads-up @bimlas
I fixed the arguments parsing section and it works on my OS X. AFAIR, I tested this script on WSL, so it might have been some quirks that made it work there.

Would appreciate it if you check the new version in your environment ✌🏻

@bimlas
Copy link

bimlas commented Jul 7, 2022

@Gems: It's accepting --scale my-service=2 and exec -it but it removes -T (uppercase flags) from exec -T

@Gems
Copy link
Author

Gems commented Jul 7, 2022

Thanks for checking and reporting @bimlas

It appears that I got to learn getopts better πŸ˜… though anyway the arguments parsing problem here isn't quite for getopts, so I replaced it with a simpler solution. I checked with the failing arguments line using exec -T and it seems to be working.

Cheers!

@bimlas
Copy link

bimlas commented Jul 11, 2022

@Gems, seems to work fine, thanks!

@Gems
Copy link
Author

Gems commented Jul 11, 2022

Thanks for your collaboration, @bimlas πŸ‘πŸ»

@jneuendorf-i4h
Copy link

jneuendorf-i4h commented Oct 19, 2023

@Gems Thanks for sharing your script. Since I personally prefer using verbose flags when using scripts, I added support for --file:

while [ -n "$1" ]; do
  case "$1" in
    -f | --file)
      shift; files+=($1)
      ;;
    *)
      args+=($1)
      ;;
  esac
  shift
done

Maybe others find that useful, as well. πŸ˜‰

PS: Also, it seems that one should now use docker compose instead docker-compose.


UPDATE

Here is my final version which also

  • correctly prefixes version: "3"
  • uses a more secure tempfile
  • avoids missing project name
  • correctly override previous config items
#!/usr/bin/env bash

PROJECT_NAME=$(basename `pwd`)

# Create temp file securely
TMP_FILE=`mktemp /tmp/docker-compose.XXXXXX` || exit 1
mv ${TMP_FILE} ${TMP_FILE}.yml
TMP_FILE=${TMP_FILE}.yml

# Clean up
finish() {
  rm ${TMP_FILE} ${TMP_FILE}.tmp 2>/dev/null
}

trap finish EXIT


# Prepend given file to config in order to keep the file's relative context
compose-config() {
  mv -f ${TMP_FILE} ${TMP_FILE}.tmp
  # Add the file twice: 1st for the context, 2nd for config overrides
  docker compose -p ${PROJECT_NAME} -f ${1} -f ${TMP_FILE}.tmp -f ${1} config > ${TMP_FILE}
  rm -f ${TMP_FILE}.tmp 2>/dev/null
}

# Separate -f/--file arguments from the rest
args=()
files=()

while [ -n "$1" ]; do
  case "$1" in
    -f | --file)
      shift; files+=($1)
      ;;
    *)
      args+=($1)
      ;;
  esac
  shift
done

# Generate config
for f in ${files[@]}; do
  compose-config ${f}
done

# Prepend version
mv -f ${TMP_FILE} ${TMP_FILE}.tmp
echo 'version: "3"' > ${TMP_FILE}
cat ${TMP_FILE}.tmp >> ${TMP_FILE}
rm -f ${TMP_FILE}.tmp 2>/dev/null

echo -e "
FINAL CONFIG
------------
"
docker compose -f ${TMP_FILE} config
docker compose -f ${TMP_FILE} ${args[@]}
exit $?

@Gems
Copy link
Author

Gems commented Oct 20, 2023

Thank you for sharing your version @jneuendorf-i4h , I bet many people find it even more useful than the original one πŸ‘πŸ»

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment