Dev Layer Builder
The ImageManager builds a development layer on top of project Docker images. This layer adds the dde user with the correct UID/GID, enabling file permission compatibility between the host and container.
Why a Dev Layer?
Section titled “Why a Dev Layer?”Docker images typically run as root or a predefined user. For development, files created inside the container need to be owned by your host user. The dev layer solves this by:
- Adding a
ddeuser and group with your host UID/GID - Installing
su-exec(Alpine) orgosu(Debian) for privilege dropping - Labeling the image with
dde.configured=trueanddde.project={name}
Label Check
Section titled “Label Check”Before building, ImageManager::hasLabel() checks if the image already has dde.configured=true. If it does, the build is skipped.
The isLayerCached() method checks if the dev image already exists locally:
public function isLayerCached(string $projectName): bool{ return $this->dockerManager->imageExists($this->getDevImageTag($projectName));}Image Naming
Section titled “Image Naming”Dev layer images follow the pattern:
dde-{projectName}:devFor example: dde-myproject:dev
Dockerfile Generation
Section titled “Dockerfile Generation”The generateDockerfile() method creates a Dockerfile based on the detected distribution:
Alpine-based Images
Section titled “Alpine-based Images”FROM php:8.4-fpm-alpineLABEL dde.configured="true"LABEL dde.project="myproject"RUN apk add --no-cache su-exec shadow \ && addgroup -g 1000 dde || true \ && adduser -u 1000 -G dde -D -h /home/dde dde || trueDebian-based Images
Section titled “Debian-based Images”FROM php:8.4-fpmLABEL dde.configured="true"LABEL dde.project="myproject"RUN apt-get update && apt-get install -y --no-install-recommends gosu && rm -rf /var/lib/apt/lists/* \ && addgroup --gid 1000 dde || true \ && adduser --uid 1000 --gid 1000 --disabled-password --gecos "" --home /home/dde dde || trueDistribution detection is done by detectDistro(), which runs cat /etc/os-release inside an ephemeral container and checks for “alpine” in the output.
Build Process
Section titled “Build Process”buildDevLayer() executes the full build:
- Generate the dev image tag (
dde-{projectName}:dev) - Detect the base image’s distribution (Alpine vs Debian)
- Generate the Dockerfile with host UID/GID from
UserContext - Create a temporary directory, write the Dockerfile
- Run
docker buildviaDockerManager::buildImage() - Clean up the temporary directory
Cache Invalidation
Section titled “Cache Invalidation”public function invalidateLayer(string $projectName): voidRemoves the dev layer image, forcing a rebuild on the next project:up. This is useful when:
- The base image has changed
- The host UID/GID has changed
- The
--buildflag is passed toproject:up
Integration with Project Lifecycle
Section titled “Integration with Project Lifecycle”The ProjectLifecycleManager::up() method calls ImageManager::ensureDevLayers() which:
- Returns
nullearly if the project name is empty - Checks if the layer is already cached (returns
nullif so) - Finds the first service with an
imagekey in the compose file - Builds the dev layer for that service
- Returns an array with the service name and image tag
The result is included in the return value of ProjectLifecycleManager::up() for informational purposes. To actually use the dev layer at runtime, the project config should set the container image explicitly (e.g. containers.web.image: dde-myproject:dev), which the runtime override will pick up via DockerComposeManager::generateOverride().