Skip to content

Latest commit

 

History

History
executable file
·
1216 lines (953 loc) · 36.3 KB

File metadata and controls

executable file
·
1216 lines (953 loc) · 36.3 KB

Environment Variables with Docker Compose

Complete Guide for Flutter DevContainers


Table of Contents

  1. Overview
  2. How .env Files Work
  3. Complete Lifecycle
  4. Variable Types & Availability
  5. Template Setup
  6. Configuration Examples
  7. Debugging & Troubleshooting
  8. Best Practices
  9. Common Pitfalls

Overview

⚙️ Centralized Configuration Architecture

Key Philosophy: The .env file is now the single source of truth for ALL project and user-specific configuration.

What This Means:

  • 🚫 Template files are NEVER modified - devcontainer.json, docker-compose.yml, etc. remain untouched
  • All customization via environment variables - container names, user settings, versions, ports
  • Perfect template reusability - same template works for all projects
  • Conflict-free updates - template improvements never conflict with your settings

What is a .env File?

A .env file is a simple text file that contains environment variables used by Docker Compose to configure your containers. In our architecture, it serves as the complete configuration center for your project. It allows you to:

  • ✅ Centralize ALL project-specific settings in one place
  • ✅ Separate configuration from template code
  • ✅ Use different values per project without modifying templates
  • ✅ Keep sensitive data out of version control
  • ✅ Make configuration explicit and readable
  • ✅ Customize container names, versions, and behavior per project

Why Use .env Files for Flutter DevContainers?

Problem Without .env:

# Hard-coded values everywhere
services:
  flutter-dev:
    container_name: ledgerlinc-dev    # ← Hard to reuse
    build:
      args:
        USER_UID: 1000                 # ← Hard-coded
        FLUTTER_VERSION: 3.24.0        # ← Hard-coded

Solution With .env:

# Flexible, reusable configuration
services:
  flutter-dev:
    container_name: ${PROJECT_NAME}-dev  # ← From .env
    build:
      args:
        USER_UID: ${USER_UID}            # ← From .env
        FLUTTER_VERSION: ${FLUTTER_VERSION}  # ← From .env

Benefits:

  • Change project name in one place
  • Different Flutter versions per project
  • Template can be copied and customized easily

How .env Files Work

Automatic Discovery

Docker Compose automatically looks for a .env file:

Project Directory/
├── .env                    ← Docker Compose finds this automatically
├── docker-compose.yml      ← Uses variables from .env
└── Dockerfile

Location Rules:

  1. Must be named exactly .env (not .env.txt or env)
  2. Must be in the same directory as docker-compose.yml
  3. Is read automatically by ALL docker-compose commands

Loading Process

┌─────────────────────────────────────────────────────────────┐
│  Developer runs: docker-compose up                          │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Docker Compose automatically reads .env file               │
│  Location: Same directory as docker-compose.yml             │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Loads all KEY=VALUE pairs into memory                      │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Parses docker-compose.yml                                  │
│  Replaces ${VARIABLE_NAME} with values from .env            │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Executes commands with resolved configuration              │
└─────────────────────────────────────────────────────────────┘

Variable Substitution

Before (in docker-compose.yml):

services:
  flutter-dev:
    container_name: ${PROJECT_NAME}-dev
    build:
      args:
        USER_UID: ${USER_UID}

What .env contains:

PROJECT_NAME=ledgerlinc
USER_UID=1000

After substitution (what Docker Compose uses):

services:
  flutter-dev:
    container_name: ledgerlinc-dev
    build:
      args:
        USER_UID: 1000

Complete Lifecycle

Step-by-Step Execution Flow

Step 1: VS Code Opens Project

User: Opens Dartwingers/ledgerlinc in VS Code
Action: Clicks "Reopen in Container"

Step 2: initializeCommand (Before Docker)

┌─────────────────────────────────────────────────────────────┐
│  Runs: start-adb-if-needed.sh                              │
│  When: On HOST (Windows/WSL2)                               │
│  Note: Does NOT use .env (runs before Docker Compose)      │
└─────────────────────────────────────────────────────────────┘

