.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:
- Examined Docker error message carefully
- Key phrase: "Are you trying to mount a directory onto a file (or vice-versa)?"
-
Indicates type mismatch between source and destination
-
Checked host filesystem to verify source file type
- 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:
.envwas a directory on host filesystem, not a file.envdirectory was copied into image viaCOPY . .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:
- Traced creation of .env directory
- Not in git history
- Not intentionally created
-
Likely created by failed Docker mount operation
-
Understood propagation mechanism
- Failed mount created empty directory on host
COPY . .in Dockerfile copied directory into image-
Subsequent mount attempts failed due to type mismatch
-
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:
- Initial Creation: A previous failed Docker mount operation created
.envas an empty directory on the host filesystem - Dockerfile COPY: The
COPY . .command in the Dockerfile copied this directory into the Docker image - Mount Conflict: When docker-compose attempted to mount
.env.dev(file) to/app/.env, Docker detected/app/.envalready existed as a directory in the image - 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.dockerignorecopies everything, including problematic directories- Build cache persists the problem across rebuilds unless
--no-cacheis 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¶
- Created
.dockerignorefile in project root
Added comprehensive patterns to exclude all .env variants except examples.
- Updated
.gitignoreto explicitly exclude.env/directory
Prevents version control issues and team propagation.
- Enhanced
make cleanwith .env directory removal and cache pruning
Automatic cleanup of problematic artifacts.
- Created
make dev-rebuildandmake test-rebuildwith built-in safety checks
Safe one-command rebuilds with all protections.
- Tested complete workflow from clean state to running application
Verified all four layers work together correctly.
- 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¶
- Complete cleanup
Result: ✅ All containers stopped, images removed, .env directory removed
- Verify .env directory doesn't exist
Result: ✅ Directory successfully removed
- Rebuild from scratch
Result: ✅ Clean build completed, no .env in image
- 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
- Start and verify application
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-cachebuilds work correctly - ✅ No regressions in application behavior
- ✅ All test suites still pass
Lessons Learned¶
Technical Insights¶
- Docker mount type strictness
Docker strictly enforces file-to-file and directory-to-directory mount matching. No exceptions, even for empty directories.
- Failed mounts create artifacts
Failed Docker operations can create empty directories as filesystem artifacts that persist and cause future problems.
- Build cache persistence
Docker build cache can preserve problems across builds. --no-cache is sometimes necessary to clear cached issues.
.dockerignoreis essential
Critical for controlling what enters Docker images. Should be created before first build, not after encountering problems.
Process Improvements¶
- Multi-layer protection approach
Single-point solutions fail. Four independent layers ensure robustness: .dockerignore, .gitignore, make clean, rebuild commands.
- 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.
- Verify assumptions with filesystem checks
Using ls -ld revealed the directory vs file distinction immediately. Check actual state, don't assume.
- 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
.envas a directory - ✅ Always use
.env.dev,.env.test,.env.cifor environment-specific configs - ✅ Use
make cleanbefore major rebuilds - ✅ Use
make dev-rebuildwhen you need a completely fresh build - ✅ Check
.dockerignoreis present before building images - ✅ Run
ls -ld .env*to verify file types if issues occur
How This Fix Prevents Future Issues:
.dockerignore: Even if.envdirectory is created, it won't be copied into imagesmake clean: Automatically removes the problematic directorymake dev-rebuild: Safely rebuilds from scratch with all safety checks.gitignore: Prevents committing the issue to version control
Future Improvements¶
Short-Term Actions¶
- 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
- Document in onboarding guide
Timeline: Complete
Owner: Done - see troubleshooting docs
Long-Term Improvements¶
- Automated environment validation
Add health checks that verify correct file types for all environment files before builds.
- Pre-commit hooks
Detect and prevent .env directories from being staged to version control.
- 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:
- Docker Setup - Docker configuration and best practices
External Resources:
- Docker .dockerignore documentation - Official .dockerignore guide
- Docker volumes documentation - Docker volume mounting
- Docker build cache - Understanding build cache behavior
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