docker-compose config invalid output when replacing env variables

Viewed 164

I'm trying to generate multiple instances of the same git repository with a generic docker-compose.yml file and multiple .env files.

For this somewhere in the code I generate a temporary folder which contains:

.env:

APP_PORT="3000"
APP_NAME="app-name"
REPO_NAME="repo-name"

docker-compose.yml

version: '3.6'
services:
  web-app:
    image: golang:alpine
    environment:
      - APP_PORT
      - APP_NAME
      - REDIS_HOST=db-app
    ports:
      - ${APP_PORT}:${APP_PORT}
    volumes:
      - /opt/docker/repositories/${REPO_NAME}:/app
    command: sh -c "cd /app && go run ./"
  db-app:
    image: redis:alpine

then running docker-compose config in this directory gives me the following output :

services:
  db-app:
    image: redis:alpine
  web-app:
    command: sh -c "cd /app && go run ./"
    environment:
      APP_NAME: app-name
      APP_PORT: '3000'
      REDIS_HOST: db-app
    image: golang:alpine
    ports:
    - published: 3000
      target: 3000
    volumes:
    - /opt/docker/repositories/repo-name:/app:rw
version: '3.6'

This did not only interpolate env variables, it also changed some fields such as ports with published and target, and a :rw at the end of my volume.

This is all done in Go, and when I try to unmarshal the output into a Go struct with yaml fields, it is not recognized as a valid docker-compose file because of the ports field (which is supposed to be an array of strings).

How can I make it so docker-compose config only replaces the ${APP_PORT} with its value and not add these extra unwanted fields ?

1 Answers

Reading the source code, I found this in the config types:

def legacy_repr(self):
    return normalize_port_dict(self.repr())

Which is the representation you need. So I searched for legacy_repr in the source code and found this:

if 'ports' in service_dict:
    service_dict['ports'] = [
        p.legacy_repr() if p.external_ip or version < VERSION else p
        for p in service_dict['ports']
    ]

So apparently, to trigger the use of the legacy representation, you either need to have an external IP address or need to do something with the version. I tried to downgrade the docker-compose.yaml file version but it didn't change anything (maybe it's the docker-compose CLI's version instead).

Reading the spec of the docker-compose config file, in the ports section, you can specify the IP address in the short syntax:

[HOST:]CONTAINER[/PROTOCOL] where:

  • HOST is [IP:](port | range)
  • CONTAINER is port | range
  • PROTOCOL to restrict port to specified protocol. tcp and udp values are defined by the specification, Compose implementations MAY offer support for platform-specific protocol names.

So a solution is to replace ${APP_PORT}:${APP_PORT} by:

0.0.0.0:${APP_PORT}:${APP_PORT}

By setting the external IP address to 0.0.0.0 you are not restricting anything and you force the use of the legacy representation.

Related