Step 3: Docker Compose Starts

┌─────────────────────────────────────────────────────────────┐
│  VS Code executes:                                          │
│  $ cd Dartwingers/ledgerlinc                                │
│  $ docker-compose -f docker-compose.yml up -d               │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Docker Compose reads:                                      │
│  1. ./docker-compose.yml                                    │
│  2. ./.env (automatically, if exists)                       │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Loads .env variables:                                      │
│  PROJECT_NAME=ledgerlinc                                    │
│  USER_NAME=developer                                        │
│  USER_UID=1000                                              │
│  USER_GID=1000                                              │
│  FLUTTER_VERSION=3.24.0                                     │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Substitutes all ${VARIABLES} in docker-compose.yml:        │
│  container_name: ledgerlinc-dev                             │
│  user: "1000:1000"                                          │
│  args: USER_UID=1000, USER_GID=1000, etc.                   │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
                    ┌─────┴─────┐
                    │ Image     │
                    │ exists?   │
                    └─────┬─────┘
                          │
        ┌─────────────────┼─────────────────┐
        │ NO                                │ YES
        ↓                                   ↓
┌───────────────────────┐         ┌────────────────────────┐
│ BUILD PHASE           │         │ Skip build             │
└───────┬───────────────┘         └────────┬───────────────┘
        │                                   │
        ↓                                   │
┌───────────────────────────────────────────┴─────────────────┐
│  Pass build args to Dockerfile:                             │
│  - USER_NAME=developer                                      │
│  - USER_UID=1000                                            │
│  - USER_GID=1000                                            │
│  - FLUTTER_VERSION=3.24.0                                   │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Dockerfile receives ARGs:                                  │
│                                                             │
│  ARG USER_UID=1000      # ← From .env via compose           │
│  ARG FLUTTER_VERSION    # ← From .env via compose           │
│                                                             │
│  RUN useradd -u ${USER_UID} ...                             │
│  RUN git clone --branch ${FLUTTER_VERSION} ...              │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Image built with configuration baked in                    │
│  ARG variables are now GONE (not in image)                  │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  RUN PHASE: Create and start container                      │
│  - Name: ledgerlinc-dev                                     │
│  - User: 1000:1000                                          │
│  - Environment: ADB_SERVER_SOCKET=tcp:shared-adb-server:5037│
│  - Network: dartnet                                         │
│  - Volumes: .:/workspace                                    │
└─────────────────────────┬───────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  Container running, ready for VS Code to attach             │
└─────────────────────────────────────────────────────────────┘

Step 4: Post-Start Commands

┌─────────────────────────────────────────────────────────────┐
│  VS Code attaches to container                              │
│  Runs: onCreateCommand, postStartCommand, postAttachCommand │
│  Note: .env values already applied, not re-read             │
└─────────────────────────────────────────────────────────────┘

Variable Types & Availability

Three Types of Variables

1. Build Arguments (ARG) - Build Time Only

Where: Dockerfile
When: During docker build
Lifetime: Gone after build completes

ARG USER_UID=1000
ARG FLUTTER_VERSION=3.24.0

# Available here during build
RUN useradd -u ${USER_UID} developer
RUN git clone --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git

# NOT available in running container!

Use for: Installing software, creating users, setting up environment

2. Environment Variables (ENV) - Runtime

Where: Dockerfile or docker-compose.yml
When: In running container
Lifetime: Available as long as container runs

In Dockerfile:

ENV FLUTTER_ROOT=/flutter
ENV PATH=$PATH:${FLUTTER_ROOT}/bin

# Available in running container

In docker-compose.yml:

services:
  flutter-dev:
    environment:
      - ADB_SERVER_SOCKET=tcp:shared-adb-server:5037
      - MY_CONFIG=${MY_VALUE}  # From .env

Use for: Configuration that apps need at runtime

