github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/docs/content/users/extend/custom-compose-files.md (about)

     1  # Defining Additional Services with Docker Compose
     2  
     3  ## Prerequisite
     4  
     5  Much of DDEV’s customization ability and extensibility comes from leveraging features and functionality provided by [Docker](https://docs.docker.com/) and [Docker Compose](https://docs.docker.com/compose/overview/). Some working knowledge of these tools is required in order to customize or extend the environment DDEV provides.
     6  
     7  There are [many examples of custom docker-compose files](https://github.com/ddev/ddev-contrib#additional-services-added-via-docker-composeserviceyaml) available on [ddev-contrib](https://github.com/ddev/ddev-contrib).
     8  
     9  ## Background
    10  
    11  Under the hood, DDEV uses a private copy of docker-compose to define and run the multiple containers that make up the local environment for a project. docker-compose supports defining multiple compose files to facilitate sharing Compose configurations between files and projects, and DDEV is designed to leverage this ability.
    12  
    13  To add custom configuration or additional services to your project, create docker-compose files in the `.ddev` directory. DDEV will process any files with the `docker-compose.[servicename].yaml` naming convention and include them in executing docker-compose functionality. You can optionally create a `docker-compose.override.yaml` to override any configurations from the main `.ddev/.ddev-docker-compose-base.yaml` or any additional docker-compose files added to your project.
    14  
    15  !!!warning "Don’t modify `.ddev-docker-compose-base.yaml` or `.ddev-docker-compose-full.yaml`!"
    16  
    17      The main docker-compose file is `.ddev/.ddev-docker-compose-base.yaml`, reserved exclusively for DDEV’s use. It’s overwritten every time a project is started, so any edits will be lost. If you need to override configuration provided by `.ddev/.ddev-docker-compose-base.yaml`, use an additional `docker-compose.<whatever>.yaml` file instead.
    18  
    19  ## `docker-compose.*.yaml` Examples
    20  
    21  * Expose an additional port 9999 to host port 9999, in a file perhaps called `docker-compose.ports.yaml`:
    22  
    23  ```yaml
    24  services:
    25    someservice:
    26      ports:
    27      - "9999:9999"
    28  ```
    29  
    30  That approach usually isn’t sustainable because two projects might want to use the same port, so we *expose* the additional port to the Docker network and then use `ddev-router` to bind it to the host. This works only for services with an HTTP API, but results in having both HTTP and HTTPS ports (9998 and 9999).
    31  
    32  ```yaml
    33  services:
    34    someservice:
    35      container_name: "ddev-${DDEV_SITENAME}-someservice"
    36      labels:
    37        com.ddev.site-name: ${DDEV_SITENAME}
    38        com.ddev.approot: ${DDEV_APPROOT}
    39      expose:
    40        - "9999"
    41      environment:
    42        - VIRTUAL_HOST=$DDEV_HOSTNAME
    43        - HTTP_EXPOSE=9998:9999
    44        - HTTPS_EXPOSE=9999:9999
    45  ```
    46  
    47  ## Confirming docker-compose Configurations
    48  
    49  To better understand how DDEV parses your custom docker-compose files, run `ddev debug compose-config`. This prints the final, DDEV-generated docker-compose configuration when starting your project.
    50  
    51  ## Conventions for Defining Additional Services
    52  
    53  When defining additional services for your project, we recommend following these conventions to ensure DDEV handles your service the same way DDEV handles default services.
    54  
    55  * The container name should be `ddev-${DDEV_SITENAME}-<servicename>`. This ensures the auto-generated [Traefik routing configuration](./traefik-router.md#project-traefik-configuration) matches your custom service.
    56  * Provide containers with required labels:
    57  
    58      ```yaml
    59          labels:
    60            com.ddev.site-name: ${DDEV_SITENAME}
    61            com.ddev.approot: ${DDEV_APPROOT}
    62      ```
    63  
    64  * Exposing ports for service: you can expose the port for a service to be accessible as `projectname.ddev.site:portNum` while your project is running. This is achieved by the following configurations for the container(s) being added:
    65  
    66      * Define only the internal port in the `expose` section for docker-compose; use `ports:` only if the port will be bound directly to `localhost`, as may be required for non-HTTP services.
    67  
    68      * To expose a web interface to be accessible over HTTP, define the following environment variables in the `environment` section for docker-compose:
    69  
    70          * `VIRTUAL_HOST=$DDEV_HOSTNAME` You can also specify an arbitrary hostname like `VIRTUAL_HOST=extra.ddev.site`.
    71          * `HTTP_EXPOSE=portNum` The `hostPort:containerPort` convention may be used here to expose a container’s port to a different external port. To expose multiple ports for a single container, define the ports as comma-separated values.
    72          * `HTTPS_EXPOSE=<exposedPortNumber>:portNum` This will expose an HTTPS interface on `<exposedPortNumber>` to the host (and to the `web` container) as `https://<project>.ddev.site:exposedPortNumber`. To expose multiple ports for a single container, use comma-separated definitions, as in `HTTPS_EXPOSE=9998:80,9999:81`, which would expose HTTP port 80 from the container as `https://<project>.ddev.site:9998` and HTTP port 81 from the container as `https://<project>.ddev.site:9999`.
    73  
    74  ## Interacting with Additional Services
    75  
    76  [`ddev exec`](../usage/commands.md#exec), [`ddev ssh`](../usage/commands.md#ssh), and [`ddev logs`](../usage/commands.md#logs) interact with containers on an individual basis.
    77  
    78  By default, these commands interact with the `web` container for a project. All of these commands, however, provide a `--service` or `-s` flag allowing you to specify the service name of the container to interact with. For example, if you added a service to provide Apache Solr, and the service was named `solr`, you would be able to run `ddev logs --service solr` to retrieve the Solr container’s logs.
    79  
    80  ## Third Party Services May Need To Trust `ddev-webserver`
    81  
    82  Sometimes a third-party service (`docker-compose.*.yaml`) may need to consume content from the `ddev-webserver` container. A PDF generator like [Gotenberg](https://github.com/gotenberg/gotenberg), for example, might need to read in-container images or text in order to create a PDF. Or a testing service may need to read data in order to support tests.
    83  
    84  A third-party service is not configured to trust DDEV’s `mkcert` certificate authority by default, so in cases like this you have to either use HTTP between the two containers, or make the third-party service ignore or trust the certificate authority.
    85  
    86  Using plain HTTP between the containers is the simplest technique. For example, the [`ddev-selenium-standalone-chrome`](https://github.com/ddev/ddev-selenium-standalone-chrome) service must consume content, so it conducts interactions with the `ddev-webserver` [by accessing `http://web`](https://github.com/ddev/ddev-selenium-standalone-chrome/blob/main/config.selenium-standalone-chrome.yaml#L17). In this case, the `selenium-chrome` container accesses the `web` container via HTTP instead of HTTPS.
    87  
    88  A second technique is to tell the third-party service to ignore HTTPS/TLS errors. For example, if the third-party service uses cURL, it could use `curl --insecure https://web` or `curl --insecure https://<project>.ddev.site`.
    89  
    90  A third and more complex approach is to make the third-party container actually trust the self-signed certificate that the `ddev-webserver` container is using. This can be done in many cases using a custom Dockerfile and some extra configuration in the `ddev-config.*.yaml`. An example would be:
    91  
    92  ```yaml
    93  services:
    94    example:
    95      container_name: ddev-${DDEV_SITENAME}-example
    96      command: "bash -c 'mkcert -install && original-start-command-from-image'"
    97      # Add a build stage so we can add `mkcert`, etc.
    98      # The Dockerfile for the build stage goes in the `.ddev/example directory` here
    99      build:
   100        context: example
   101      environment:
   102        - HTTP_EXPOSE=3001:3000
   103        - HTTPS_EXPOSE=3000:3000
   104        - VIRTUAL_HOST=$DDEV_HOSTNAME
   105      # Adding external_links allows connections to `https://example.ddev.site`,
   106      # which then can go through `ddev-router`
   107      external_links:
   108        - ddev-router:${DDEV_SITENAME}.${DDEV_TLD}
   109      labels:
   110        com.ddev.approot: $DDEV_APPROOT
   111        com.ddev.site-name: ${DDEV_SITENAME}
   112      restart: 'no'
   113      volumes:
   114        - .:/mnt/ddev_config
   115        # `ddev-global-cache` gets mounted so we have the CAROOT
   116        # This is required so that the CA is available for `mkcert` to install
   117        # and for custom commands to work
   118        - ddev-global-cache:/mnt/ddev-global-cache
   119  ```
   120  
   121  ```Dockerfile
   122  FROM example/example
   123  
   124  # CAROOT for `mkcert` to use, has the CA config
   125  ENV CAROOT=/mnt/ddev-global-cache/mkcert
   126  
   127  # If the image build does not run as the default `root` user,
   128  # temporarily change to root. If the image already has the default setup
   129  # where it builds as `root`, then
   130  # there is no need to change user here.
   131  USER root
   132  # Give the `example` user full `sudo` privileges
   133  RUN echo "example ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/example && chmod 0440 /etc/sudoers.d/example
   134  # Install the correct architecture binary of `mkcert`
   135  RUN export TARGETPLATFORM=linux/$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && mkdir -p /usr/local/bin && curl --fail -JL -s -o /usr/local/bin/mkcert "https://dl.filippo.io/mkcert/latest?for=${TARGETPLATFORM}"
   136  RUN chmod +x /usr/local/bin/mkcert
   137  USER original_user
   138  ```