Skip to content

Setup your own project with WebDev

Short explaination

Instead of using on the examples from the example repository, we will setup a project with WebDev from the ground up.

Creating the folder structure

Since the development environment is based on devcontainer, we need to create first a folder called .devcontainer in your project root. Afterwards create the following files and folders as shown below. The files can be empty for now and we will go through all of them in the next section.

.
└─ .devcontainer
   ├─ scripts
   │  └─ .gitkeep
   ├─ tools
   │  ├─ adminer.php
   │  ├─ phpinfo.php
   │  └─ xdebuginfo.php
   ├─ traefik
   │  ├─ certs
   │  │  └─ .gitkeep
   │  └─ config
   │  │  └─ traefik.yml
   ├─ vhost
   │  └─ .gitkeep
   ├─ .gitignore
   ├─ devcontainer.json
   ├─ docker-compose.yml
   └─ webdev.ynl

Scripts folder (line 3)

As in detail explained in Extending WebDev, we can add custom commands to WebDev. This folder is the default folder in which custom scripts will be searched for. Further down in this tutorial, we will create a script that setups our system.

Tools folder (line 5)

Currently there are three scripts included. First one is adminer.php which is an alternative to phpmyadmin and consists of one file. The content of that file can be downloaded here.

Content for phpinfo.php

php
<?php

phpinfo();

Content for xdebuginfo.php

php
<?php

xdebug_info();

Traefik folder (line 9)

Traefik is used as reverse proxy and helps managing SSL Certificates for our dev domain and the routing of the subdomains to their correct service.

The certs directory only needs a .gitkeep file as traefik will generate the certificates on the first start of the development environment.

For traefik to work, we need to add a little config file(line 13), which content is the following:

yaml
global:
  sendAnonymousUsage: false

api:
  dashboard: true
  insecure: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    watch: true
    exposedByDefault: false

  file:
    filename: /etc/traefik/dynamic.yml
    watch: true

log:
  level: DEBUG
  format: common

entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"

vhost (Line 14)

The vhost folder will contain the virtual hosts configurations files for apache. These will be generated by WebDev on startup.

.gitignore (Line 16)

This file contains dynamic files that are generated by WebDev and should not be added to the git repository.