3. Compose Variables - Compose Configuration

Where: docker-compose.yml
When: While parsing compose file
Lifetime: Used to configure container, not passed to container

services:
  flutter-dev:
    container_name: ${PROJECT_NAME}-dev  # ← Configures container name
    user: "${USER_UID}:${USER_GID}"      # ← Configures user

Availability Timeline

┌────────────────────────────────────────────────────────────────┐
│                    VARIABLE LIFECYCLE                          │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  .env file           ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●      │
│  (disk file)         Read once                          Done   │
│                      ↓                                         │
│                                                                │
│  Compose parsing     ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●      │
│  Variables           ${VAR} → value                      Done   │
│                      ↓                                         │
│                                                                │
│  Build Phase                                                   │
│  ARG variables       ●━━━━━━━━━━●                             │
│                      Available    Gone                         │
│                      ↓                                         │
│                                                                │
│  Container Running                                             │
│  ENV variables       ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━→ │
│                      Available forever                         │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Availability Matrix

Variable Type .env File docker-compose.yml Dockerfile Build Running Container
ARG ✅ Defined ✅ Passed as arg ✅ Available ❌ Gone
ENV (Dockerfile) ✅ Via ARG ✅ Via arg ✅ Available ✅ Available
ENV (compose) ✅ Defined ✅ Set directly ❌ N/A ✅ Available
Compose config ✅ Defined ✅ Substituted ❌ N/A ❌ Used for config only

Template Setup

Directory Structure

DevBench/FlutterBench/templates/flutter-devcontainer-template/
├── .devcontainer/
│   └── devcontainer.json
├── .vscode/
│   └── tasks.json
├── .env.example          # Template (checked into git)
├── .gitignore            # Excludes .env
├── docker-compose.yml    # Uses ${VARIABLES}
├── Dockerfile            # Receives ARGs
└── README.md

Template Files

.env.example

# ====================================
# Flutter DevContainer Configuration
# ====================================
# Copy this file to .env and customize for your project

# ====================================
# Project Configuration
# ====================================
PROJECT_NAME=myproject

# Container naming configuration
APP_CONTAINER_SUFFIX=app              # Results in: PROJECT_NAME-app
SERVICE_CONTAINER_SUFFIX=service      # Results in: PROJECT_NAME-service (or gateway for Dartwing)

# ====================================
# User Configuration
# ====================================
# These should match your WSL2 user for proper file permissions
USER_NAME=developer
USER_UID=1000
USER_GID=1000

# ====================================
# Flutter Configuration
# ====================================
FLUTTER_VERSION=3.24.0

# ====================================
# Container Resources (optional)
# ====================================
CONTAINER_MEMORY=4g
CONTAINER_CPUS=2

# ====================================
# ADB Configuration
# ====================================
ADB_SERVER_HOST=shared-adb-server
ADB_SERVER_PORT=5037

docker-compose.yml

version: '3.8'

services:
  flutter-dev:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        # Build-time arguments from .env
        USER_NAME: ${USER_NAME:-developer}
        USER_UID: ${USER_UID:-1000}
        USER_GID: ${USER_GID:-1000}
        FLUTTER_VERSION: ${FLUTTER_VERSION:-3.24.0}
    
    # Container configuration from .env
    container_name: ${PROJECT_NAME:-myproject}-dev
    
    # Run as user specified in .env
    user: "${USER_UID:-1000}:${USER_GID:-1000}"
    
    # Runtime environment variables
    environment:
      - ADB_SERVER_SOCKET=tcp:${ADB_SERVER_HOST:-shared-adb-server}:${ADB_SERVER_PORT:-5037}
      - FLUTTER_VERSION=${FLUTTER_VERSION:-3.24.0}
    
    # Network configuration
    networks:
      - dartnet
    
    # Volume mounts
    volumes:
      - .:/workspace
      # Persist pub cache per project
      - flutter-pub-cache-${PROJECT_NAME:-myproject}:/home/${USER_NAME:-developer}/.pub-cache
    
    # Resource limits (optional)
    deploy:
      resources:
        limits:
          memory: ${CONTAINER_MEMORY:-4g}
          cpus: '${CONTAINER_CPUS:-2}'
    
    command: sleep infinity
    
    restart: unless-stopped

