Envbuilder

repository·main·Indexed 43 days ago

https://github.com/coder/envbuilder

A tool to build development environments from Dockerfiles or devcontainer.json files on Docker, Kubernetes, and OpenShift. It features layer caching via registries, support for Docker build secrets through ENVBUILDER_BUILD_SECRETS, and integration with Coder Agents for logging and telemetry. Currently in maintenance mode.

Tokens
17.1K
Snippets
33
Records
64
Agent score
54%

What's inside Envbuilder

  1. Overview of Envbuilder

    main

    Envbuilder is a tool used to build development environments from a Dockerfile or devcontainer.json on Docker, Kubernetes, and OpenShift. It allows developers to iterate on their environment configuration in a tight feedback loop by caching image layers with registries for fast builds.

    Note: envbuilder is currently in maintenance mode and no new features are planned.

  2. How Envbuilder determines the target non-root user

    main

    Envbuilder follows a specific hierarchy to decide which non-root user to use for the initialization command after dropping privileges:

    1. devcontainer.json: It first attempts to use the containerUser defined in your devcontainer.json.
    2. Dockerfile/Image: If containerUser is not specified, it looks up the last USER directive found in the specified Dockerfile or the base image.
    3. Fallback: If no user is identified via the methods above, Envbuilder falls back to root.

    Devcontainer Features Note: When installing Devcontainer Features, Envbuilder inserts a USER ${remoteUser} directive immediately after the feature installation directives. If remoteUser is not explicitly defined, it defaults to the containerUser.

  3. Build secrets vs. Volumes for secret management

    main

    When deciding how to provide sensitive information to your environment, choose based on the required lifecycle:

    FeatureBuild Secrets (--mount=type=secret)Volumes (-v or similar)
    PurposeInformation needed only during build (e.g., API keys for downloading dependencies).Information needed at runtime (e.g., user credentials, config files).
    PersistenceNot present in the final image or at runtime.Not included in the image, but available when the container runs.
    Envbuilder UsageSet via ENVBUILDER_BUILD_SECRETS.Mounted via standard container orchestration/runtime mechanisms.
  4. Understand Envbuilder's privilege model

    main
    Envbuilder is designed to run as root within its container to ensure it has the necessary permissions to build images. However, after the build process is complete, Envbuilder automatically drops root privileges. It then executes the command or script specified in ENVBUILDER_INIT_COMMAND or ENVBUILDER_INIT_SCRIPT as a non-root user.
  5. Quickstart with Docker

    main

    The fastest way to use Envbuilder is via its Docker container. The container performs the following steps:

    1. Clones a repository specified by ENVBUILDER_GIT_URL.
    2. Builds an image from a Dockerfile or devcontainer.json found in that repository.
    3. Runs the command specified in $ENVBUILDER_INIT_SCRIPT (or $ENVBUILDER_INIT_COMMAND) inside the newly built container.

    To clone a specific branch, append the ref to the URL in the format #refs/heads/branch-name (e.g., https://github.com/user/repo#refs/heads/my-branch).

    To persist demo data between runs, mount a local directory to /tmp/envbuilder.

    docker run -it --rm \
        -v /tmp/envbuilder:/workspaces \
        -e ENVBUILDER_GIT_URL=https://github.com/coder/envbuilder-starter-devcontainer \
        -e ENVBUILDER_INIT_SCRIPT=bash \
        ghcr.io/coder/envbuilder
  6. Use local files instead of a Git repository

    main

    If you do not have a remote Git repository or want to iterate quickly with local files, you can omit the ENVBUILDER_GIT_URL environment variable. Instead, mount your local directory containing your code to /workspaces/empty inside the Envbuilder container.

    By default, Envbuilder searches for a devcontainer.json or Dockerfile in:

    1. ${ENVBUILDER_WORKSPACE_FOLDER}
    2. ${ENVBUILDER_WORKSPACE_FOLDER}/.devcontainer
    # Create a sample Devcontainer and Dockerfile in the current directory
    printf '{"build": { "dockerfile": "Dockerfile"}}' > devcontainer.json
    printf 'FROM debian:bookworm\nRUN apt-get update && apt-get install -y cowsay' > Dockerfile
    
    # Run envbuilder with the current directory mounted into `/workspaces/empty`.
    docker run -it --rm -e ENVBUILDER_INIT_SCRIPT='bash' -v $PWD:/workspaces/empty ghcr.io/coder/envbuilder:latest
  7. Select a specific Git branch using ENVBUILDER_GIT_URL

    main

    To target a specific branch when using ENVBUILDER_GIT_URL, use a refs/heads/ reference in the URL. This allows Envbuilder to pull from a branch other than the default one.

    ENVBUILDER_GIT_URL=https://github.com/coder/envbuilder-starter-devcontainer/#refs/heads/my-feature-branch
  8. Use Local Directory for Layer Cache Experimentation

    main

    If you want to test layer caching without setting up a remote registry, you can use a local directory by setting the ENVBUILDER_LAYER_CACHE_DIR environment variable and mounting a local volume to that path.

    docker run -it --rm \
      -v /tmp/envbuilder-cache:/cache \
      -e ENVBUILDER_LAYER_CACHE_DIR=/cache \
      ...
  9. Configure the container init process with ENVBUILDER_SETUP_SCRIPT

    main

    You can use the ENVBUILDER_SETUP_SCRIPT environment variable to dynamically configure the user and the init command (PID 1) after the build process is complete.

    When the setup script runs, it should write configuration to the $ENVBUILDER_ENV file. The following variables can be defined in that file to shape the container's execution:

    • TARGET_USER: Specifies which user will execute the ENVBUILDER_INIT_COMMAND (e.g., root).
    • ENVBUILDER_INIT_COMMAND: The command to be executed by the TARGET_USER (e.g., /bin/bash or systemd).
    • ENVBUILDER_INIT_ARGS: Arguments passed to the ENVBUILDER_INIT_COMMAND (e.g., -c 'sleep infinity').
    # Example init.sh script
    if command -v systemd >/dev/null; then
      echo "Hey 👋 $TARGET_USER"
      echo ENVBUILDER_INIT_COMMAND=systemd >> $ENVBUILDER_ENV
    else
      echo ENVBUILDER_INIT_COMMAND=bash >> $ENVBUILDER_ENV
    fi
    
    # Running envbuilder with the setup script
    docker run -it --rm \
      -v ./:/some-dir \
      -e ENVBUILDER_SETUP_SCRIPT=/some-dir/init.sh \
      ghcr.io/coder/envbuilder
  10. Configure SSH Authentication for Git repositories

    main

    If ENVBUILDER_GIT_URL does not start with http:// or https://, envbuilder assumes SSH authentication. You can authenticate using one of the following three methods:

    1. Private Key File

    Set ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH to the path of an SSH private key mounted inside the container.

    docker run -it --rm \
      -v /tmp/envbuilder:/workspaces \
      -e ENVBUILDER_GIT_URL=git@example.com:path/to/private/repo.git \
      -e ENVBUILDER_INIT_SCRIPT=bash \
      -e ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH=/.ssh/id_rsa \
      -v /home/user/id_rsa:/.ssh/id_rsa \
      ghcr.io/coder/envbuilder

    2. Base64 Encoded Private Key

    Set ENVBUILDER_GIT_SSH_PRIVATE_KEY_BASE64 to the base64-encoded content of your private key.

    docker run -it --rm \
        -v /tmp/envbuilder:/workspaces \
        -e ENVBUILDER_GIT_URL=git@example.com:path/to/private/repo.git \
        -e ENVBUILDER_INIT_SCRIPT=bash \
        -e ENVBUILDER_GIT_SSH_PRIVATE_KEY_BASE64=$(base64 < ~/.ssh/id_ed25519) \
        ghcr.io/coder/envbuilder

    3. SSH Agent

    Mount your SSH agent socket and set the SSH_AUTH_SOCK environment variable.

    docker run -it --rm \
      -v /tmp/envbuilder:/workspaces \
      -e ENVBUILDER_GIT_URL=git@example.com:path/to/private/repo.git \
      -e ENVBUILDER_INIT_SCRIPT=bash \
      -e SSH_AUTH_SOCK=/tmp/ssh-auth-sock \
      -v $SSH_AUTH_SOCK:/tmp/ssh-auth-sock \
      ghcr.io/coder/envbuilder

    Note on Host Keys: By default, envbuilder accepts and logs all host keys. For strict host key checking, set SSH_KNOWN_HOSTS and mount a known_hosts file.

  11. How to verify that images are secret-free

    main

    To ensure that build secrets have not been accidentally persisted into your image layers or manifests, you can perform a manual inspection of the exported image.

    1. Pull the image and save it as a tar archive.
    2. Extract the archive.
    3. Use tar and grep to scan all files within the image for the secret strings.

    If the image is correctly built, the search for the secret values should return no results.

    # 1. Save the image
    docker pull localhost:5000/test-container:latest
    docker save -o test-container.tar localhost:5000/test-container
    
    # 2. Extract and scan
    mkdir -p test-container
    tar -xf test-container.tar -C test-container/
    cd test-container
    
    # Scan image layers for secrets
    find . -type f | xargs tar -xOf 2>/dev/null | strings | grep -rn "secret-foo"
    
    # Scan image manifests for secrets
    find . -type f | xargs -n1 grep -rnI 'secret-foo'
  12. Probe the Layer Cache for Pre-built Images

    main

    To verify if a pre-built image already exists in your cache without actually performing a build, set ENVBUILDER_GET_CACHED_IMAGE=1.

    This performs a "dry-run" build that consults ENVBUILDER_CACHE_REPO for each layer.

    • If any layer is missing from the cache, Envbuilder exits with an error.
    • If all layers are found, the image is emitted in the log output prefixed with ENVBUILDER_CACHED_IMAGE=....
    # Set this to check for existing layers instead of building
    ENVBUILDER_GET_CACHED_IMAGE=1