Custom Docker Compose Services¶
When you need services that aren’t available as DDEV add-ons, or require deep customization beyond what add-ons provide, you can create custom Docker Compose services using docker-compose.*.yaml files.
From Custom Services to Add-ons
Many successful custom services eventually become DDEV add-ons so they can be shared with teams, between projects, or with the broader community. If you find your custom service useful and stable, consider converting it to an add-on using the DDEV Add-on Template.
When to Use Custom Services¶
Use custom Docker Compose services when:
- You need a custom or highly specialized service
- You require deep customization of service configuration
- You’re prototyping or experimenting with service configurations
- The service doesn’t justify creating a full add-on yet
- You need tight integration with your specific project setup
Use add-ons when:
- An add-on is already available that provides a standard, tested service (Redis, Elasticsearch, Solr)
- You want automatic configuration and setup
See Using Add-ons for pre-built add-ons.
Creating Custom Services¶
Create docker-compose.*.yaml files in your project’s .ddev directory. DDEV automatically processes any files matching this pattern and merges them into the full docker-compose configuration.
Basic Service Example¶
Create .ddev/docker-compose.myservice.yaml:
services:
myservice:
container_name: "ddev-${DDEV_SITENAME}-myservice"
image: nginx:alpine
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
ports:
- "8080"
environment:
- VIRTUAL_HOST=${DDEV_HOSTNAME}
- HTTP_EXPOSE=8080:8080
- HTTPS_EXPOSE=8081:8080
volumes:
- ".:/mnt/ddev_config"
Service Configuration Best Practices¶
Required Labels¶
Always include these labels for proper DDEV integration:
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
Container Naming¶
Use consistent naming with the DDEV project:
Restart Policy¶
Set restart policy to prevent issues:
Port Exposure¶
For HTTP services that should be accessible via ddev-router:
ports:
- "8080" # Expose port to Docker network
environment:
- VIRTUAL_HOST=${DDEV_HOSTNAME}
- HTTP_EXPOSE=8080:8080 # HTTP access
- HTTPS_EXPOSE=8081:8080 # HTTPS access
For direct port binding (can cause conflicts between projects):
Volume Mounts¶
Mount your .ddev directory for configuration access:
Mount project files if needed:
x-ddev Extension¶
The x-ddev extension field lets you customize DDEV behavior per service in your .ddev/docker-compose.*.yaml files.
| Key | Description |
|---|---|
describe-url-port |
Text shown in the URL/PORT column of ddev describe |
describe-info |
Text shown in the INFO column of ddev describe |
ssh-shell |
Shell used by ddev ssh -s <service> for this service |
omit-ddev-labels |
Skip injecting com.ddev.* labels onto this service |
Customizing ddev describe Output¶
You can use the x-ddev extension field in your .ddev/docker-compose.*.yaml configuration to customize the output of ddev describe.
This feature is useful for showing credentials, URLs, or usage notes for custom services.
services:
rabbitmq:
container_name: "ddev-${DDEV_SITENAME}-rabbitmq"
image: rabbitmq:3-management-alpine
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
expose:
- "15672"
environment:
- VIRTUAL_HOST=${DDEV_HOSTNAME}
- HTTP_EXPOSE=15672:15672
- HTTPS_EXPOSE=15673:15672
- RABBITMQ_DEFAULT_USER=rabbitmq
- RABBITMQ_DEFAULT_PASS=rabbitmq
x-ddev:
# Can be multi-line block
describe-info: |
User: rabbitmq
Pass: rabbitmq
# Or single line string
describe-url-port: "extra help here"
x-ddev.describe-url-port: Appears in theURL/PORTcolumn when runningddev describe.x-ddev.describe-info: Appears in theINFOcolumn, making it easy for team members to view relevant service details without checking config files.
Tip
See related x-ddev.ssh-shell configuration for Changing ddev ssh Shell.
Omitting com.ddev.* Labels from a Service¶
Setting x-ddev.omit-ddev-labels: true stops DDEV from injecting com.ddev.* labels onto a service.
DDEV normally stamps these labels (like com.ddev.site-name) on every service and has ddev start wait for the matching containers. That makes ddev start fail when a one-shot container exits before the wait completes. Omitting the labels drops the service from that wait, while leaving it in the compose file so ddev stop, ddev poweroff, and ddev delete still tear it down.
services:
# One-shot container: prepares the shared volume, then exits
init:
container_name: "ddev-${DDEV_SITENAME}-init"
image: busybox:stable
command:
- sh
- -c
- |
mkdir -p /mnt/assets/cache &&
chown -R ${DDEV_UID}:${DDEV_GID} /mnt/assets
volumes:
- "assets:/mnt/assets"
x-ddev:
omit-ddev-labels: true
# web mounts the same volume
web:
volumes:
- "assets:/mnt/assets"
depends_on:
init:
condition: service_completed_successfully
volumes:
assets:
Advanced Service Examples¶
SQL Server Database Service¶
This example shows a custom SQL Server database service, useful when you need a database not natively supported by DDEV.
Create .ddev/docker-compose.sqlsrv.yaml:
services:
sqlsrv:
container_name: "ddev-${DDEV_SITENAME}-sqlsrv"
image: mcr.microsoft.com/mssql/server:2022-latest
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
ports:
- "1433:1433" # Direct port binding for SQL Server protocol
environment:
- SA_PASSWORD=Password123!
- ACCEPT_EULA=Y
- MSSQL_PID=Express
volumes:
- "sqlsrv-data:/var/opt/mssql"
- ".:/mnt/ddev_config"
# Platform specification for ARM64 compatibility
platform: linux/amd64
volumes:
sqlsrv-data:
external: true
name: "${DDEV_SITENAME}-sqlsrv-data"
Non-HTTP Services Require Direct Port Binding
SQL Server uses a proprietary protocol that cannot be routed through the DDEV router, so it requires direct ports binding. This means only one project can use SQL Server at a time unless you change the port.
Service with Custom Configuration¶
Create .ddev/docker-compose.elasticsearch.yaml:
services:
elasticsearch:
container_name: "ddev-${DDEV_SITENAME}-elasticsearch"
image: elasticsearch:8.11.0
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
ports:
- "9200"
environment:
- VIRTUAL_HOST=${DDEV_HOSTNAME}
- HTTP_EXPOSE=9200:9200
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- "elasticsearch-data:/usr/share/elasticsearch/data"
- "./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro"
volumes:
elasticsearch-data:
external: true
name: "${DDEV_SITENAME}-elasticsearch-data"
Multi-Service Setup¶
Create .ddev/docker-compose.cache.yaml:
services:
redis:
container_name: "ddev-${DDEV_SITENAME}-redis"
image: redis:7-alpine
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
ports:
- "6379"
memcached:
container_name: "ddev-${DDEV_SITENAME}-memcached"
image: memcached:alpine
# These two labels are added automatically since DDEV v1.25.2+
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
ports:
- "11211"
Environment Variables and Configuration¶
Available DDEV Variables¶
Here is a compressed list of commonly used variables in your service definitions:
${DDEV_SITENAME}- Project name${DDEV_HOSTNAME}- Comma-separated list of FQDN hostnames${DDEV_TLD}- Default top-level domain (ddev.site)${DDEV_APPROOT}- Full path to project root${DDEV_DOCROOT}- Document root (relative to project root)${DDEV_PHP_VERSION}- PHP version${DDEV_WEBSERVER_TYPE}- Web server type${DDEV_DATABASE_FAMILY}- Database family (mysql,postgres)
For a full list, please see Environment Variables Provided.
Custom Environment Variables¶
Define project-specific variables in .ddev/.env:
Then use in your service:
services:
myservice:
image: myservice:${MYSERVICE_VERSION:-latest}
environment:
- MEMORY_LIMIT=${MYSERVICE_MEMORY:-256m}
Service-Specific Environment Files¶
Use .ddev/.env.servicename for service-specific variables:
Testing and Debugging Services¶
Check Service Status¶
Verify Configuration¶
This shows the complete merged docker-compose configuration.
Connect to Service¶
Network Connectivity¶
Test connectivity from the web container:
Service Integration Patterns¶
Database Integration¶
Add database connection info to web container in .ddev/docker-compose.web-env.yaml:
services:
web:
environment:
- MYDB_HOST=mydb
- MYDB_PORT=5432
- MYDB_DATABASE=myproject
- MYDB_USER=db
- MYDB_PASSWORD=db
Configuration File Mounting¶
Mount configuration from your project in .ddev/docker-compose.config.yaml:
Initialization Scripts¶
Run initialization scripts in .ddev/docker-compose.init.yaml:
Troubleshooting¶
Common Issues¶
Port conflicts: Multiple projects using the same service may conflict. Use project-specific ports or let DDEV handle routing.
Service won’t start: Check ddev logs servicename for error messages.
Network connectivity: Ensure services are on the same Docker network (automatic with DDEV).
File permissions: Use appropriate volume mount options (:cached, :ro).
Debugging Steps¶
- Verify syntax:
ddev utility compose-config - Check logs:
ddev logs servicename - Test connectivity:
ddev exec "ping servicename" - Inspect container:
ddev exec --service=servicename bash
Migration from ddev-contrib¶
Many services previously documented in ddev-contrib have been converted to official add-ons. Check DDEV Add-on Registry first before creating custom services.
Still Available in ddev-contrib¶
- Old PHP Versions: Old PHP Versions
- Specialized configurations: Various experimental and niche services
Best Practices¶
Performance¶
- Use specific image tags instead of
latest - Set appropriate resource limits
- Use volume caching options (
:cached) - Minimize container layers and size
Security¶
- Don’t expose unnecessary ports to the host
- Use non-root users when possible
Maintainability¶
- Document your service configuration
- Use meaningful container names
- Group related services in single files
- Comment complex configurations
Team Sharing¶
- Include service documentation in your project readme
- Use environment variables for customizable values
- Provide setup and testing instructions
- Consider creating an add-on for reusable services
Converting to Add-ons¶
If your custom service becomes stable and useful for multiple projects, consider converting it to a DDEV add-on. This allows you to:
- Share the service with your team across projects
- Contribute to the DDEV community
- Benefit from automatic installation and configuration
- Add version management and updates
Steps to convert:
- Create an add-on repository from DDEV Add-on Template
- Move your service configuration to the add-on
- Add installation actions and configuration options
- Create tests and documentation
- Publish and share with the community
See Creating Add-ons for detailed instructions.
Custom Docker Compose services provide the ultimate flexibility for customizing your DDEV environment. While add-ons are recommended for common services, custom services let you integrate exactly what your project needs, with the potential to evolve into shareable add-ons.