# External network (managed by infrastructure)
networks:
  dartnet:
    external: true
    name: dartnet

# Named volume for pub cache
volumes:
  flutter-pub-cache-${PROJECT_NAME:-myproject}:
    name: flutter-pub-cache-${PROJECT_NAME:-myproject}

Dockerfile

FROM ubuntu:22.04

# ====================================
# Build Arguments (from .env via docker-compose)
# ====================================
ARG USER_NAME=developer
ARG USER_UID=1000
ARG USER_GID=1000
ARG FLUTTER_VERSION=3.24.0

# Prevent interactive prompts
ENV DEBIAN_FRONTEND=noninteractive

# ====================================
# System Dependencies
# ====================================
RUN apt-get update && apt-get install -y \
    # Essential build tools
    curl \
    git \
    unzip \
    xz-utils \
    zip \
    wget \
    # Flutter dependencies
    libglu1-mesa \
    # Java for Android development
    openjdk-17-jdk \
    # ADB client
    android-tools-adb \
    # User utilities
    sudo \
    # Helpful tools
    vim \
    nano \
    && rm -rf /var/lib/apt/lists/*

# ====================================
# Create User
# ====================================
# Create group and user with specific UID/GID for file permission matching
RUN groupadd -g ${USER_GID} ${USER_NAME} 2>/dev/null || true && \
    useradd -m -u ${USER_UID} -g ${USER_GID} -s /bin/bash ${USER_NAME} 2>/dev/null || true && \
    echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# ====================================
# Install Flutter (as user)
# ====================================
USER ${USER_NAME}
WORKDIR /home/${USER_NAME}

# Clone Flutter at specified version
ENV FLUTTER_ROOT=/home/${USER_NAME}/flutter
RUN git clone --depth 1 --branch ${FLUTTER_VERSION} \
    https://github.com/flutter/flutter.git ${FLUTTER_ROOT}

# Add Flutter to PATH
ENV PATH=$PATH:${FLUTTER_ROOT}/bin

# Pre-download Flutter dependencies and verify installation
RUN flutter precache --android && \
    flutter config --no-analytics && \
    flutter doctor

# ====================================
# Setup Workspace
# ====================================
WORKDIR /workspace

CMD ["bash"]

.gitignore

# Environment variables (contains user-specific and potentially sensitive info)
.env

# Flutter
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# Logs
*.log

# OS
.DS_Store
Thumbs.db

Configuration Examples

Example 1: Standard Configuration

Project: Dartwingers/ledgerlinc

.env

PROJECT_NAME=ledgerlinc
USER_NAME=developer
USER_UID=1000
USER_GID=1000
FLUTTER_VERSION=3.24.0

Result:

  • Container name: ledgerlinc-dev
  • User: developer (UID 1000, GID 1000)
  • Flutter: version 3.24.0
  • Pub cache: flutter-pub-cache-ledgerlinc

Example 2: Different Flutter Version

Project: Dartwingers/lablinc

.env

PROJECT_NAME=lablinc
USER_NAME=developer
USER_UID=1000
USER_GID=1000
FLUTTER_VERSION=3.19.0  # ← Older version for compatibility

Result:

  • Container name: lablinc-dev
  • Flutter: version 3.19.0 (different from ledgerlinc!)
  • Both projects run independently

Example 3: Custom User Configuration

Project: DavinciDesigner/flutter-app

.env

PROJECT_NAME=davincidesigner
USER_NAME=davinci  # ← Custom username
USER_UID=1500      # ← Custom UID
USER_GID=1500      # ← Custom GID
FLUTTER_VERSION=3.24.0

Result:

  • Container name: davincidesigner-dev
  • User: davinci (UID 1500, GID 1500)
  • Files created by container owned by UID 1500

Example 4: Resource Limits

Project: Large project needing more resources

.env

PROJECT_NAME=bigproject
USER_NAME=developer
USER_UID=1000
USER_GID=1000
FLUTTER_VERSION=3.24.0
CONTAINER_MEMORY=8g    # ← More memory
CONTAINER_CPUS=4       # ← More CPUs

Result:

  • Container limited to 8GB RAM and 4 CPUs
  • Better performance for large builds

Debugging & Troubleshooting

Check What Docker Compose Sees

# Navigate to project
cd Dartwingers/ledgerlinc

# Show resolved configuration (with all variables substituted)
docker-compose config

# Expected output shows resolved values:
# services:
#   flutter-dev:
#     container_name: ledgerlinc-dev  # ← ${PROJECT_NAME} replaced
#     user: "1000:1000"                # ← ${USER_UID}:${USER_GID} replaced
#     build:
#       args:
#         USER_UID: 1000                # ← ${USER_UID} replaced

Verify .env Is Being Read

Test 1: Check Variables

# Create test .env
cat > .env << EOF
PROJECT_NAME=testproject
TEST_VAR=hello
EOF

# View resolved config
docker-compose config | grep container_name
# Should show: container_name: testproject-dev

Test 2: Compare With and Without .env

# Without .env (uses defaults)
mv .env .env.backup
docker-compose config | grep container_name
# Shows: container_name: myproject-dev (default)

# With .env
mv .env.backup .env
docker-compose config | grep container_name
# Shows: container_name: ledgerlinc-dev (from .env)

Common Issues and Solutions

Issue 1: Variables Not Substituted

Symptom: Container name is literally ${PROJECT_NAME}-dev

Cause: .env file not found or wrong location

Solution:

# Check location
ls -la .env
ls -la docker-compose.yml
# Both should be in same directory

# Check file name
# Must be exactly ".env", not "env.txt" or ".env.local"

Issue 2: Wrong Values Used

Symptom: Container uses default values, not .env values

Cause: Syntax error in .env file

Solution:

# Check .env syntax
cat .env

# Common mistakes:
# ❌ PROJECT_NAME = ledgerlinc    # Spaces around =
# ❌ PROJECT_NAME="ledgerlinc"    # Quotes (become part of value)
# ❌ PROJECT NAME=ledgerlinc      # Space in key

# ✅ Correct:
# PROJECT_NAME=ledgerlinc

Issue 3: Variables Not Available in Container

Symptom: echo $MY_VAR in container shows nothing

Cause: Variable only used for compose config, not passed to container

Solution:

# To make available in container, add to environment:
services:
  flutter-dev:
    environment:
      - MY_VAR=${MY_VAR}  # Now available at runtime

Issue 4: Build Not Using New .env Values

Symptom: Changed .env but container still uses old values

Cause: Docker cached the build

Solution:

# Rebuild without cache
docker-compose build --no-cache

# Or force rebuild when starting
docker-compose up --build --force-recreate

Debug Commands

# Show all environment variables Docker Compose sees
docker-compose config --environment

# Show only specific service
docker-compose config --services

# Validate compose file
docker-compose config --quiet
# No output = valid, errors = invalid

# Show what variables are set in running container
docker-compose exec flutter-dev env

# Check .env file format
cat .env | grep -v '^#' | grep -v '^$'
# Should show only KEY=VALUE lines

Best Practices

1. Use .env.example for Templates

# Checked into git (safe, no secrets)
.env.example

# Not in git (user-specific, may contain secrets)
.env

In .gitignore:

# Never commit actual .env files
.env
.env.local
.env.*.local

2. Provide Defaults in docker-compose.yml

# Good: Has fallback if .env missing
USER_UID: ${USER_UID:-1000}

# Bad: Fails if .env missing
USER_UID: ${USER_UID}

Syntax: ${VARIABLE:-default}

  • If VARIABLE is set in .env: uses that value
  • If VARIABLE is not set: uses default

3. Document All Variables

# .env.example

# ====================================
# Project Configuration
# ====================================

# PROJECT_NAME: Used for container name and volume names
# Must be unique across all projects
# Example: ledgerlinc, lablinc, dartwing
PROJECT_NAME=myproject

# USER_UID: User ID inside container
# Should match your WSL2 user ID (run 'id -u' to check)
# Default: 1000
USER_UID=1000

4. Validate .env on Project Setup

Create a setup script:

scripts/manual-setup-project.sh

#!/bin/bash

if [ ! -f .env ]; then
    echo "⚠️  .env file not found!"
    echo "Creating from .env.example..."
    cp .env.example .env
    echo "✅ Created .env file"
    echo "📝 Please edit .env and set PROJECT_NAME"
    exit 1
fi

# Validate required variables
REQUIRED_VARS=("PROJECT_NAME" "USER_NAME" "USER_UID" "USER_GID")
MISSING=()

for VAR in "${REQUIRED_VARS[@]}"; do
    if ! grep -q "^${VAR}=" .env; then
        MISSING+=("$VAR")
    fi
done

if [ ${#MISSING[@]} -gt 0 ]; then
    echo "❌ Missing required variables in .env:"
    printf '   - %s\n' "${MISSING[@]}"
    exit 1
fi

echo "✅ .env file validated"

5. Use Descriptive Variable Names

# Good: Clear what it's for
FLUTTER_VERSION=3.24.0
ADB_SERVER_HOST=shared-adb-server
CONTAINER_MEMORY=4g

# Bad: Unclear
VERSION=3.24.0
HOST=server
MEM=4g

6. Group Related Variables

# ====================================
# Project Configuration
# ====================================
PROJECT_NAME=myproject

# ====================================
# User Configuration  
# ====================================
USER_NAME=developer
USER_UID=1000
USER_GID=1000

# ====================================
# Flutter Configuration
# ====================================
FLUTTER_VERSION=3.24.0

7. Test Configuration Before Committing

# Always test compose config
docker-compose config

# Test build
docker-compose build

# Test run
docker-compose up -d
docker-compose exec flutter-dev flutter --version
docker-compose down

Common Pitfalls

❌ Pitfall 1: Wrong File Location

# WRONG - .env in parent directory
Dartwingers/
├── .env                    # ← Won't be found!
└── ledgerlinc/
    └── docker-compose.yml

# CORRECT - .env next to docker-compose.yml
Dartwingers/
└── ledgerlinc/
    ├── .env                # ← Here!
    └── docker-compose.yml

❌ Pitfall 2: Quotes in Values

# WRONG - Quotes become part of value
PROJECT_NAME="ledgerlinc"   
# Result: container name is "ledgerlinc"-dev (with quotes!)

# CORRECT - No quotes
PROJECT_NAME=ledgerlinc

❌ Pitfall 3: Spaces Around Equals

# WRONG
PROJECT_NAME = ledgerlinc   # Spaces cause parsing errors

# CORRECT
PROJECT_NAME=ledgerlinc     # No spaces

❌ Pitfall 4: Expecting .env in Container

# .env is NOT copied into the container
# It's only read by Docker Compose on the HOST

# Won't work:
docker exec my-container cat .env  # File doesn't exist

❌ Pitfall 5: Using .env for Runtime Configuration

# WRONG - Trying to use .env for app config
# File: app.py
from dotenv import load_dotenv
load_dotenv()  # Won't find .env (not in container!)

# CORRECT - Use environment variables passed by compose
# File: docker-compose.yml
environment:
  - MY_CONFIG=${MY_VALUE}  # From .env, passed to container

# File: app.py
import os
config = os.environ.get('MY_CONFIG')  # Works!

❌ Pitfall 6: Forgetting to Rebuild After Changes

# Change .env
echo "FLUTTER_VERSION=3.19.0" >> .env

# Won't take effect automatically
docker-compose up  # Still uses old version!

# CORRECT - Rebuild
docker-compose up --build

❌ Pitfall 7: Committing .env to Git

# NEVER commit .env - it may contain:
# - User-specific paths
# - API keys
# - Passwords
# - Machine-specific configuration

# Always in .gitignore:
.env
.env.local
.env.*.local

# Only commit:
.env.example

❌ Pitfall 8: Multiline Values

# WRONG - Docker Compose doesn't support multiline in .env
MY_VALUE=line1
line2
line3

# CORRECT - Use quotes and \n or put in file and mount
MY_VALUE="line1\nline2\nline3"

# Or better - mount a file:
volumes:
  - ./config.txt:/app/config.txt

When Each File Is Used

Complete Timeline Summary

Step File Action .env Used?
1 .devcontainer/devcontainer.json initializeCommand runs ❌ No
2 .env Docker Compose starts ✅ Read automatically
3 docker-compose.yml Parse and substitute ✅ Variables replaced
4 Dockerfile Build image (if needed) ✅ Via build args
5 Container Start container ✅ Via environment vars
6 Container Running ❌ Values already applied

File Dependencies

.env.example (template in git)
    ↓
    Copy to
    ↓
.env (user's local config)
    ↓
    Read by
    ↓
docker-compose.yml (substitutes ${VARS})
    ↓
    Passes args to
    ↓
Dockerfile (receives ARGs, builds image)
    ↓
    Creates
    ↓
Container (runs with ENV vars)

Quick Reference

.env File Format

# Comments start with #
KEY=value
KEY_TWO=value_two

# No spaces around =
# No quotes needed
# One variable per line
# Empty lines ignored

Docker Compose Commands That Use .env

docker-compose config      # ✅ Uses .env
docker-compose build       # ✅ Uses .env
docker-compose up          # ✅ Uses .env
docker-compose down        # ✅ Uses .env
docker-compose restart     # ✅ Uses .env
docker-compose exec        # ✅ Container has ENV vars from .env

Variable Substitution Syntax

# Basic
${VARIABLE}

# With default
${VARIABLE:-default}

# Must be set (error if missing)
${VARIABLE?error message}

# Use value or empty
${VARIABLE-default}

Checking Your Setup

# 1. Verify .env exists
ls -la .env

# 2. Check .env syntax
cat .env | grep -v '^#' | grep -v '^$'

# 3. See what compose will use
docker-compose config

# 4. Test build
docker-compose build

# 5. Verify in running container
docker-compose up -d
docker-compose exec flutter-dev env | grep MY_VAR

Conclusion

Key Takeaways

Automatic: Docker Compose reads .env automatically from the same directory
Parse Time: Variables substituted before anything else happens
Build Args: Passed to Dockerfile during image build
Runtime ENV: Available in running containers
Per Project: Each project has its own .env for custom configuration
Git Safe: .env in .gitignore, .env.example in git

For Flutter DevContainer Templates

When copying the template to a new project:

  1. ✅ Copy .env.example to .env
  2. ✅ Edit PROJECT_NAME in .env
  3. ✅ Adjust other values if needed (USER_UID, FLUTTER_VERSION, etc.)
  4. ✅ Open in VS Code → Reopen in Container
  5. ✅ Docker Compose automatically uses .env
  6. ✅ Container built and configured correctly

That's it! The .env file makes each project independently configurable while using the same template structure.


Additional Resources

Template Location

DevBench/FlutterBench/templates/flutter-devcontainer-template/

Related Documentation

  • flutter-infrastructure-architecture.md - Overall architecture
  • vscode-tasks-snippets.md - Tasks and configuration snippets
  • path-pinning-verification.md - Infrastructure paths

Support

For issues with .env files in Flutter DevContainers, check:

  1. This guide's troubleshooting section
  2. Run docker-compose config to see resolved values
  3. Validate .env syntax (no spaces, no quotes)
  4. Ensure .env is in same directory as docker-compose.yml