Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Dev Containers: Reproducible Development Environments

By Kokil Thapa | Last reviewed: September 2026

Dev Containers: Reproducible Development Environments solve a problem every team hits eventually. One developer runs PHP 8.3, another still has 8.1, and staging uses 8.4. A Laravel app works locally but fails after deploy because extensions or Node versions differ. I've seen this on production Laravel applications and on legal-tech portals where document uploads depend on specific PHP modules. Dev Containers wrap your project in a defined Docker image and connect your editor to it, so everyone codes inside the same environment. If you already use Docker Compose for local Laravel development, Dev Containers add the missing editor layer that makes onboarding fast.

What are Dev Containers and how do they create reproducible development environments?

Dev Containers are a specification maintained at containers.dev. The spec defines how an editor opens a project inside a Docker container instead of on the host OS. Your source code lives in a mounted volume. The container holds the runtime: PHP, Composer, Node.js, database clients, and whatever else the project needs.

The core file is .devcontainer/devcontainer.json. It tells the editor which image to use, which ports to forward, which VS Code extensions to install inside the container, and optional lifecycle scripts. Because that file lives in Git, every clone gets the same definition. Change PHP from 8.3 to 8.4 in one commit, and the whole team picks it up on rebuild.

Dev Containers ArchitectureHost MachineVS Code / IDEDev Containers extDocker EngineBuild and runProject FilesGit bind mountDev ContainerPHP 8.3 + ComposerNode.js 26 LTSMySQL client toolsServicesMySQL 9.7Redis 8.10Editor attaches to container — host OS version no longer matters
Dev Containers: reproducible development environments connect your editor to a Docker container with fixed PHP, Node, and tool versions.

Reproducibility comes from three layers working together. The Dockerfile or base image pins the OS and packages. Docker Compose can add sidecar services like MySQL or Redis. The devcontainer.json file wires ports, extensions, and post-create commands. Together they replace the README step that says "install PHP, then run these five commands and hope nothing conflicts."

For teams shipping Laravel applications in Nepal or abroad, this matters because hardware varies widely. A developer on Windows with WSL, another on Ubuntu 24, and a contractor on macOS can all open the same container. The app sees identical PHP extensions and identical Composer dependencies.

How do you set up Dev Containers for a Laravel project?

Start with Docker Desktop or Docker Engine on Linux. Install the Dev Containers extension in VS Code. Microsoft documents the workflow at code.visualstudio.com/docs/devcontainers. Create a .devcontainer folder at your project root.

Step 1: Create devcontainer.json

For a Laravel 13 project on PHP 8.3, a practical starting point looks like this:

{
  "name": "Laravel Dev",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/var/www/html",
  "forwardPorts": [8000, 5173, 3306],
  "postCreateCommand": "composer install && cp .env.example .env && php artisan key:generate",
  "customizations": {
    "vscode": {
      "extensions": [
        "bmewburn.vscode-intelephense-client",
        "onecentlin.laravel-blade",
        "dbaeumer.vscode-eslint"
      ],
      "settings": {
        "terminal.integrated.defaultProfile.linux": "bash"
      }
    }
  },
  "remoteUser": "www-data"
}

Step 2: Add Docker Compose for services

Dev Containers work well with a Compose file that defines both the app container and databases. This pattern mirrors what I use in multi-container local development setups:

services:
  app:
    build:
      context: .
      dockerfile: .devcontainer/Dockerfile
    volumes:
      - ..:/var/www/html
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: laravel
    volumes:
      - mysql-data:/var/lib/mysql

  redis:
    image: redis:8.10

volumes:
  mysql-data:

Step 3: Write a focused Dockerfile

Keep the Dockerfile minimal. Install only what Laravel needs:

FROM php:8.3-cli-bookworm

RUN apt-get update && apt-get install -y \
    git unzip libzip-dev libpng-dev \
    && docker-php-ext-install pdo_mysql zip gd bcmath \
    && curl -fsSL https://deb.nodesource.com/setup_26.x | bash - \
    && apt-get install -y nodejs \
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

WORKDIR /var/www/html

Step 4: Reopen in container

Open the project in VS Code. Run the command palette action Dev Containers: Reopen in Container. The first build takes several minutes. Later opens are faster because Docker caches layers. Run php artisan serve --host=0.0.0.0 inside the integrated terminal. Port 8000 forwards to your host browser automatically.

