This is a copy of the Substack article.
Find the original on https://bestiadev.substack.com/p/containerized-development-environment.
Article date: 2023-05-12T13:16:10.840Z

Containerized development environment for the Rust programming language

Simple, repeatable, coherent, savable, and isolated

Benefits

The benefits of having a development environment inside a Linux container are many:

1. Simple run without installation
The Linux container is just downloaded and run on any system (Linux, Windows WSL2, and MacOS). No need to learn how to install programs and configure the system.
The container has already installed all programs with configurations for basic Rust development.

2. Simple reset of the environment
If something goes wrong inside the container, it is easy to reset everything. Just delete the container and run a new fresh one.

3. Repetitive build of the container image
When a tool needs to be updated or upgraded, it is easy to change the script and repeat the container image build.
Then we save the image on Docker Hub and all other developers can just pull it and run it.

4. Extend the container image for specific purposes
When in need of some specific tools, it is easy to build a new container image from the basic image with added functionality.
The build script starts with the basic image, installs programs, and set configurations. It is repeatable, customizable, and easily distributable.

5. Coherent environment between developers
More developers can collaborate on the same project easily when they use the same developing environment.

6. Saving an exact point in the development
When a project is not developed for a long time, many things can change, OS version, compiler, other developer tools, libraries,...
It is difficult/impossible to recreate the same development environment after some time.
Because of containers, we can save the exact development environment when the project was actively developed.
Later we can then use exactly the same environment in the same shape and form.

7. Isolation
Programming is a complex matter. It needs many tools, programs, configurations, and libraries.
Sometimes we experiment and we can not trust just everybody.
Work inside the container is isolated from the host system, it does not have access to files and the whole network.
The dev. env. has a customizable firewall to whitelist only the network places that are strictly needed.
I admit, containers are not perfectly isolated, but it is hard to crack them and it is better than just leaving everything at the mercy of unknown programs.
Execution is isolated in the development time and runtime of the compiled programs.

8. Remote Git repository
Containers are ephemeral and are easily deleted. It is even recommended to delete them from time to time.
The source code must therefore be pushed to a remote git repository. So we can pull it from the repository when we have a fresh container.
By doing this, we are sure there are no files or information we forgot to add to the repository or instructions.

Repository

The repository with all the scripts and thorough information about this project are on GitHub and some instructions are also on YouTube.

//github.com/CRUSTDE-ContainerizedRustDevEnv/crustde_cnt_img_pod

//bestia.dev/youtube/crustde_cnt_img_pod.html

Basic Rust dev. container `crustde_cargo_img`

This container has all the tools that are needed for Rust programming. Except for the code editor, because developers’ preferences differ. The content of the containers is transparent and observable by reading the bash script.

Basic and advanced functionality:

  • slim Debian OS with curl, git, rsync, nano, ssh, build-essentials

  • non-privileged user/container with Podman

  • Rust tools, compiler, docs, source

  • much faster `mold` linker

  • cross-compile to Windows, musl, and Webassembly/Wasm

  • automation tasks cargo-auto

  • compilation artifact cache - sccache

These are most of the commands to build the container image (from the bash script):

from Debian:bullseye-slim
apt -y update
apt -y full-upgrade
apt install -y curl
apt install -y git
apt install -y rsync
apt install -y build-essential
apt install -y nano
apt install -y procps
apt install -y pkg-config
apt install -y libssl-dev
apt install -y postgresql-client
useradd -ms /bin/bash rustdevuser
mkdir -vp ~/rustprojects
mkdir -vp ~/.ssh
chmod 700 ~/.ssh
curl https://sh.rustup.rs -sSf | sh -s -- -yq
rustup component add rust-src
apt-get install -y mingw-w64
rustup target add x86_64-pc-windows-gnu
apt-get install -y musl-tools
rustup target add x86_64-unknown-linux-musl
copy crustde_cargo_img 'mold' '/usr/bin/'
mkdir /home/rustdevuser/.cargo/bin/mold
ln -s /usr/bin/mold /home/rustdevuser/.cargo/bin/mold/ld
cargo install cargo-auto
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
cargo install dev_bestia_cargo_completion
cargo install sccache
mkdir /home/rustdevuser/.ssh/crustde_pod_keys
copy crustde_cargo_img 'cargo_config.toml' '/home/rustdevuser/.cargo/config.toml'
apt -y autoremove
apt -y clean

VSCode editor container `crustde_vscode_img`

I prefer the VSCode editor. I build a container image that contains the server part of VSCode with my favorite extensions. Then the client GUI part of VSCode can connect over SSH to the VSCode server from any OS.

