Skip to content

.env Directory Docker Mount Issue

During the Dashtam infrastructure migration, Docker volume mounts failed when attempting to mount .env.dev as a file to /app/.env. Investigation revealed that .env existed as an empty directory on the host machine, created by a previous failed Docker mount operation. When COPY . . copied this directory into the image, subsequent file mounts failed with "not a directory" errors.

The solution implemented a comprehensive four-layer protection system: (1) .dockerignore to prevent copying .env into images, (2) enhanced .gitignore to exclude .env directories, (3) updated make clean to remove problematic directories, and (4) new dev-rebuild/test-rebuild commands with built-in safety checks. This ensures the issue cannot recur.

Duration: ~30 minutes | Impact: Blocked all Docker startup | Resolution: Complete with automated prevention

Initial Problem

Symptoms

Environment: Docker Compose (development)

Error response from daemon: failed to create task for container: failed to create shim task:
OCI runtime create failed: runc create failed: unable to start container process:
error during container init: error mounting "/host_mnt/Users/faiyazhaider/Dashtam/.env.dev"
to rootfs at "/app/.env": mount src=/host_mnt/Users/faiyazhaider/Dashtam/.env.dev,
dst=/app/.env, dstFd=/proc/thread-self/fd/8, flags=0x5000: not a directory: unknown:
Are you trying to mount a directory onto a file (or vice-versa)?

Affected Commands: make dev-up, docker compose up

Expected Behavior

Docker should mount .env.dev file from host to /app/.env inside the container, providing environment variables for the application.

Actual Behavior

Docker refused to mount .env.dev file because /app/.env already existed as a directory in the Docker image, causing mount type mismatch error.

Impact

  • Severity: Critical
  • Affected Components: All Docker containers, development environment
  • User Impact: Complete inability to start development environment, blocked all development work

Investigation Steps

Step 1: Error Analysis

Hypothesis: Docker mount configuration might be incorrect.

Investigation:

  1. Examined Docker error message carefully
  2. Key phrase: "Are you trying to mount a directory onto a file (or vice-versa)?"
  3. Indicates type mismatch between source and destination

  4. Checked host filesystem to verify source file type

ls -ld .env
# Output: drwxr-xr-x  2 user  staff  64 Oct  1 10:00 .env/
  1. Checked Docker image to verify destination type
docker run --rm --entrypoint sh dashtam-app -c "ls -ld /app/.env"
# Output: drwxr-xr-x  2 root  root  64 Oct  1 10:00 /app/.env/

Findings:

  • .env was a directory on host filesystem, not a file
  • .env directory was copied into image via COPY . . in Dockerfile
  • Mount failure caused by directory-to-file mismatch

Result: ✅ Issue found - .env exists as directory preventing file mount

Step 2: Root Cause Identification

Hypothesis: Directory was created by previous failed Docker operation and propagated into image.

Investigation:

  1. Traced creation of .env directory
  2. Not in git history
  3. Not intentionally created
  4. Likely created by failed Docker mount operation

  5. Understood propagation mechanism

  6. Failed mount created empty directory on host
  7. COPY . . in Dockerfile copied directory into image
  8. Subsequent mount attempts failed due to type mismatch

  9. Confirmed hypothesis by testing fix

# Remove directory from host
rm -rf .env

# Rebuild image with --no-cache
docker-compose build --no-cache

# Try mount again
docker-compose up
# Success!

Findings:

  • Removing directory and rebuilding resolved the immediate issue
  • Problem would recur without systematic prevention
  • Need multi-layer protection to prevent recurrence

Result: ✅ Root cause confirmed - prevention system needed

Root Cause Analysis

Primary Cause

Problem: .env existed as directory instead of file, preventing Docker volume mount

Chain of Events:

  1. Initial Creation: A previous failed Docker mount operation created .env as an empty directory on the host filesystem
  2. Dockerfile COPY: The COPY . . command in the Dockerfile copied this directory into the Docker image
  3. Mount Conflict: When docker-compose attempted to mount .env.dev (file) to /app/.env, Docker detected /app/.env already existed as a directory in the image
  4. Mount Failure: Docker correctly refused the mount due to type mismatch (file → directory)