Dev Container Setup FlowClone RepoGit pull mainOpen VS CodeInstall extensionBuild ImageDocker layersAttach EditorRemote windowpostCreateCommand runscomposer install, migrate, npm installTeam codes in identical environmentSame PHP, Node, extensions, and DB services
Setting up Dev Containers for Laravel: clone, build, run lifecycle scripts, then develop inside a shared container environment.
  1. Commit .devcontainer/, Dockerfile, and Compose file to Git.
  2. Document any required host env vars in .env.example.
  3. Add a postStartCommand if queues or Vite must restart on every open.
  4. Pin image tags instead of using bare latest for databases.
  5. Test the flow on a clean machine before onboarding juniors or contractors.

On booking systems like Adventure Third Pole Trek, where Laravel and Livewire share Redis and MySQL, a Dev Container removes the "works on my laptop" friction during feature sprints. New contributors run one command instead of a two-page setup guide.

What is the difference between Dev Containers and Docker Compose alone?

Docker Compose orchestrates containers. Dev Containers add editor integration, automatic extension installation, port forwarding UI, and lifecycle hooks tied to your IDE session. Compose alone still leaves each developer responsible for connecting their editor, installing Intelephense locally, and matching PHP binaries on the host.

ApproachReproducibilityEditor integrationOnboarding timeBest for
Native host installLow — drift is constantFull local speedHours to daysSolo devs on one OS
Vagrant VMsHigh — full VM snapshotSSH + remote pluginsModerate — heavy imagesLegacy PHP stacks, non-Docker teams
Docker Compose onlyHigh for servicesManual — exec into containerModerateCI pipelines, headless workflows
Dev ContainersHigh — image + editor config in GitNative — terminal, debugger, extensions inside containerLow after first buildTeams on mixed OS, Laravel/Node monorepos

Compose remains the right tool when you only need docker compose up for integration tests in CI. Dev Containers shine when humans need to write code daily inside that same stack. Many projects use both: Compose defines services, and devcontainer.json points at the same file.

Compared to Vagrant, Dev Containers start faster and use less RAM. A full Ubuntu VM for custom software projects can consume 2 GB before your app runs. A PHP Dev Container often sits under 512 MB for the app service alone. The trade-off is that containers share the host kernel, so kernel-level differences between macOS and Linux still require testing in CI.

Choosing a Reproducible Dev SetupSolo dev, one machine?Native OKDev ContainerYesMixed OS team?Dev ContainersRecommended pathLegacy non-DockerConsider VagrantCI-only headlessDocker ComposeDev Containers win for cross-platform teamsCompose alone lacks editor lifecycle integration
Decision guide: when Dev Containers beat native installs, Vagrant, and Docker Compose alone for reproducible team environments.

Which VS Code extensions and tools do you need for Dev Containers?

Install the Dev Containers extension (ID: ms-vscode-remote.remote-containers). On Apple Silicon or Windows, Docker Desktop must be running before you reopen in container. Linux users can use Docker Engine directly, which aligns well with Linux server administration workflows many Nepal teams already follow.

List extensions inside devcontainer.json, not on each developer's host. That way Intelephense, ESLint, and Prettier versions stay aligned. Common picks for Laravel stacks:

  • Intelephense — PHP autocompletion using container PHP paths
  • Laravel Blade Snippets — template support inside the remote workspace
  • ESLint + Prettier — frontend linting for Vite 8.x asset pipelines
  • Docker extension — inspect Compose services from the sidebar
  • GitLens — blame and history without leaving the remote window

GitHub Codespaces implements the same Dev Containers spec in the cloud. Push your .devcontainer folder, and a teammate opens a browser-based VS Code session with identical tooling. Useful for contractors who refuse to install Docker locally. Costs apply for compute minutes, so local Dev Containers remain cheaper for daily use.

JetBrains IDEs support Dev Containers through remote development gateways. VS Code remains the most documented path. The Docker Compose documentation helps when you debug service networking independent of the editor layer.

How do you troubleshoot common Dev Containers problems?

Most failures fall into a short list. I've hit each of these during production-adjacent development on Laravel upgrades and on database migrations in team environments.

