Understanding Drone CI Configuration
Drone CI is a container-native continuous integration platform built on Docker. Unlike traditional CI tools that rely on agent-based architectures, Drone executes every build step inside isolated containers. This approach guarantees reproducible builds regardless of the host environment. The configuration for a Drone pipeline lives entirely in a single YAML file stored alongside your source code — typically named .drone.yml — making pipelines version-controlled, auditable, and portable.
When you push code to your repository, Drone reads this configuration file, pulls the specified container images, and executes your pipeline steps in the exact order you define. There are no hidden plugins, no opaque server-side configurations, and no drift between local and CI environments. Everything is explicit, transparent, and self-documenting.
Core Architecture Concepts
Before diving into syntax, it's important to understand Drone's execution model. A Drone pipeline consists of steps that run in sequence by default. Each step operates inside its own container. Steps can optionally specify depends_on to run in parallel. Pipelines can also declare services — auxiliary containers that start alongside steps and provide supporting infrastructure like databases or caches during the build. The entire pipeline is triggered by events such as pushes, pull requests, or tags.
Basic Pipeline Structure
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →The simplest possible Drone configuration looks like this:
kind: pipeline
type: docker
name: default
steps:
- name: greet
image: alpine:3.18
commands:
- echo "Hello from Drone CI"
- echo "Build started at $(date)"
Let's break down each top-level field:
- kind: Always set to
pipelinefor standard builds. Drone also supportssignatureandsecretkinds in advanced setups. - type: Specifies the execution environment. Use
dockerfor the Docker runner,kubernetesfor the Kubernetes runner, orexecfor shell-based execution on the host. - name: A human-readable identifier for the pipeline. This becomes the pipeline's label in the Drone UI.
- steps: The ordered list of build actions. Each step is a container with an image and commands or plugin configuration.
Step Configuration in Detail
A step has several configurable properties beyond just name and image. Here is a fully-annotated step block:
- name: build
image: golang:1.21
commands:
- go build -o app ./cmd/server
- go vet ./...
environment:
GOOS: linux
GOARCH: amd64
CGO_ENABLED: "0"
volumes:
- name: deps
path: /go/pkg/mod
depends_on:
- clone
when:
branch: main
event: push
Key step-level fields explained:
- commands: Shell commands executed inside the container. Drone uses
/bin/shby default. For Bash-specific features, override the entrypoint. - environment: Environment variables injected into the container at runtime. These are plain text and visible in logs — use secrets for sensitive values.
- volumes: Mount persistent or shared volumes into the step. Volumes defined at the pipeline level can be referenced here by name.
- depends_on: Explicit dependency declaration. If omitted, the step depends on the previous step in the list. Use this to create parallel execution graphs.
- when: Conditional execution rules. The step only runs if all specified conditions match.
Using Plugins for Common Tasks
Drone's plugin ecosystem is one of its greatest strengths. A plugin is simply a Docker container designed to accept environment variables as parameters and perform a specific task — publishing artifacts, sending notifications, deploying to cloud providers, etc. Plugins are invoked by setting the step's image to the plugin's Docker image and providing settings under a dedicated settings block:
- name: publish
image: plugins/docker:linux-amd64
settings:
registry: registry.example.com
repo: registry.example.com/my-app
username:
from_secret: docker_username
password:
from_secret: docker_password
tags:
- latest
- ${DRONE_COMMIT_SHA}
depends_on:
- build
Notice how credentials are pulled from secrets using the from_secret directive. This avoids ever hardcoding sensitive values in the configuration file. The plugin receives these values as environment variables internally.
Services and Dependencies
Services are containers that run for the duration of the pipeline and are typically used for databases, caches, or mock APIs needed during testing. Unlike steps, services are not expected to exit — they run continuously until the pipeline completes:
services:
- name: postgres
image: postgres:15-alpine
environment:
POSTGRES_USER: drone
POSTGRES_PASSWORD:
from_secret: pg_password
POSTGRES_DB: drone_test
steps:
- name: test
image: myapp:dev
commands:
- sleep 5 # wait for postgres to be ready
- go test ./... -v
environment:
DATABASE_URL: postgres://drone:${PG_PASSWORD}@postgres:5432/drone_test?sslmode=disable
Drone automatically creates a Docker network for each pipeline. All steps and services can reach each other by their name as a hostname. In the example above, the test step connects to the Postgres service using postgres as the hostname. No port mapping or link configuration is required — container name resolution is built in.
Parallel Pipelines and Fan-Out Patterns
A single .drone.yml file can define multiple independent pipelines by listing them sequentially. Each pipeline has its own execution graph and can run in parallel with others:
---
kind: pipeline
type: docker
name: backend
steps:
- name: test
image: golang:1.21
commands:
- go test ./...
---
kind: pipeline
type: docker
name: frontend
steps:
- name: lint
image: node:20-alpine
commands:
- npm install
- npm run lint
---
kind: pipeline
type: docker
name: deploy
depends_on:
- backend
- frontend
steps:
- name: release
image: plugins/heroku
settings:
api_key:
from_secret: heroku_key
app_name: my-app
This configuration defines three pipelines. The backend and frontend pipelines run concurrently. The deploy pipeline uses the depends_on key at the pipeline level to wait for both to succeed before executing. This is a classic fan-in pattern that enables fast feedback while maintaining deployment safety.
Triggers and Conditional Execution
Drone provides granular control over when pipelines and steps execute. Conditions can filter on branch names, event types, commit messages, and even file changes:
steps:
- name: deploy-prod
image: plugins/ssh
settings:
host: prod.example.com
user: deploy
script:
- docker pull myapp:latest
- docker compose up -d
when:
branch: main
event: push
status: success
- name: notify
image: plugins/slack
settings:
webhook:
from_secret: slack_webhook_url
channel: deployments
when:
branch:
include:
- main
- release/*
exclude:
- release/experimental-*
trigger:
branch:
- main
- feature/*
event:
- push
- pull_request
ref:
- refs/tags/v*
action:
- opened
- synchronize
The when block on a step supports include and exclude patterns with glob-style matching. The pipeline-level trigger block determines whether the entire pipeline runs at all. For pull requests, the action field lets you specify whether the pipeline runs on opened, synchronize (new commits pushed to the PR), or merged events.
Environment Variables and Secrets Management
Drone injects a rich set of built-in environment variables into every step. These provide metadata about the commit, branch, tag, and build environment:
DRONE_BUILD_NUMBER=42
DRONE_COMMIT_SHA=a1b2c3d4e5f6...
DRONE_COMMIT_BRANCH=feature/new-api
DRONE_COMMIT_AUTHOR=Jane Doe
DRONE_COMMIT_AUTHOR_EMAIL=jane@example.com
DRONE_TAG=v2.1.0
DRONE_REPO=octocat/hello-world
DRONE_DEPTH=50
Secrets are managed separately from the configuration file. You add them via the Drone CLI or API, and they are injected into steps using from_secret:
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_key
Secrets are encrypted at rest and are never exposed in build logs. Drone automatically masks secret values in the output. For pull request events, secrets are not available by default — you must explicitly grant access per-repository in the Drone settings to prevent secrets leaking through unverified PRs.
Working with Volumes and Workspaces
By default, each step starts with a fresh workspace containing only the cloned repository. To share data between steps, you have several options:
volumes:
- name: cache
host:
path: /var/drone/cache/${DRONE_REPO}
- name: artifacts
temp: {}
steps:
- name: build
image: node:20
commands:
- npm ci
- npm run build
- cp -r dist /drone/artifacts/
- name: package
image: alpine:3.18
commands:
- tar czf release.tar.gz /drone/artifacts/
volumes:
- name: artifacts
path: /drone/artifacts
- name: cache-deps
image: node:20
commands:
- npm ci --cache /drone/cache
volumes:
- name: cache
path: /drone/cache
- Host volumes: Mount a directory from the host machine. Useful for persistent caches across builds but ties you to a specific runner host.
- Temporary volumes: Created fresh for each pipeline and destroyed after completion. Perfect for passing artifacts between steps without host coupling.
- Named volumes: Defined in the pipeline and mounted into specific steps. The
temptype creates an ephemeral Docker volume.
The built-in drone workspace is automatically mounted at /drone/src in every step. You can customize this with the workspace block at the pipeline level if your project layout requires a different working directory.
Kubernetes Runner Configuration
When using the Kubernetes runner, the configuration syntax adapts slightly to leverage Kubernetes-native features. Instead of Docker volumes, you can mount ConfigMaps and Secrets directly. Resource limits become available:
kind: pipeline
type: kubernetes
name: k8s-pipeline
workspace:
path: /go/src/github.com/octocat/hello-world
steps:
- name: build
image: golang:1.21
commands:
- go build -o app .
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: 2000m
memory: 1Gi
tolerations:
- key: "dedicated"
operator: "Equal"
value: "ci"
effect: "NoSchedule"
- name: deploy
image: alpine/k8s:1.28
commands:
- kubectl apply -f deployment.yaml
env_file:
- name: kubeconfig
secret_key: kubeconfig_file
The Kubernetes runner gives you access to resources for CPU and memory constraints, tolerations for node scheduling, and env_file for mounting entire secret files as environment variable sources. The underlying mechanism creates a Pod per pipeline step, ensuring isolation through the Kubernetes scheduler.
Matrix Builds
Drone supports matrix builds natively — no YAML templating or external tools required. Define a matrix block with variable dimensions, and Drone expands the pipeline automatically:
kind: pipeline
type: docker
name: test-matrix
steps:
- name: test
image: ${IMAGE}
commands:
- echo "Testing on ${OS} with Go version ${GO_VERSION}"
- go version
- go test ./...
environment:
OS: ${OS}
GO_VERSION: ${GO_VERSION}
matrix:
GO_VERSION:
- "1.20"
- "1.21"
- "1.22"
OS:
- linux
- darwin
IMAGE:
- golang:${GO_VERSION}-${OS}
Drone computes the Cartesian product of all matrix dimensions. For the example above, that yields 3 × 2 = 6 distinct build combinations, each running in parallel. Matrix variables are available via ${VARIABLE} interpolation throughout the pipeline definition. You can combine matrix builds with exclude blocks to skip invalid combinations:
matrix:
GO_VERSION:
- "1.20"
- "1.21"
OS:
- linux
- darwin
exclude:
- GO_VERSION: "1.20"
OS: darwin
Advanced Step Features
Custom Entrypoints and Shells
When you need Bash specifically or a custom entrypoint, override the container defaults:
- name: complex-setup
image: alpine:3.18
entrypoint:
- /bin/bash
- -c
commands:
- |
set -euo pipefail
for dir in src lib test; do
echo "Processing $dir"
ls -la "$dir"
done
Failure Handling
Steps can declare failure strategies. The failure block lets a step execute specifically when previous steps fail — useful for cleanup or notification:
- name: cleanup
image: alpine:3.18
commands:
- rm -rf /tmp/build-artifacts
when:
status: failure
- name: always-notify
image: plugins/slack
settings:
webhook:
from_secret: slack_webhook_url
when:
status:
- success
- failure
Clone Configuration
Drone automatically clones your repository before the first step runs. You can customize clone behavior:
clone:
depth: 50
tags: true
strategy:
method: merge
ref: refs/heads/main
skip_verify: false
lfs: true
submodule:
override: true
recursive: true
include:
- lib/common
- lib/shared
- depth: Shallow clone depth for faster clones on large repositories.
- tags: Whether to fetch Git tags during clone. Essential for versioning workflows.
- lfs: Enable Git LFS support if your repository tracks large files.
- submodule: Control submodule initialization — specify which submodules to include, override repository settings, or enable recursive fetching.
Secrets at the Pipeline Level
Beyond step-level secrets, you can inject secrets into the entire pipeline. This is useful for credentials needed across multiple steps:
kind: pipeline
type: docker
name: production-deploy
environment:
DOCKER_REGISTRY:
from_secret: registry_url
NPM_TOKEN:
from_secret: npm_token
steps:
- name: docker-build
image: plugins/docker
settings:
registry: ${DOCKER_REGISTRY}
username:
from_secret: docker_user
password:
from_secret: docker_pass
- name: npm-publish
image: node:20
commands:
- echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
- npm publish
Pipeline-level environment variables are inherited by all steps. This reduces repetition but remember that each variable is visible in every step's environment — be mindful of the principle of least privilege.
Best Practices for Drone CI Configuration
1. Keep Pipelines Deterministic
Pin container image tags to specific versions rather than using latest. A pipeline that worked yesterday should work identically today. Use digest pinning for critical production pipelines:
image: golang:1.21.6@sha256:abc123...
2. Structure Multi-Pipeline Repositories Clearly
For monorepos with multiple services, create one pipeline per service. Use pipeline-level depends_on to model the dependency graph rather than cramming everything into a single linear pipeline. This enables parallel execution and faster feedback loops.
3. Use Temporary Volumes for Artifact Passing
Avoid writing artifacts to the workspace directory unless you explicitly need them across steps. Temporary volumes are cleaner, isolated, and automatically cleaned up. Host volumes should be reserved for true persistent caching needs.
4. Leverage Built-in Environment Variables
Use DRONE_COMMIT_SHA, DRONE_BUILD_NUMBER, and DRONE_TAG for versioning artifacts rather than computing versions manually. These are guaranteed consistent across the entire pipeline execution.
5. Protect Secrets Aggressively
Never echo secrets in commands. Enable the skip_verify flag on clone only if you fully trust the TLS infrastructure. For pull requests from forks, ensure secret access is restricted — Drone's default behavior of blocking secrets on PRs is a security feature, not a limitation.
6. Use Matrix Builds Instead of Scripting Loops
When testing across multiple versions or platforms, use Drone's native matrix support rather than writing shell loops. Matrix builds are parallelized automatically and produce distinct, individually-rerunnable pipeline runs in the UI.
7. Version Control Your Pipeline Configuration
The .drone.yml file lives in your repository for a reason. Treat it like any other source file: review changes in pull requests, test pipeline modifications in feature branches, and roll back faulty configurations via Git revert. Never rely on UI-based pipeline configuration.
8. Prefer Plugins Over Custom Scripts
Before writing a complex shell script to deploy to AWS, publish to NPM, or notify Slack, check the Drone plugin registry. Plugins are maintained, tested, and handle edge cases like retries and error formatting. Custom scripts should be the exception, not the rule.
9. Implement Pipeline Notifications Early
Add a notification step — Slack, email, or webhook — as the first step after getting a green build. Notifications should include the commit SHA, branch, build number, and a direct link to the build log. This pays dividends when debugging failures later.
10. Use YAML Anchors for Repeated Configurations
When multiple steps share common configuration, use YAML anchors and aliases to reduce duplication:
definitions:
common-env: &common-env
environment:
NODE_ENV: test
CI: "true"
steps:
- name: lint
<<: *common-env
image: node:20
commands:
- npm run lint
- name: test
<<: *common-env
image: node:20
commands:
- npm test
Debugging and Troubleshooting Configuration
When a pipeline fails, Drone provides several mechanisms to diagnose issues:
- Build logs: Every step's output is captured and available in the Drone UI with timestamps. Failed steps are highlighted.
- Debug mode: Add
debug: trueto a step to enable verbose output, including environment variable dumps (secrets remain masked). - CLI validation: Use
drone lintto validate your.drone.ymlbefore committing:
drone lint --repo octocat/hello-world --path .drone.yml
The drone exec command lets you run pipelines locally for rapid iteration:
drone exec --pipeline default --include build --env-file .env.local
Local execution requires Docker running on your machine. It respects the pipeline configuration but skips triggers and some server-side features like secrets (you provide them via the env-file).
Conclusion
Drone CI's configuration model is intentionally simple: one YAML file, container-based steps, explicit dependencies, and declarative conditions. This simplicity scales remarkably well — from single-service hobby projects to monorepos with dozens of interconnected pipelines. By treating your CI configuration as source code, leveraging plugins for common tasks, protecting secrets rigorously, and using matrix builds for cross-platform testing, you build a CI system that is fast, reproducible, and maintainable. The skills you develop writing Drone pipelines translate directly to any container-based CI platform, but Drone's commitment to a single configuration file and native container execution keeps the experience focused and predictable. Start with a single pipeline, add services as needed, split into parallel pipelines when your project grows, and always validate with drone lint before you push.