Adding a Custom Python Service to DDEV
A quick guide on adding a local machine learning microservice to your DDEV environment, reachable from your app container over HTTP.
The pattern is useful when you want to keep ML workloads in a separate process — a stateless HTTP service that your app calls directly in development, while in production the same logic runs as a queue worker.
Create ml-service/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001", "--reload"]
Create .ddev/docker-compose.ml.yaml
services:
ml:
container_name: ddev-${DDEV_SITENAME}-ml
image: ${DDEV_SITENAME}-ml-service
build:
context: ../ml-service
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
expose:
- "8001"
environment:
- VIRTUAL_HOST=$DDEV_HOSTNAME
networks:
- ddev_default
restart: "no"
The build.context points to the ml-service/ directory at the repo root. DDEV builds the image on ddev restart.
Create .ddev/commands/host/ml
#!/bin/bash
## Description: Run a command in the ml container
## Usage: ml [command]
## Example: ddev ml python -c "import sklearn; print(sklearn.__version__)"
docker exec -it ddev-${DDEV_SITENAME}-ml "$@"
Make it executable:
chmod +x .ddev/commands/host/ml
Restart DDEV
ddev restart
DDEV will build the image on first start. Subsequent restarts reuse it unless the Dockerfile or requirements.txt changes.
Verify the service is running
ddev exec curl -s http://ddev-nf-ml:8001/docs
The service is reachable from the web container at http://ddev-{SITENAME}-ml:8001.
Calling the service from your app
From inside the web container (e.g. your PHP app), use the container hostname:
http://ddev-{SITENAME}-ml:8001
No credentials needed — the service is only accessible within the DDEV network.
Note: The service is stateless by design. Your app fetches the data it needs from its own database and passes it in the request body. The ML container never touches the database — a deliberate architectural boundary that keeps local and production behaviour consistent.