vscode-remote-release
vscode-remote-release copied to clipboard
Export `LOCAL_WORKSPACE_FOLDER` env var to docker compose
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:
- Otherwise bind mounts when running
docker runinside of the devcontainer won't work - Otherwise
docker-composecommands 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 alwaysworkspaceor 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.
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}.
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}",
}
And now I think the approach above would be the cleanest one. :)
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).
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?