Elixir – How to setup devcontainer on VSCode

I decided to learn Elixir in 2025. So the first thing that I want to do is to create a devcontainer.
A default devcontainer for Elixir is not provided in VSCode. So I created my own simple devcontainer.

Sponsored links

Create necessary files

VSCode requires an entry point to start a devcontainer. Create the following two files.

  • .devcontainer/devcontainer.json
  • .devcontainer/Dockerfile

devcontainer.json is loaded first and the image is built according to the Dockerfile.

Sponsored links

Content of Dockerfile

We should first check whether an official Docker image exists. The official docker image can be found here.

Since the latest version was 1.18.0 as of 2024-12-30, the simplest setup looks as follows.

FROM elixir:1.18.0

However, the docker image doesn’t contain the user vscode and thus I got the following error message.

Error response from daemon: unable to find user vscode: no matching entries in passwd file
Error: failed to start containers: 8cf375eff806234374de4501a8d74ae05541c34048b15b7aceff19bc9852eb72

We can find a VSCode doc to create a non-root user here.

Let’s add the lines to our Dockerfile. The final content is as follows.

FROM elixir:1.18.0

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Create the user
RUN groupadd --gid $USER_GID $USERNAME \
    && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
    #
    # [Optional] Add sudo support. Omit if you don't need to install software after connecting.
    && apt-get update \
    && apt-get install -y sudo \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME \
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y

USER vscode

Excecute Dev Containers: Rebuild and Reopen in Container.
Note that Dev Containers: Reopen in Container command doesn’t rebuild the image. It means that the change is not reflected to the result.

Content of devcontainer.json

The next step is to create devcontainer.json. Since we want to use our own Dockerfile, we need to set "dockerfile": "Dockerfile".

{
    "name": "Elixir-dev",
    "build": {
        "dockerfile": "Dockerfile"
    },
    "runArgs": [
        "--cap-add=SYS_PTRACE",
        "--security-opt",
        "seccomp=unconfined"
    ],
    "customizations": {
        "vscode": {
            "settings": {
                "terminal.integrated.defaultProfile.linux": "bash"
            },
            "extensions": [
                "jakebecker.elixir-ls"
            ]
        }
    },
    "remoteUser": "vscode"
}

You can add your desired extensions in customizations/extensions.

Version check

Execute elixir --version whether it works well. If it shows the version, the container works well.

$ elixir --version
Erlang/OTP 27 [erts-15.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Elixir 1.18.0 (compiled with Erlang/OTP 27)

Hello World

Let’s create a file src/hello_world.ex and add the following content.

IO.puts("hello world")

Then, execute the following command.

$ elixir src/hello_world.ex 
hello world

hellow world was shown on the terminal.

Adding Phoenix dependencies

We need to add the following command to install Phoenix.

USER vscode

# Add the following

# Install Phoenix
EXPOSE 4000
ARG PHOENIX_VERSION="1.7.18"
RUN mix local.hex --force \
  && mix local.rebar --force \
  && mix archive.install --force hex phx_new ${PHOENIX_VERSION}

Since Phoenix uses port 4000 by default, we need to expose the port to check it from the host.

Elixir was installed for the user vscode. Therefore, Phoenix must be installed for the same user.

If you don’t want to use USER vscode, you can write the command in the following way instead. The command is executed for the specified user by su ${USERNAME} -c.

RUN su ${USERNAME} -c "mix local.hex --force \
  && mix local.rebar --force \
  && mix archive.install --force hex phx_new ${PHOENIX_VERSION}"

mix local.hex installs a tool called hex that is a package manager for Erlang.

mix local.rebar installs a Erlang build tool called rebar3.

mix archive.install hex phx_new installs hex and phx.new globally so that we can use them anywhere.

Start a server

We haven’t installed PostgreSQL which is used by Phoenix by default but we don’t need it for testing. So let’s create a new web project under web directory in the following way. --no-ecto option. Database related config is removed with this option.

mix phx.new web --no-ecto

We are ready to go!! Change the directory and start the server.

$ cd web
$ mix phx.server
Compiling 14 files (.ex)
Generated web app
[error] `inotify-tools` is needed to run `file_system` for your system, check https://github.com/rvoicilas/inotify-tools/wiki for more information about how to install it. If it's already installed but not be found, appoint executable file with `config.exs` or `FILESYSTEM_FSINOTIFY_EXECUTABLE_FILE` env.
[warning] Not able to start file_system worker, reason: {:error, :fs_inotify_bootstrap_error}
[warning] Could not start Phoenix live-reload because we cannot listen to the file system.
You don't need to worry! This is an optional feature used during development to
refresh your browser when you save files and it does not affect production.

[info] Running WebWeb.Endpoint with Bandit 1.6.1 at 127.0.0.1:4000 (http)
[info] Access WebWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...

Rebuilding...

Done in 670ms.
[info] GET /
[debug] Processing with WebWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 200 in 160ms

Open a browser and access localhost:4000. The setting is successful when you can see the following page.

Comments

Copied title and URL