-
-
Notifications
You must be signed in to change notification settings - Fork 39
Add devcontainer setup #528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MareStare
wants to merge
88
commits into
philomena-dev:master
Choose a base branch
from
MareStare:feat/devcontainer
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
679752e
to
4c83c10
Compare
1 task
e56be33
to
4519128
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Before you begin
TLDR
See the video presentation of this PR here: https://www.youtube.com/watch?v=fMdQnI7HR0U
This PR adds the
.devcontainer
definition allowing us to use a consistent dev environment with predefined configurations and dependencies. Note, however, that running your IDE in a devcontainer is not required. If you'd still like to develop from your host the scripts will spin up a background devcontainer for you (which just sleeps) and forward their execution to that container viadocker exec
, so things should still just work.I think I've tried almost every configuration design possible at this point. You can see all the tries in the commit history. I'll squash them before merging.
This PR also adds the
shellcheck
linter for all the shell scripts.Not Covered
apk
), and this part needs its own overhaul, because instead we should userustup
. For example, the existing rust tooclhain setup doesn't include neitherrustfmt
norclippy
.Full Description
Let's take a look at the dev container setup. It should work with any IDE that supports the devcontainer specification, which includes VSCode and Jetbrains, but I will use VSCode as an example for this demo.
Here we have just one local OS that hosts the entire thing.
In this case, you have your Docker daemon on your host, and you can create a dev container that can be used for development.
It contains all the required dependencies and configurations pre-made for you.
It is configured with a
devcontainer.json
file. That file contains a reference to adocker-compose
stack with just a single container.We are using docker-compose here even if it's just a single container to make it easier to reuse the container definition between the IDE usage of the devcontainer and our custom scripts.
What I mean by this is that developing in the devcontainer is not a hard requirement. If you run our custom scripts from your host machine they will still benefit from the devcontainer docker-compose definition. They will lazily initialize that devcontainer stack and forward their execution into that container via
docker exec
. This way our custom scripts may always assume they are running inside of our container that has all the required dependencies and configs.Note that your repository is bind-mounted into the container, so any changes that you make inside of the devcontainer in the repo will still be visible on the host.
Extensions
So if you'd like to run your IDE in the devcontainer, then you should know about the extensions affiliation to their execution environment.
There is this thing in VSCode called the extension host. This is the process that hosts all VSCode extensions implemented in NodeJS.
VSCode runs this extension host inside of this container. This is where extensions like Elixir, TypeScript or Rust LSP prefer to run, because they need access to the underlying system, processes and files.
However, there is another class of extensions that can run and actually should run on the local machine, because they are simple UI extensions like a UI theme, icons customization and stuff pure like that which can run in a sandbox. VSCode does in fact run them on the local machine's instance of VSCode.
So the extensions have an attribute called
extensionKind
that specifies a prioritized list of environments where the extension perfers to run. Why this is a list, it's a different story, but what you should know is that some extensions will need to be explicitly installed in the devcontainer by specifying them in thedevcontainer.json
extension. For your UI extensions, you don't need to install them there and they will just work transparently for UI.However, some extension authors aren't careful about defining the
extensionKind
of their extension, and thus their extensions are assigned the default"workspace"
environment. You can work around that by overriding theextensionKind
for a specific extension in your settings. Note that the extension may or may not work on the UI environment depending on the extension's business logic.Shell
The shell configuration is apparently part of the container. There are numerous different shell implementations but we have to select only one for the devcontainer, because this is where we create the container user (which not
root
by the way, it's calledphilomena
). This is where we configure its default shell. Right now this shell iszsh
with someoh-my-zsh
plugins. The.zshrc
file and all other shell configs are committed directly into the repo.But, if you'd like to customize the shell, maybe add some personal stuff to this environment including custom utilities, you can do that by defining this configuration via a
dotfiles
repository.Dotfiles
Let's review how the dotfiles customization works. It's actually quite a handy model. This is a repository with all the various system configuration files that are symlinked to the dotfiles repository from their expected location.
For example, usually you'd define your
dotfiles
repository in the home directory under$HOME/dotfiles
.There you can put stuff like your custom
.zshrc
,.gitconfig
, and public SSH keys. Then you can use a dotfiles utility like GNUstow
to install your dotfiles into your actual system as symlinks.You need to script that process via a file in the repo that can be called
install.sh
. In thatinstall.sh
you can do any custom modifications to the system that you want.You can then reference your dotfiles repository in your VSCode setting via
dotfiles.repository
, and VSCode will always clone and run theinstall.sh
in your devcontainer when it builds that container.Annoyances
Path Mapping
There are however, some annoyances with this setup. For example, there is a problem inherent to mounting the docker sock from the host into the container. It's that when you are running docker commands inside of the container and you are asking to bind-mount a volume you need to make sure you specify the source path of the bind mount in terms of the host's OS filesystem view. For this reason, the setup code for the devcontainer defines separate
HOST_WORKSPACE
andCONTAINER_WORKSPACE
environment variables that are used interchangeably in different contexts.Networking
Right now, the devcontainer is defined to use the Host networking stack. This way it has access to all other containers via
localhost
. All other containers that publish their ports to the host are visible to the devcontainer as usual vialocalhost
.I tried a different setup where the devcontainer was attached to the same docker network as the main application stack. This way it could access all the services using their host names like
http://web:8080
orhttp://app:4000
. That is pretty cool, but that setup is a bit hard to implement, because it requires defining the docker network outside of the docker compose, because in this scenario we have two different docker compose stacks that requires the same network. Doing that with docker compose syntax is possible, but then there is left an annoying problem of needing to pre-create the custom docker network outside of the docker-compose.A much simpler solution is to just use the host network, which should work fine, unless I saw some issues somewhere about poor MacOS support for using the host network inside of containers.
Devcontainer Config Editing Loop
Let's review how you can edit and contribute to the devcontainer. The devcontainer image is defined using the same Dockerfile that we use for the main app image. This Dockerfile became a multistage Dockerfile that has two stages - app and devcontainer. You can extend the devcontainer part of the Dockerfile to add some more dev-only dependencies like linters, formatters, shell configs, etc. The
devcontainer
stage extends theapp
stage, so it also automatically inherits all the dependencies from the app image such asffmpeg
,elixir
andRust
toolchains etc.You can also modify the configs in the
.devcontainer
directory including thedevcontainer.json
file where the vscode extensions and settings are defined. Then you can run the command> Dev Containers: Rebuild Container
to reload the VSCode and get all the updates installed.When VSCode is building the container it shows the notification on the right bottom part of the screen which you can click on to see the build logs. If the build fails VSCode will suggest you to re-open the workspace from your host, so you can edit the configs, fix the problem and restart the build again.
Note that there is another annoyance here that I faced while working on this. The build log doesn't show you the logs from the container when it starts. If something fails at that stage you'll need to go back to your host, and view the logs from the unhealthy container via
docker logs
or the Docker VSCode extension manually. This is bearable if you are running this setup on your own machine where you have access to host, but this is extremely painful in Github Codespaces where you don't.CI
We are now using our devcontainer definition on our CI to make sure our CI is also consistent with our dev environment. Note, however, that this slows down the CI a bit. The CI jobs now spend somewhere around 30 seconds to build the container before they start running all the checks and the build.
We can fix this by moving the container definition into a separate repository and publishing the pre-build images into
ghcr
ordockerhub
container image registry. But we'll leave this improvement for another day for now.Fully custom
devcontainer.json
There is also a way to define a fully custom
devcontainer.json
to override the repository config. You can find that in VSCode documentation as "Alternative repository configuration folders".