Permission errors on storage and cache

Laravel's storage/ and bootstrap/cache/ directories need write access. If the container runs as root but your host user is UID 1000, files created inside the container may be root-owned on the host. Fix it by matching remoteUser or adding a post-create chown:

"postCreateCommand": "sudo chown -R www-data:www-data storage bootstrap/cache"

Slow file sync on macOS and Windows

Bind mounts from Docker Desktop on non-Linux hosts can make vendor/ scans painfully slow. Options include copying dependencies into a named volume, using Mutagen sync, or developing on Linux natively. For large WooCommerce or Magento trees, expect noticeable lag unless you exclude node_modules and vendor from the bind mount.

Port forwarding conflicts

If port 3306 is already used by a local MySQL install, change the host side mapping in Compose:

ports:
  - "3307:3306"

Update your .env DB_PORT to match. VS Code shows forwarded ports in the Ports panel. You can label them for clarity.

Stale containers after config changes

Editing Dockerfile or Compose without rebuilding leaves you in an old environment. Run Dev Containers: Rebuild Container after changing base images or PHP extensions. This is the Dev Containers equivalent of opcache stale code after deploy — the fix is always a rebuild, not guessing.

Dev Container Troubleshooting MapPermission deniedFix remoteUser / chown storageMatch UID on Linux hostsSee Laravel storage pathsSlow file I/OExclude vendor from bindUse named Docker volumesPrefer Linux nativelyPort already in useRemap host ports in ComposeUpdate .env DB_PORTCheck VS Code Ports tabWrong PHP versionRebuild container imageVerify Dockerfile base tagPin Composer platform PHP
Four frequent Dev Containers failure modes and the fixes teams apply for reproducible Laravel development environments.

For deeper container debugging, read how to debug a running container and rootless containers for security. Scan custom images with guidance from container image scanning with Trivy before sharing internally.

Validate JSON configs with the JSON formatter tool before committing a broken devcontainer.json. A trailing comma there blocks the entire team from opening the project.

How do Dev Containers fit CI, previews, and production parity?

Dev Containers define development parity, not production deployment. Your production server on Ubuntu with Apache and PHP-FPM still needs its own pipeline. The win is that developers stop polluting staging with "only broken on my machine" bugs caused by missing extensions.

Reuse the same Dockerfile in CI for lint and test jobs. GitLab CI can build the Dev Container image, run composer test, and exit. That connects neatly to reproducible builds practices and preview environments for every pull request.

For PostgreSQL-specific apps, swap the MySQL service with patterns from running PostgreSQL in Docker for development. Keep database version pins close to production — MySQL 8.4 LTS in dev when production uses 8.4, not 5.7.

Environment variables belong in .env, never hard-coded in devcontainer.json. Mount secrets through Docker Compose env files listed in .gitignore. This mirrors how Ubuntu environment variables work on bare-metal staging servers.

Teams building REST APIs in Nepal often pair Dev Containers with API-first development workflows. The container runs PHP and Postman collections share the same base URL via forwarded ports. Frontend contractors hit localhost:8000 without installing PHP locally.

Key Takeaways

  • Commit .devcontainer/devcontainer.json plus Docker files to Git so every developer gets identical PHP, Node, and extension versions.
  • Use Docker Compose inside Dev Containers for MySQL, Redis, and other services — do not run databases on the host.
  • List VS Code extensions in customizations.vscode.extensions so Intelephense and linters match across the team.
  • Run Rebuild Container after Dockerfile changes; a stale image is the most common hidden version mismatch.
  • Fix Laravel permission issues with remoteUser and targeted postCreateCommand chown on storage/.
  • Reuse the Dev Container Dockerfile in CI pipelines to extend reproducibility from laptop to merge request checks.

People Also Ask

Do Dev Containers work without VS Code?

Yes. The Dev Containers spec is editor-agnostic. VS Code and GitHub Codespaces have the strongest support. JetBrains Gateway and other compatible clients can attach to the same devcontainer.json. CLI-only workflows still use Docker Compose directly without the Dev Containers layer.

Can you use Dev Containers on Apple Silicon Macs?

Docker Desktop for Mac supports ARM images. Use multi-arch base images like php:8.3-cli-bookworm or specify platform: linux/amd64 when you need x86 compatibility for older binaries. First builds may pull larger images; subsequent opens cache quickly.