.createDoneLock
vhost/*.conf
traefik/certs/*.pem
docker-compose.proxy.yml
traefik/config/dynamic.yml

devcontainer.json (Line 17)

This is an example on how the devcontainer.json could look like. You can use it as a starting point to customize your environment with the extension you need for your IDE etc. You can find a detailed explaination about this file here.

To get started, you should change the value for name (line 4) to match your project name.

json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/php
{
	"name": "WebDev Development Environment",
	"dockerComposeFile": ["docker-compose.yml", "docker-compose.proxy.yml"],
	"service": "devcontainer",
	"runServices": ["devcontainer"],

	// Configure tool-specific properties.
	"customizations": {
		// Configure properties specific to VS Code.
		"vscode": {
			"settings": {},
			"extensions": [
				"in4margaret.compareit",
				"DEVSENSE.composer-php-vscode",
				"donjayamanne.githistory",
				"eamodio.gitlens",
				"DEVSENSE.intelli-php-vscode",
				"DEVSENSE.phptools-vscode",
				"marabesi.php-import-checker",
				"DEVSENSE.profiler-php-vscode",
				"bmewburn.vscode-intelephense-client",
				"xdebug.php-debug",
				"Vue.volar",
				"dbaeumer.vscode-eslint",
				"phil294.git-log--graph",
				"zackiles.cursor-workbench"
			]
		}
	},
	"forwardPorts": [8080,3306,8025,8081,8082],
	"portsAttributes": {
		"8080": {
			"label": "Apache Webserver",
			"onAutoForward": "notify"
		},
		"3306": {
			"label": "MySQL Database",
			"onAutoForward": "notify"
		},
		"8025": {
			"label": "Mailpit Webinterface",
			"onAutoForward": "notify"
		},
		"8081": {
			"label": "PHPMyAdmin",
			"onAutoForward": "notify"
		},
		"8082": {
			"label": "PHPCacheAdmin",
			"onAutoForward": "notify"
		}
	},
	"initializeCommand": "webdev workspaces-on-init --no-header && webdev tasks init --no-header",
	"postStartCommand": "sudo webdev workspaces-post-start --no-header && webdev tasks start --no-header",
	"postCreateCommand": "sudo ln -sf ~/webdev/webdev.sh /usr/local/bin/webdev && webdev tasks create --no-header",
	"remoteUser": "webdev",
	"workspaceFolder": "/var/www/html/",
	"containerEnv": {
    	"WEBDEV_WORKSPACE_FOLDER": "/var/www/html/",
		// We need to set this variable so webdev can determine if it is running within a container or locally
		"DEVCONTAINER": "1",
		"COMPOSE_PROJECT_NAME": "${localWorkspaceFolderBasename}"
	}
}

docker-compose.yml (Line 18)

The docker-compose.yml defines all services that should be available in our development environment, including the devcontainer which runs apache, php, node etc. Of course you can add any additional service you may need for your project but it should be a good starting point.

In line 8 you can see that we use ghcr.io/derroylo/devcontainer-prebuilds/shopware:latest as a base image for our main image. You can of course build your own base image, extend the existing one or choose one of the prebuild Images.

yaml
# Docker compose for the development environment
services:
  # Base image for our devcontainer
  devcontainer:
    labels:
      com.webdev.category: "devcontainer"
      com.webdev.proxy.port: "8080"
    image: ghcr.io/derroylo/devcontainer-prebuilds/shopware:latest
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-app
    volumes:
      - ..:/var/www/html:cached
      - bashhistory:/commandhistory
      - ~/webdev:/home/webdev/webdev
    command: sleep infinity
    networks:
      - webdev-network
    ports:
      - "8080:8080"

  traefik:
    image: traefik:latest
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-traefik
    ports:
      - "80:80"
      - "443:443"
      - "8083:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik/config/traefik.yml:/etc/traefik/traefik.yml:ro"
      - "./traefik/config/dynamic.yml:/etc/traefik/dynamic.yml:ro"
      - "./traefik/certs/:/etc/certs:ro"
    labels:
      com.webdev.category: "proxy"
      com.webdev.name: "Traefik"
      com.webdev.description: "Reverse Proxy for Web Development"
      com.webdev.proxy.subdomain: "traefik"
      com.webdev.proxy.port: "8080"
    networks:
      - webdev-network

  # MySql Server
  # For more infos check: https://hub.docker.com/_/mysql/
  mysql:
    labels:
      com.webdev.category: "database"
      com.webdev.name: "MySQL Server"
      com.webdev.description: "Relational Database"
    image: mysql:8.0
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-mysql
    networks:
      - webdev-network
    ports:
      # make this port public so it can also accessed via HeidiSQL or similar tools
      - 3306:3306
    environment:
      # mysql root password
      - MYSQL_ROOT_PASSWORD=webdev
      # create this database by default
      - MYSQL_DATABASE=webdev
      # allow connections from any host
      - MYSQL_ROOT_HOST=%

  # Mailpit (can fetch all outgoing mails and show them via webinterface)
  # For more infos check: https://hub.docker.com/r/axllent/mailpit
  mailpit:
    labels:
      com.webdev.category: "mail"
      com.webdev.name: "Mailpit"
      com.webdev.description: "email testing tool"
      com.webdev.proxy.subdomain: "mail"
      com.webdev.proxy.port: "8025"
    image: axllent/mailpit:latest
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-mailpit
    networks:
      - webdev-network
    ports:
      # make this port public for the webinterface
      - 8025:8025
      # port for incoming mails
      - 1025:1025

  # Redis Server
  # For more infos check: https://hub.docker.com/_/redis
  redis:
    labels:
      com.webdev.category: "cache"
      com.webdev.name: "Redis Server"
      com.webdev.description: "In-Memory Key-Value Store"
    image: redis:latest
    networks:
      - webdev-network
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-redis
    ports:
      - 6379:6379

  # PhpMyAdmin
  # For more infos check: https://hub.docker.com/_/phpmyadmin
  phpmyadmin:
    labels:
      com.webdev.category: "tools"
      com.webdev.name: "PhpMyAdmin"
      com.webdev.description: "MySQL Admin Tool"
      com.webdev.proxy.subdomain: "pma"
      com.webdev.proxy.port: "80"
    image: phpmyadmin:latest
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-pma
    depends_on:
      - mysql
    networks:
      - webdev-network
    environment:
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=webdev
      - UPLOAD_LIMIT=2048M
      - PMA_PMADB=pma
    ports:
      - "8081:80"
    volumes:
      - /tmp/apache2/logs:/var/log/apache2

  # PhpCacheAdmin
  # For more infos check: https://hub.docker.com/r/robinn/phpcacheadmin
  phpcacheadmin:
    labels:
      com.webdev.category: "tools"
      com.webdev.name: "PhpCacheAdmin"
      com.webdev.description: "UI for managing PHP cache systems"
      com.webdev.proxy.subdomain: "pca"
      com.webdev.proxy.port: "80"
    image: robinn/phpcacheadmin
    container_name: ${COMPOSE_PROJECT_NAME:-devcontainer}-phpcacheadmin
    networks:
      - webdev-network
    ports:
      - "8082:80"
    environment:
      - PCA_REDIS_0_HOST=redis
      - PCA_REDIS_0_PORT=6379
    volumes:
      - /tmp/apache2/logs:/var/log/apache2

volumes:
  bashhistory:

networks:
  webdev-network:
    driver: bridge

webdev.yml (Line 19)

Last but not least, we need to give WebDev a little config file so it knows what todo on startup etc.

In short we tell WebDev that it should start traefik and mysql and define a few tasks that should install our project and show a summary after it started.

For a full example of this file, take a look at the reference section.

yaml
services:
  active:
  - traefik
  - mysql
tasks:
  fixOwner:
    name: Fixes the owner of different folders
    create:
    - sudo chown -R $(id -u):$(id -g) /home/webdev
  services:
    name: Start the active services
    init:
    - webdev services start -d
  install:
    name: Run composer install and setup everything
    onlyMain: false
    create:
    - webdev project install
  apache:
    name: Start apache
    start:
    - apachectl start
  info:
    name: Show information about the development environment
    start:
    - WEBDEV_DISABLE_HEADER=1 webdev project-start-summary

Install your project

Before we can start our project, we should create a file that setups the project. We usually don´t want to execute all setup commands manually each time we create a new instance, or when a coworker also wants to work on it. That way we make it easier for everyone to work on this project, without remembering all tasks that needs to be done, so he just needs the project and can directly start working on it.

Create a new folder called tasks within the scripts folder. Now create a new file called setup.sh within that folder, with the following content:

bash
#!/bin/bash

# webDevCommand: install
# webDevBranch: project
# webDevBranchDescription: Commands for the project
# webDevDescription: Install project

Now it heavily depends on your project on what you need to put in here.

An example could look like this:

bash
#!/bin/bash

# webDevCommand: install
# webDevBranch: project
# webDevBranchDescription: Commands for the project
# webDevDescription: Install project

# Install dependencies
composer install

# Write database connection
echo "DATABASE_URL=mysql://root:webdev@mysql:3306/webdev" >> .env.local

So with this commands we would install all composer packages and write the database dsn to our .env.local file. You could also leave this file empty for now and start the dev environment, execute the commands manually and add them to the file afterwards.

Document root

Before we can finally start our project, we need to make sure that the document root folder exists, otherwise the apache webserver will fail to start. When you use one of the prebuild images, then the document root would be public. To change that folder, you need to edit your webdev.yml.

yaml
...
workspaces:
    main:
        docRoot: src/public

Adjust the value for docRoot to your needs.

Starting your project

When you are using VS Code or one of its forks, like Cursor, Kiro or Windsurf, you just need to open your project in it. Usually you will get a notification that a DevContainer setup was found and asks you if you want to start it. If you click yes, then it will take a few minutes to get everything ready for you. The first time could take a bit longer, since he needs to download all docker images etc.

If you are using PHPStorm i would not recommend using the integrated DevContainer extension. It works the same way as VS Code but it will need to install the server side of the IDE into your devcontainer, which would add another 2GB to it. I would recommend to start the devcontainer environment manually with the command webdev project start and open the project folder on your host system. If you want to execute commands in your devcontainer then you can use the command webdev terminal in your shell.

What todo afterwards?

Well now that everything is running, you can start coding or further adjust it to your needs. Checkout the CLI or Reference docs on how to change the php version for example.