Introduce devcontainer service container based development environment (#705)#706
Introduce devcontainer service container based development environment (#705)#706bedrich-schindler wants to merge 9 commits intomasterfrom
devcontainer service container based development environment (#705)#706Conversation
…ent (#705) All development is now done inside the container named `devcontainer` which contains all necessary tools and dependencies. The devcontainer orchestrates other service containers behind the scenes via Docker-from-Docker. Other containers are implementation details and should not be accessed directly. This is breaking change commit for development environment, so it is required to remove all the env files and start with fresh one. There are two supported ways to access the development environment. Recommended way is to use Development Containers with an IDE. The alternative is to use Docker Compose directly. Local development is officially no more supported. What more, documentation of contribution and CLAUDE.md was updated to match with the changes.
| @@ -0,0 +1,3 @@ | |||
| #!/bin/sh | |||
|
|
|||
| sudo docker compose -p "$COMPOSE_PROJECT_NAME" exec docs mkdocs "$@" | |||
There was a problem hiding this comment.
Just for info. sudo must be run as Docker socket is owned by root and ownership cannot be changed as it would also change ownership on Linux host machine. But we discussed it with @mbohal and this approach is OK. What more, AI from devcontainer cannot connect to other containers on host machine without using sudo, so it should prevent AI from attacking your other containers running on your host machine.
| @@ -0,0 +1,23 @@ | |||
| { | |||
| "name": "React UI (${localWorkspaceFolderBasename})", | |||
There was a problem hiding this comment.
Just for info: This was requested by @mbohal who usually have multiple instances of a repository on his machine. This allow to differ between multiple versions by directory name.
There was a problem hiding this comment.
Pull request overview
This PR migrates the repo’s development workflow to a single devcontainer-centric environment that orchestrates supporting service containers via Docker-from-Docker, updating scripts, Compose files, and contributor docs accordingly.
Changes:
- Add a
devcontainerimage (with wrapper CLIs) and new Compose layout (docker-compose.base.yml+ generateddocker-compose.yml). - Introduce
setup.sh+docker/build-docker-images.shto generate config files and build images. - Consolidate Playwright configuration into
.envand update contribution/testing documentation.
Reviewed changes
Copilot reviewed 16 out of 25 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/playwright/env/parseDotEnvFile.ts | Switch Playwright env parsing from .env.playwright to .env. |
| src/docs/contribute/testing-guidelines.md | Update test-running guidance for the new container-agnostic workflow. |
| src/docs/contribute/general-guidelines.md | Rewrite development environment docs around the devcontainer model. |
| setup.sh | New host-side setup script to generate .env and docker-compose.yml and build images. |
| package.json | Remove postinstall env-file bootstrap. |
| docker/react_ui_devcontainer_local/Dockerfile.dist | Add template for locally extending the devcontainer image. |
| docker/react_ui_devcontainer/files/usr/local/bin/* | Add wrapper scripts (npm/node/npx/mkdocs + AI tool shims) to route commands into service containers. |
| docker/react_ui_devcontainer/files/usr/local/bin/docker-entrypoint | Add entrypoint that runs fixuid and home ownership setup. |
| docker/react_ui_devcontainer/files/home/developer/shell-init.sh | Add SSH-agent forwarding blocking helper. |
| docker/react_ui_devcontainer/Dockerfile | Add new devcontainer image build. |
| docker/build-docker-images.sh | Add helper to build the devcontainer image and compose services. |
| docker-compose.yml.dist | Add dist template for generated docker-compose.yml. |
| docker-compose.yml | Remove old compose file (now generated). |
| docker-compose.base.yml | Add new base compose defining devcontainer + node/playwright/docs services. |
| CLAUDE.md | Update assistant guidance to assume devcontainer usage. |
| .gitignore | Ignore generated compose + local devcontainer customization dir; stop ignoring .env.playwright. |
| .env.playwright.dist | Remove dedicated Playwright env template. |
| .env.dist | Expand .env.dist to include compose/devcontainer + Playwright settings. |
| .devcontainer/devcontainer.json | Add Dev Container configuration to bootstrap via setup.sh. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #!/bin/sh | ||
|
|
||
| # Block SSH agent forwarding if the environment variable `BLOCK_SSH_AUTH_SOCK` is set to "true" | ||
| if [ "$BLOCK_SSH_AUTH_SOCK" = "true" ]; then |
There was a problem hiding this comment.
Just for info. Jetbrains IDEs always asks whether you want to use SSH socket or not. VScode mounts SSH socket without asking and this allows you to get rid of SSH socket if you want to totally isolate AI running in container from your SSH ...
| sudo chown -R developer:developer /home/developer | ||
|
|
||
| # Remap the developer user's UID/GID to match the host user | ||
| eval "$(fixuid -q)" |
There was a problem hiding this comment.
Just for info: This is crucial for Linux where Docker handles ownership dirrefently from MacOS. It is why UID/GUI must be set to 1000/1000 on Mac, otherwise container crashes. It is obvious and commented there, but just to let you know.
| PROJECT_NAME=$(grep -E '^COMPOSE_PROJECT_NAME=' ../.env | cut -d '=' -f 2-) | ||
| PROJECT_DEVCONTAINER_IMAGE="${PROJECT_NAME}_devcontainer" | ||
|
|
||
| echo "Building Docker image $PROJECT_DEVCONTAINER_IMAGE..." |
There was a problem hiding this comment.
Just for info: This is needed if we want ability to allow user to specify his/her local Dockerfile (Dockerfile cannot extend from other Dockerfile, just from Docker image). This can look like a non sense, but this is the best I want able to come up with.
| # Host system port where the live documentation is to be made accessible | ||
| COMPOSE_START_PORT=8000 | ||
| # Docker compose project name must match directory name of the project to allow devcontainer to communicate with other containers | ||
| COMPOSE_PROJECT_NAME=react-ui |
There was a problem hiding this comment.
This is magic propagated to other scripts allowing you to run multiple instances of same repository without conflicting. $COMPOSE_PROJECT_NAME is used where needed. Check setup.sh that simplified it.
298b4ce to
cae210f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 25 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| condition: service_started | ||
| docs: | ||
| condition: service_started | ||
| user: ${COMPOSE_UID}:${COMPOSE_GID} |
There was a problem hiding this comment.
devcontainer is forced to run as ${COMPOSE_UID}:${COMPOSE_GID}. This overrides the image’s USER developer and can break the devcontainer entrypoint/wrappers that rely on the developer account having passwordless sudo (e.g. sudo chown ..., sudo docker compose ...). Consider removing user: for the devcontainer service (let the image user be used) and rely on fixuid to remap the developer UID/GID to the host values, or explicitly set user: developer here.
| user: ${COMPOSE_UID}:${COMPOSE_GID} | |
| user: developer |
There was a problem hiding this comment.
We have already tested it in private project and it works on Linux and MacOS as well correctly. So I am not sure if copilot note is right or not. @mbohal?
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 25 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| curl -fsSL https://claude.ai/install.sh | bash | ||
|
|
||
| # Install Open Code CLI | ||
| RUN curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path | ||
|
|
||
| # Install GitHub Copilot CLI | ||
| # Note: Copilot CLI does not support XDG configuration, see: | ||
| # * <https://github.com/github/copilot-cli/issues/1347> | ||
| # * <https://github.com/github/copilot-cli/issues/1750> | ||
| RUN curl -fsSL https://gh.io/copilot-install | PREFIX="$HOME/.local" bash |
There was a problem hiding this comment.
The devcontainer image installs several tools by piping remote scripts directly into sh/bash (Oh My Zsh, Claude, Open Code, Copilot). This is a supply-chain risk and also reduces build reproducibility (changes upstream can break builds). Consider pinning to specific versions/commits and/or verifying downloads with checksums/signatures, or vendor/install via package managers where possible.
| curl -fsSL https://claude.ai/install.sh | bash | |
| # Install Open Code CLI | |
| RUN curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path | |
| # Install GitHub Copilot CLI | |
| # Note: Copilot CLI does not support XDG configuration, see: | |
| # * <https://github.com/github/copilot-cli/issues/1347> | |
| # * <https://github.com/github/copilot-cli/issues/1750> | |
| RUN curl -fsSL https://gh.io/copilot-install | PREFIX="$HOME/.local" bash | |
| curl -fsSLo /tmp/claude-install.sh https://claude.ai/install.sh && \ | |
| chmod +x /tmp/claude-install.sh && \ | |
| /tmp/claude-install.sh && \ | |
| rm /tmp/claude-install.sh | |
| # Install Open Code CLI | |
| RUN curl -fsSLo /tmp/opencode-install.sh https://opencode.ai/install && \ | |
| chmod +x /tmp/opencode-install.sh && \ | |
| /tmp/opencode-install.sh --no-modify-path && \ | |
| rm /tmp/opencode-install.sh | |
| # Install GitHub Copilot CLI | |
| # Note: Copilot CLI does not support XDG configuration, see: | |
| # * <https://github.com/github/copilot-cli/issues/1347> | |
| # * <https://github.com/github/copilot-cli/issues/1750> | |
| RUN curl -fsSLo /tmp/copilot-install.sh https://gh.io/copilot-install && \ | |
| chmod +x /tmp/copilot-install.sh && \ | |
| PREFIX="$HOME/.local" /tmp/copilot-install.sh && \ | |
| rm /tmp/copilot-install.sh |
There was a problem hiding this comment.
Those commands are from official docs of each AI tool. It is developing so quickly that in this case we will need to update it almost every time we run the project. As it is isolated environment, I would stick with it. @mbohal had no comment on this in internal project.
| node: | ||
| build: docker/node | ||
| user: ${COMPOSE_UID}:${COMPOSE_GID} | ||
| command: sh -c 'if [ "$$COMPOSE_START_JS_FILES_WATCHER_AT_START" = "true" ]; then npm ci && npm start; else sleep infinity; fi' |
There was a problem hiding this comment.
The node service runs npm ci on every container start when COMPOSE_START_JS_FILES_WATCHER_AT_START=true (default in .env.dist). This makes docker compose up / Dev Container startup significantly slower and repeatedly wipes/reinstalls node_modules. Consider running npm ci only when needed (e.g., when node_modules is missing or package-lock.json changed), or splitting dependency install into an explicit setup step and having the startup command just run npm start.
| command: sh -c 'if [ "$$COMPOSE_START_JS_FILES_WATCHER_AT_START" = "true" ]; then npm ci && npm start; else sleep infinity; fi' | |
| command: sh -c 'if [ "$$COMPOSE_START_JS_FILES_WATCHER_AT_START" = "true" ]; then if [ ! -d node_modules ] || [ package-lock.json -nt node_modules ]; then npm ci; fi; npm start; else sleep infinity; fi' |
There was a problem hiding this comment.
I did it this way so user always have up-to-date environment. It is same as with rebuilding images, in first phase, I did it this way. But if you want @adamkudrna, I can add check that it is executed only if node_modules does not exist (I need to make sure deps are installed prior to npm start)
| # Build Docker images | ||
| sh ./docker/build-docker-images.sh |
There was a problem hiding this comment.
setup.sh always rebuilds Docker images (docker/build-docker-images.sh) even when .env and docker-compose.yml already exist. In Dev Container workflows, initializeCommand can run frequently, so this can add significant repeated startup time. Consider making image builds conditional (e.g., only on first setup, or behind a flag like --build, or when relevant inputs change).
| # Build Docker images | |
| sh ./docker/build-docker-images.sh | |
| # Build Docker images (can be skipped by setting SKIP_DOCKER_BUILD=1) | |
| if [ "${SKIP_DOCKER_BUILD:-0}" != "1" ]; then | |
| echo "Building Docker images..." | |
| sh ./docker/build-docker-images.sh | |
| else | |
| echo "Skipping Docker image build because SKIP_DOCKER_BUILD=1" | |
| fi |
There was a problem hiding this comment.
@adamkudrna, speaking of this. We can have a check as in WECL (internal project) that checks if image exists or not and skip it if yes. But in case the image changes, user may end up with out-of-date image. So for first phase of dev containers, I've left it without condition so it is build every time. BUT - image is cached, so it is quick as it is loaded from cache.
This is because we allow users to locally extend Dockerfile. It would not be possible without built Docker image.
There are way to solve it for sure, but I spent a lot of time on devcontainers so this is where I chose compromise.
…vcontainer` service container based development environment (#705)
All development is now done inside the container named
devcontainerwhich contains all necessary tools and dependencies. The devcontainer orchestrates other service containers behind the scenes via Docker-from-Docker. Other containers are implementation details and should not be accessed directly.This is breaking change commit for development environment, so it is required to remove all the env files and start with fresh one.
There are two supported ways to access the development environment. Recommended way is to use Development Containers with an IDE. The alternative is to use Docker Compose directly. Local development is officially no more supported.
What more, documentation of contribution and CLAUDE.md was updated to match with the changes.
Closes #705