Are Dev Containers the same as production containers?

No. Dev Containers optimise for developer experience: mounted source, debug extensions, and root access for Composer. Production images should be minimal, non-root, and free of editor tooling. Share the base PHP layer if useful, but keep separate Dockerfiles for dev and prod.

How much RAM do Dev Containers need?

Budget 4 GB for Docker Desktop on macOS or Windows, plus RAM for your app services. A Laravel app with MySQL and Redis typically runs comfortably in 6–8 GB total. Linux hosts with native Docker Engine use less overhead than Desktop VM wrappers.

Ship consistent environments from day one

Dev Containers: Reproducible Development Environments turn onboarding from a half-day install marathon into a single Reopen in Container action. You define PHP 8.3, Composer 2.10, Node.js 26 LTS, and MySQL 8.4 once. Every developer, contractor, and Codespace session inherits that stack from Git. Start with a Laravel or Symfony project, commit the config, and rebuild whenever the stack changes. If your team needs help standardising Docker-based workflows across development and deployment, see web development services or testing and optimization support, then contact us to plan a reproducible setup that matches how you ship to production.

Frequently Asked Questions

Dev Containers use a devcontainer.json file plus Docker to build a shared workspace your editor attaches to, so PHP, Node, extensions, and tools match on every machine without manual host setup.

Reproducibility comes from three layers committed to Git. A Dockerfile or base image pins the OS and packages such as PHP 8.3, Composer, and Node.js 26. Docker Compose adds sidecar services like MySQL 8.4 and Redis 8.10. The devcontainer.json file wires ports, VS Code extensions, lifecycle scripts, and workspace settings. Every clone gets the same definition. Change PHP from 8.3 to 8.4 in one commit and the whole team picks it up on rebuild, replacing README steps that drift within weeks across Windows WSL, Ubuntu, and macOS machines.

Install Docker Desktop or Docker Engine and the VS Code Dev Containers extension. Create a .devcontainer folder with devcontainer.json pointing at docker-compose.yml and your app service, workspaceFolder set to /var/www/html, and forwardPorts for 8000, 5173, and 3306. Write a minimal Dockerfile installing PHP extensions, Composer, and Node.js. Add MySQL and Redis in Compose with pinned image tags. Set postCreateCommand for composer install, copying .env.example, and php artisan key:generate. Run Dev Containers: Reopen in Container, then php artisan serve --host=0.0.0.0. Commit all Dev Container files to Git before onboarding juniors or contractors.

Docker Compose orchestrates containers but leaves each developer responsible for connecting their editor, installing Intelephense locally, and matching PHP binaries on the host. Dev Containers add native editor integration: automatic extension installation, port forwarding UI, lifecycle hooks, and debugging inside the remote workspace. Compose alone suits CI pipelines and headless integration tests where humans never write code inside the stack. Dev Containers suit mixed-OS teams shipping Laravel daily. Many projects use both: the same docker-compose.yml backs devcontainer.json and CI jobs. Compared to Vagrant VMs consuming around 2 GB before your app runs, a PHP Dev Container app service often sits under 512 MB.

Install ms-vscode-remote.remote-containers on the host, then declare extensions inside devcontainer.json customizations so versions stay aligned across the team. Common Laravel picks include Intelephense for PHP autocompletion using container paths, Laravel Blade Snippets for template support, ESLint and Prettier for Vite 8.x frontend linting, the Docker extension to inspect Compose services from the sidebar, and GitLens for blame without leaving the remote window. Listing extensions in Git prevents one developer running stale tooling while another gets correct autoload hints. On Apple Silicon or Windows, Docker Desktop must be running before you reopen in container.

Laravel needs write access on storage/ and bootstrap/cache/. Problems appear when the container runs as root but your host user is UID 1000, creating root-owned files on the bind mount that block artisan and uploads. Set remoteUser to www-data in devcontainer.json and add a postCreateCommand with a targeted chown on storage and bootstrap/cache. This mirrors fixes I've applied during Laravel upgrades on legal-tech portals where document uploads depend on specific directory permissions. Match ownership consistently whether you run artisan inside the container terminal or edit files from the host-side mount.

