Drone CI, by default, runs your build steps in isolated containers. Sometimes, your tests or application need to communicate with other services that aren’t part of the main build container itself, like a database, a message queue, or a separate API. This is where background services come in.

Imagine you have a Go application that needs to talk to a PostgreSQL database during its integration tests. Your .drone.yml might look like this:

kind: pipeline
type: docker
name: default

steps:
- name: build
  image: golang:1.20
  commands:
  - go build ./...

- name: test
  image: golang:1.20
  services:
  - postgres:5432 # This is the key!
  commands:
  - go test -v ./...

services:
- name: postgres
  image: postgres:14
  environment:
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpassword
    POSTGRES_DB: testdb
  ports:
  - 5432

When Drone starts this pipeline, it doesn’t just launch the main build container. It also spins up a separate container for PostgreSQL, defined in the services section. This PostgreSQL container is accessible from the test step container. The services: - postgres:5432 line in the test step tells Drone that the postgres service is available at port 5432.

Inside your Go test code, you’d then connect to localhost:5432 using the credentials defined in the environment section of the postgres service. Drone magically handles the network routing so that localhost within the test container resolves to the postgres service container.

This pattern isn’t limited to databases. You can run Redis, Kafka, Elasticsearch, or any other service that can be containerized. The services section in your Drone configuration is where you declare these dependencies.

Let’s break down the services block in more detail:

  • name: This is an arbitrary name you give to the service. It’s used to reference the service in your build steps (e.g., services: - redis:6379).
  • image: The Docker image to use for the service. You can use official images or your own custom ones.
  • environment: A map of environment variables to set within the service container. This is crucial for configuration, like setting database credentials or API keys.
  • ports: A list of ports to expose from the service container. These ports will be made accessible to the build step containers. Drone typically maps these to localhost within the build step.
  • volumes: You can also mount volumes into service containers, for example, to persist data between pipeline runs (though this is less common for transient test services).

The real power here is how Drone orchestrates these containers. It launches them before your build steps begin and stops them after your build steps complete. This ensures that your services are up and running when your tests need them, and that no lingering resources are left behind.

Consider a scenario where you’re testing a web application that relies on a separate API. You can define both your web app’s build step and the API service side-by-side:

kind: pipeline
type: docker
name: web-api-test

steps:
- name: api-tests
  image: node:18
  services:
  - api-service:3000 # Reference the API service
  commands:
  - npm install
  - npm test

services:
- name: api-service
  image: my-custom-api:latest # Your custom API image
  ports:
  - 3000

In this setup, the api-service container starts first. Then, the api-tests container starts. Your Node.js tests can then make HTTP requests to http://localhost:3000, and Drone will route that traffic to your running api-service container.

The most surprising true thing about this system is that the localhost resolution isn’t a simple DNS entry. Drone injects network configurations into the build step container that directly map the service name and port (e.g., postgres:5432) to the IP address of the corresponding service container, effectively creating a private network segment for your pipeline’s dependencies.

This pattern dramatically simplifies setting up complex testing environments. Instead of managing separate Docker Compose files or manually starting services, you declare them directly in your .drone.yml, and Drone handles the lifecycle.

When you’re done with background services and want to move on to deploying your application, you’ll likely explore how to manage secrets and configuration for your deployed services.

Want structured learning?

Take the full Drone course →