Why This Happens:

  • Docker mounts are strict about type matching (file-to-file, dir-to-dir)
  • Failed mount operations can create empty directories as filesystem artifacts
  • COPY . . without .dockerignore copies everything, including problematic directories
  • Build cache persists the problem across rebuilds unless --no-cache is used

Impact:

Complete failure to start any Docker containers using the affected image. No workaround available without fixing the root cause.

Contributing Factors

Factor 1: Missing .dockerignore

No .dockerignore file existed to prevent copying .env files/directories into Docker images during the build process.

Factor 2: Docker Build Cache

Build cache preserved the problematic directory across builds, masking the issue until a fresh --no-cache build was performed.

Solution Implementation

Approach

Implemented a comprehensive four-layer protection system to prevent .env directory issues and ensure safe rebuilds. Each layer provides independent protection, creating defense in depth.

Changes Made

Change 1: .dockerignore File

Before:

No .dockerignore file existed.

After:

# Environment files - NEVER copy these into images
# They should be mounted at runtime
.env
.env.*
!.env.*.example
.env.backup

Rationale:

Ensures that even if .env exists (as file or directory), it will NEVER be copied into the Docker image during COPY . .. This is the primary line of defense.

Change 2: .gitignore Enhancement

Before:

Basic .env exclusion.

After:

# Environment files (all variants except examples)
.env
.env/          # ← Explicitly excludes .env directory
.env.*
!.env.*.example

Rationale:

Prevents accidentally committing problematic .env directories to version control, which could spread the problem to other developers.

Change 3: Enhanced make clean Command

Before:

Basic cleanup without .env directory removal.

After:

clean:
    @echo "🧹 Cleaning up ALL environments..."
    @docker-compose -f docker-compose.dev.yml down -v --remove-orphans 2>/dev/null || true
    @docker-compose -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true
    @docker rmi dashtam-dev-app dashtam-dev-callback 2>/dev/null || true
    @docker rmi dashtam-test-app dashtam-test-callback 2>/dev/null || true
    @echo "  → Removing problematic .env directory (if exists)..."
    @if [ -d ".env" ]; then rm -rf .env && echo "    ✓ Removed .env directory"; fi
    @echo "  → Pruning Docker build cache..."
    @docker builder prune -f 2>/dev/null || true
    @echo "✅ Cleanup complete!"

Rationale:

Automatically detects and removes the problematic .env directory during cleanup, plus prunes build cache to remove any cached layers containing it.

Change 4: New dev-rebuild and test-rebuild Commands

Before:

No dedicated rebuild commands.

After:

dev-rebuild:
    @echo "🔄 Rebuilding DEVELOPMENT images from scratch..."
    @echo "  → Removing problematic .env directory (if exists)..."
    @if [ -d ".env" ]; then rm -rf .env && echo "    ✓ Removed .env directory"; fi
    @echo "  → Stopping containers..."
    @docker-compose -f docker-compose.dev.yml down 2>/dev/null || true
    @echo "  → Removing old images..."
    @docker rmi dashtam-dev-app dashtam-dev-callback 2>/dev/null || true
    @echo "  → Building with --no-cache..."
    @docker-compose -f docker-compose.dev.yml build --no-cache
    @echo "✅ Development images rebuilt from scratch"

Rationale:

Provides a safe, idempotent command to rebuild from scratch when needed, with all safety checks built-in. Developers don't need to remember the cleanup steps.

Implementation Steps

  1. Created .dockerignore file in project root

Added comprehensive patterns to exclude all .env variants except examples.

  1. Updated .gitignore to explicitly exclude .env/ directory

Prevents version control issues and team propagation.

  1. Enhanced make clean with .env directory removal and cache pruning

Automatic cleanup of problematic artifacts.

  1. Created make dev-rebuild and make test-rebuild with built-in safety checks

Safe one-command rebuilds with all protections.

  1. Tested complete workflow from clean state to running application

Verified all four layers work together correctly.

  1. Documented workflow commands for team reference

Added usage examples to Makefile and documentation.

Verification

Test Results

Before Fix:

Error: OCI runtime create failed
All Docker containers failed to start
Development environment completely blocked
Manual workarounds unsuccessful