VSCode functionality:

  • VSCode server

  • code completion, syntax highlighting

  • basic spell-checker that works well with code and documents

  • Markdown linting and style checking

  • matching brackets to be identified with colors

  • XML Formatting

  • helper for dependencies in Cargo.toml

  • preview webpages with a local webserver

  • Database Management SQLTools

Some commands from the bash script:

from crustde_cargo_img
apt install -y openssh-server
mkdir -vp ~/.vscode-server/bin/commit-sha
mkdir -vp ~/.vscode-server/extensions
curl -L -s https://update.code.visualstudio.com/commit-sha/server-linux-64/stable --output x.tar.gz
tar --no-same-owner -xzv --strip-components=1 -C ~/.vscode-server/bin/commit-sha-f x.tar.gz
rm /tmp/vscode-server-linux-x64.tar.gz
code-server --install-extension streetsidesoftware.code-spell-checker
code-server --install-extension rust-lang.rust-analyzer
code-server --install-extension davidanson.vscode-markdownlint
code-server --install-extension 2gua.rainbow-brackets
code-server --install-extension dotjoshjohnson.xml
code-server --install-extension serayuzgur.crates
code-server --install-extension ms-vscode.live-server
code-server --install-extension mtxr.sqltools
code-server --ins
tall-extension mtxr.sqltools-driver-pg

Typescript container rust_ts_dev_vscode_img

For projects that need Typescript and Rust, I built a specialized container image with these commands:

from rust_ts_dev_vscode_img
apt install -y nodejs npm
npm install -g typescript

Create pod crustde_pod

The single container is not enough. For many development purposes, we need more than one container. We can group them in a `pod`. I use `podman` for that. In this phase, we must copy our secrets to the container like ssh keys for GitHub and the production web server. I use the squid container to limit the network connection only to whitelisted places. I start the ssh server, so I can connect VSCode over ssh.

add crustde_squid_cnt
cp etc_squid_squid.conf crustde_squid_cnt:/etc/squid/squid.conf
add crustde_vscode_cnt
cp ~/.ssh/crustde_pod_keys/etc_ssh_sshd_config.conf crustde_vscode_cnt:/etc/ssh/sshd_config
cp ~/.ssh/crustde_pod_keys/etc/ssh/ssh_host_ed25519_key crustde_vscode_cnt:/etc/ssh/ssh_host_ed25519_key
cp ~/.ssh/crustde_pod_keys/etc/ssh/ssh_host_ed25519_key.pub crustde_vscode_cnt:/etc/ssh/ssh_host_ed25519_key.pub
cp ~/.ssh/crustde_rustdevuser_ssh_1.pub crustde_vscode_cnt:~/.ssh/crustde_rustdevuser_ssh_1.pub
cp ~/.ssh/rustdevuser_rsa_key.pub crustde_vscode_cnt:~/.ssh/rustdevuser_rsa_key.pub
pod start crustde_pod
git config --global pull.rebase false
sh ~/.ssh/crustde_pod_keys/personal_keys_and_settings.sh
crustde_vscode_cnt service ssh restart

Virtual machines

Another way to create a development environment is by using virtual machines. That sounds great, but they are big and slow.

I know standard Linux OCI containers are not real virtual machines. They are also not perfectly sandboxed. There exist vulnerabilities that can be exploited to get access to the host machine. But that is hard to do and is patched day by day. There are also efforts to make containers more isolated like gVisor or Kata Containers. So the future is bright. Very importantly the development container runs in unprivileged mode with Podman.

Rust development is “not safe”

I am very interested in Rust because I think it can become very safe in many aspects, not just memory-safe. I think developing Rust on bare metal is "not safe" and should NOT be recommended.

In a normal Rust program, we use hundreds of open-source libraries, including the transient dependencies. We cannot trust all of them and it is impossible to check the code of all. So the starting point is that we are just lucky if none of the hundreds of dependency versions is malicious. Sooner or later it will happen.

Even before we compile our program the rust-analyzer will compile the dependency libraries to help us with code completion. Doing that will run the build.rs code that can do anything on our system. It can delete or replace any file we have access to. Very bad and not trustworthy!

The same is true when compiling and later when running compiled programs.

We also use many developer tools (programs); each can be problematic and have access to the whole system.

So, it is “not safe" to develop Rust programs on a bare metal machine. Using Linux OCI containers is much better.

open-source and free as a beer

My open-source projects are free as a beer (MIT license).
I just love programming.
But I need also to drink. If you find my projects and tutorials helpful, please buy me a beer by donating to my PayPal.
You know the price of a beer in your local bar ;-) So I can drink a free beer for your health :-)

Na zdravje! Alla salute! Prost! Nazdravlje! 🍻

//bestia.dev
//github.com/bestia-dev
//bestiadev.substack.com
//youtube.com/@bestia-dev-tutorials