vscode-remote-release icon indicating copy to clipboard operation
vscode-remote-release copied to clipboard

Export `LOCAL_WORKSPACE_FOLDER` env var to docker compose

Open felipecrs opened this issue 3 years ago • 4 comments
trafficstars

On my devcontainers, I need to mount my workspace folder to the same location as they are in my host machine.

That's required because:

  1. Otherwise bind mounts when running docker run inside of the devcontainer won't work
  2. Otherwise docker-compose commands within the devcontainer won't work (because docker-compose identifies projects based on their folder names, and they would differ from host to inside the devcontainer - the latter usually is always workspace or so)

When I don't use docker-compose, the solution is simple:

// .devcontainer/devcontainer.json

{
  // Mounts the repository in the devcontainer in the same location of the repository in the host.
  // This is needed for running docker containers inside of the devcontainer when mounting volumes
  // inside of the repository, as docker in the devcontainer will talk to the docker daemon in the host.
  "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind,consistency=cached",
  "workspaceFolder": "${localWorkspaceFolder}",
}

But when using docker-compose, the only solution I found was a lot trickier:

.devcontainer/initialize.sh

#!/bin/bash

set -euo pipefail

readonly wanted_line_key="LOCAL_WORKSPACE_FOLDER"
readonly wanted_line="${wanted_line_key}='${PWD}'"
readonly file=".env"

echo "Writing ${wanted_line} to ${file}" >&2
if [[ -f "${file}" ]] && grep --quiet "^${wanted_line_key}=" "${file}"; then
    sed --in-place "s,^${wanted_line_key}=.*,${wanted_line}," "${file}"
else
    echo "${wanted_line}" >>"${file}"
fi

.devcontainer/devcontainer.json

{
  "dockerComposeFile": "../docker-compose.yml",
  "service": "dev",
  "workspaceFolder": "${localWorkspaceFolder}",
  "shutdownAction": "stopCompose",
  "initializeCommand": ".devcontainer/initialize.sh"
}

docker-compose.yml

version: '3'
services:
  dev:
    user: node
    build: .devcontainer
    env_file:
      - .env
    volumes:
      - .:${LOCAL_WORKSPACE_FOLDER}:cached

The above could be greatly simplified if VS Code could export the LOCAL_WORKSPACE_FOLDER before running docker-compose commands so that I could simply refer it there.

felipecrs avatar Jun 19 '22 00:06 felipecrs

Another option might be for us to offer a "localEnv" property similarly to "containerEnv" and "remoteEnv". That would allow for setting local variables using substitutions like ${localWorkspaceFolder}.

chrmarti avatar Jun 20 '22 13:06 chrmarti

Another option might be for us to offer a "localEnv" property similarly to "containerEnv" and "remoteEnv". That would allow for setting local variables using substitutions like ${localWorkspaceFolder}.

But I suppose this would only be applicable for the devcontainer.json, which would not directly help in my case.

An alternative that could help, though, would be to support workspaceMount in docker-compose scenarios (it's already supported in non-docker-compose scenarios).

This way, I could omit the following from the docker-compose:

volumes:
  - .:/workspace:cached

And use the following instead:

// .devcontainer/devcontainer.json

{
  "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind,consistency=cached",
  "workspaceFolder": "${localWorkspaceFolder}",
}

felipecrs avatar Jun 20 '22 13:06 felipecrs

And now I think the approach above would be the cleanest one. :)

felipecrs avatar Jun 20 '22 13:06 felipecrs

This is what I'm currently using.

I define a initializeCommand.sh referenced in devcontainer.json like:

  "initializeCommand": [
    "./.devcontainer/initializeCommand.sh"
  ],

In initializeCommand.sh I create a .env file under .devconatiner folder (where I have my docker-compose.yml) and set the value of localWorkspaceFolder:

#!/bin/bash -i

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

# Create .devcontainer/.env if it does not already exists
[ -e .devcontainer/.env ] || touch .devcontainer/.env
# Load .devcontainer/.env environment variables
source .devcontainer/.env
# Set localWorkspaceFolder environment variable if not already set
[ ! -z "${localWorkspaceFolder}" ] || printf "\nlocalWorkspaceFolder=${SCRIPT_DIR}/..\n" >> .devcontainer/.env

This way, docker-compose uses it's value to replace references in my docker-compose.xml

version: '3.9'
services:
  my-project:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      # Mounts the project folder
      - type: bind
        source: ${localWorkspaceFolder}/
        target: /workspaces/my-project

The drawback is that this technique can't be used with simple Dockerfiles (as there is no environment variables interpolation in the Dockerfile itself).

rubensa avatar Sep 20 '22 12:09 rubensa

Can I make a sanity check to see if I need to watch this and make new friends (reference: https://xkcd.com/2881/)?

My devcontainer docker-compose file looks like this:

version: '3'

services:
  codespace:
    build:
      context: .
      dockerfile: Dockerfile
    ...

  mongodb:
    image: mongo:7
    restart: always
    ports:
      - target: 27017
        published: ${EXPOSED_PORT:27017}
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example

I want the local-env to be able to assign to a port range but in my CI/CD pipeline use this same file to setup mongodb. But I can't give this EXPOSED_PORT. It do not let me define it, even with the initializeCommand. And a .env is not a solution because I don't want the CI/CD using it.

Again, we can make crazy solutions but would be nice to have a envVar spot in devcontainer.json and make it elegant.

So... new friends?

flpStrri avatar Jan 23 '24 15:01 flpStrri