After Fix:

 All containers start successfully
 .env files mount correctly  
 Application runs without errors
 Rebuild commands work reliably

Verification Steps

  1. Complete cleanup
make clean

Result: ✅ All containers stopped, images removed, .env directory removed

  1. Verify .env directory doesn't exist
ls -ld .env 2>&1
# Output: "No such file or directory"

Result: ✅ Directory successfully removed

  1. Rebuild from scratch
make dev-rebuild

Result: ✅ Clean build completed, no .env in image

  1. Verify .env is not in the image
docker run --rm --entrypoint sh dashtam-app -c "ls -la /app/ | grep '\.env'"
# Should only show .env.dev.example and .env.test.example

Result: ✅ Only example files present, no .env directory

  1. Start and verify application
make dev-up
sleep 10
curl -sk https://localhost:8000/health

Result: ✅ Application running, health check passing

Regression Testing

Verified that all existing functionality remained intact:

  • ✅ Environment variable loading works correctly
  • ✅ All make commands function as expected
  • ✅ Docker builds complete successfully with cache
  • --no-cache builds work correctly
  • ✅ No regressions in application behavior
  • ✅ All test suites still pass

Lessons Learned

Technical Insights

  1. Docker mount type strictness

Docker strictly enforces file-to-file and directory-to-directory mount matching. No exceptions, even for empty directories.

  1. Failed mounts create artifacts

Failed Docker operations can create empty directories as filesystem artifacts that persist and cause future problems.

  1. Build cache persistence

Docker build cache can preserve problems across builds. --no-cache is sometimes necessary to clear cached issues.

  1. .dockerignore is essential

Critical for controlling what enters Docker images. Should be created before first build, not after encountering problems.

Process Improvements

  1. Multi-layer protection approach

Single-point solutions fail. Four independent layers ensure robustness: .dockerignore, .gitignore, make clean, rebuild commands.

  1. Read error messages carefully

"Are you trying to mount a directory onto a file?" pointed directly to the issue. Don't skip or skim error details.

  1. Verify assumptions with filesystem checks

Using ls -ld revealed the directory vs file distinction immediately. Check actual state, don't assume.

  1. Build safety into workflows

Don't rely on developers remembering cleanup steps. Build safety checks into make commands.

Best Practices

Prevention Checklist:

  • Never manually create .env as a directory
  • Always use .env.dev, .env.test, .env.ci for environment-specific configs
  • ✅ Use make clean before major rebuilds
  • ✅ Use make dev-rebuild when you need a completely fresh build
  • ✅ Check .dockerignore is present before building images
  • ✅ Run ls -ld .env* to verify file types if issues occur

How This Fix Prevents Future Issues:

  1. .dockerignore: Even if .env directory is created, it won't be copied into images
  2. make clean: Automatically removes the problematic directory
  3. make dev-rebuild: Safely rebuilds from scratch with all safety checks
  4. .gitignore: Prevents committing the issue to version control

Future Improvements

Short-Term Actions

  1. Add pre-build validation script

Timeline: Next sprint

Owner: DevOps team

Script to check for common issues before building:

# scripts/pre-build-check.sh
if [ -d ".env" ]; then
  echo "ERROR: .env is a directory. Run 'make clean' first."
  exit 1
fi
  1. Document in onboarding guide

Timeline: Complete

Owner: Done - see troubleshooting docs

Long-Term Improvements

  1. Automated environment validation

Add health checks that verify correct file types for all environment files before builds.

  1. Pre-commit hooks

Detect and prevent .env directories from being staged to version control.

  1. CI/CD validation

Add pipeline step to verify .dockerignore is present and contains required patterns.

Monitoring & Prevention

No ongoing monitoring needed. The four-layer protection system ensures the issue cannot recur:

  • Layer 1 (.dockerignore) prevents copying
  • Layer 2 (.gitignore) prevents committing
  • Layer 3 (make clean) removes artifacts
  • Layer 4 (rebuild commands) provides safe workflows

References

Related Documentation:

External Resources:

Related Issues:

  • Infrastructure migration (October 2025)
  • Docker Compose configuration updates

Document Information

Template: troubleshooting-template.md Created: 2025-10-01 Last Updated: 2025-10-20