# Contents

The versatility of containerized development environments lies not just in their isolation but also in maintaining consistency across various stages of software development. A significant aspect of this consistency is environment management, particularly the use of environmental variables. In this article, we'll explore the containerEnv and remoteEnv properties within the devcontainer.json file and how they play pivotal roles in shaping the development experience.

Understanding containerEnv and remoteEnv

Environmental variables are key-value pairs that can influence the behavior of processes and applications. The containerEnv and remoteEnv properties in devcontainer.json make managing these variables within your development containers both powerful and intuitive.

The containerEnv Property

The containerEnv property sets or overrides environment variables for the container. These variables are accessible to any process within the container, and they are static for the life of the container instance. Here is a simple example of how containerEnv might be used:

    "containerEnv": {
        "DATABASE_URL": "postgresql://user:password@db:5432/mydb",
        "NODE_ENV": "development"

In this configuration, DATABASE_URL and NODE_ENV are set for any process within the container. Any changes to these values would necessitate a container rebuild to take effect.

The remoteEnv Property

Conversely, remoteEnv sets or overrides environment variables for the devcontainer.json supporting services or tools — such as terminals or debuggers — and not for the container as a whole. This allows for more dynamic changes and per-tool customization, as variables can be updated without a full container rebuild.

    "remoteEnv": {
        "PATH": "${containerEnv:PATH}:/custom/bin",
        "JAVA_HOME": "/docker-java-home"

In this snippet, PATH is appended with a custom directory, and JAVA_HOME is set specifically for processes initiated by devcontainer tools.

Why containerEnv and remoteEnv Matter

The reason for having both of these properties is flexibility and scope. containerEnv is more global and less flexible, providing a consistent set of variables for all processes, beneficial for application runtime settings. On the other hand, remoteEnv is more granular and flexible, tailored for development-time settings that could vary per session or tool used.

Real-World Application Scenario

Imagine working on a Python web application with different requirements for development and production environments. Let's create a scenario with Flask:

# app.py

import os
from flask import Flask

app = Flask(__name__)

def hello():
    return f"Environment: {os.getenv('FLASK_ENV')}"

if __name__ == '__main__':

In development, you want FLASK_ENV set to development and FLASK_DEBUG enabled. Here's how you could configure containerEnv for these requirements:

    "containerEnv": {
        "FLASK_ENV": "development",
        "FLASK_DEBUG": "true"

Suppose you also frequently SSH into this container from various tools that require a specific PATH. Here's where remoteEnv becomes useful:

    "remoteEnv": {
        "PATH": "${containerEnv:PATH}:/home/myuser/.local/bin"

This remoteEnv setting adjusts the PATH for processes initiated by supporting tools, such as remote SSH tools, to include the .local/bin directory for the developer's user.

Best Practices

  • Use containerEnv for Application-Level Settings: Ideal for settings that should be consistent across all container sessions.

  • Use remoteEnv for Development Environment Customization: Best for development-specific settings that may vary or need to be changed more frequently.

  • Avoid Hard-Coded Secrets: Never store sensitive information like passwords or API keys directly. Use secrets management tools instead.

Wrapping Up

In the realm of containerized development, maintaining consistent behavior while allowing customization is essential. The containerEnv and remoteEnv properties in devcontainer.json provide the necessary mechanisms to manage your environment effectively. By understanding their distinct purposes and deploying them wisely, you can ensure an efficient, reproducible, and flexible development experience.