Dev Containers define development parity, not production deployment. Your Ubuntu server with Apache and PHP-FPM still needs its own pipeline. The win is fewer staging bugs from missing PHP extensions or mismatched Node versions on individual laptops. Reuse the same Dockerfile in GitLab CI to build the image, run composer test, and exit. Pin database versions close to production, such as MySQL 8.4 LTS in dev when production uses 8.4, not 5.7. Keep secrets in .env and Compose env files listed in .gitignore, never hard-coded in devcontainer.json. Frontend contractors can hit forwarded localhost:8000 without installing PHP locally.

Yes. The Dev Containers spec is editor-agnostic. VS Code and GitHub Codespaces have the strongest support. JetBrains Gateway attaches to the same devcontainer.json. CLI-only workflows use Docker Compose directly without the Dev Containers editor layer.

Docker Desktop for Mac supports ARM images on Apple Silicon. Use multi-arch base images like php:8.3-cli-bookworm for native ARM builds. When an older binary requires x86, set platform linux/amd64 in Compose and accept slower emulation. First builds may pull larger images; Docker layer cache speeds later opens. Ensure Docker Desktop is running before Dev Containers: Reopen in Container because the extension depends on a live Docker daemon. Teams mixing M-series Macs with Windows and Linux contractors still share one devcontainer.json committed to Git, which removes onboarding friction during feature sprints on booking systems and Laravel Livewire apps.

No. Dev Containers optimise developer experience with mounted source, debug extensions, and Composer access. Production images should stay minimal, non-root, and free of editor tooling.

Budget at least 4 GB for Docker Desktop on macOS or Windows because the Desktop VM wrapper adds overhead compared to native Docker Engine on Linux. A Laravel app container plus MySQL and Redis services typically runs comfortably in 6 to 8 GB total host RAM. The app service alone often sits under 512 MB, unlike a full Vagrant Ubuntu VM that can consume 2 GB before your application starts. Linux hosts with Docker Engine use less overhead than Desktop wrappers. Close other heavy host applications if RAM is tight before rebuilding containers with multiple Compose services running.

Bind mounts through Docker Desktop on non-Linux hosts make vendor/ and node_modules scans painfully slow because every file access crosses the VM boundary. Options include copying dependencies into a named Docker volume, using Mutagen sync, or developing on Linux natively where Docker Engine avoids the Desktop VM penalty. Large WooCommerce or Magento trees show the worst lag unless you exclude node_modules and vendor from the bind mount. I've seen Intelephense indexing stall for minutes in team environments during upgrades. Linux hosts align well with the same Apache and PHP-FPM workflows many teams already follow on staging servers.

Run Dev Containers: Rebuild Container after editing the Dockerfile, changing PHP extensions, or updating Compose service images. Reopening without rebuilding leaves you in a stale environment with old packages, similar to opcache serving stale code after deploy where the fix is always a reload, not guessing. Pin database image tags instead of bare latest so rebuilds stay predictable. Validate devcontainer.json with a JSON formatter before committing, because a trailing comma blocks the entire team from opening the project. Scan custom images with Trivy before sharing internally. Test the rebuild flow on a clean machine before onboarding juniors or contractors.

GitHub Codespaces implements the same Dev Containers spec in the cloud. Push your .devcontainer folder and a teammate opens a browser-based VS Code session with identical PHP, Node, and extension versions without installing Docker locally. Useful for contractors who refuse local Docker setup. Costs apply for compute minutes, so local Dev Containers remain cheaper for daily full-time development on your own hardware. Both read the same devcontainer.json, preserving reproducibility across environments. Local Dev Containers forward ports to localhost for API testing with shared Postman collections, while Codespaces exposes forwarded ports through the cloud session URL.

Native host installs offer full local speed but drift constantly as developers run different PHP and Node versions. Vagrant delivers high reproducibility through VM snapshots but starts slower and consumes around 2 GB RAM before your app runs. Dev Containers deliver high reproducibility with image and editor config in Git, native debugging inside the container, and low onboarding time after the first build. Best for mixed-OS Laravel teams, Node monorepos, and projects where new contributors need one command instead of a two-page setup guide. Solo developers on a single stable OS may stay native. Legacy non-Docker stacks may still suit Vagrant until containerised incrementally.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: