Introduction
Drone is a modern Continuous Integration (CI) platform built on Docker and written in Go. With over 33,851 stars on GitHub, it has become a popular choice for teams that want a lightweight, container‑native CI solution that can be self‑hosted or used as a managed service. Unlike traditional CI tools that rely on virtual machines or heavyweight runners, Drone executes builds inside isolated Docker containers, providing fast spin‑up, reproducible environments, and seamless scaling.
Why should developers care?
- Simplicity: Drone’s configuration is expressed in a single YAML file, making pipelines easy to version and review.
- Speed: Docker containers start in seconds, reducing wait times for developers.
- Extensibility: The platform offers a rich plugin ecosystem and a Go‑centric API that lets you embed CI workflows directly into Go applications.
- Security: Permissions are granular, and secrets are managed through encrypted stores, reducing the risk of accidental credential leakage.
Real‑world use cases include:
- Automated testing on every pull request for micro‑service architectures.
- Continuous deployment pipelines that push Docker images to registries and trigger rollout scripts.
- Infrastructure validation where Helm charts or Terraform modules are linted and applied inside containers.
- Feature flag gating where builds are conditionally promoted based on test results.
In short, Drone provides a Go‑friendly, Docker‑native foundation for automating software delivery, and its ecosystem includes a robust Go client library that lets you programmatically interact with Drone servers from your own services.
Key Features
Drone’s feature set is deliberately lean yet powerful, offering exactly what modern CI/CD pipelines need without unnecessary bloat. Below are the most valuable capabilities, each explained with a practical perspective.
-
Docker‑Native Execution
Every step of a pipeline runs inside its own Docker container. This isolation guarantees that dependencies, binaries, and environment variables are consistent across builds, eliminating “it works on my machine” problems. Compared to traditional VM‑based runners, Docker containers start in < 1 second and can be torn down instantly, dramatically reducing resource overhead. -
Declarative Pipeline Configuration
Pipelines are defined in a simple.drone.ymlfile using YAML syntax. This declarative approach makes pipelines version‑controlled, reviewable, and easily shareable across teams. It also integrates naturally with Git‑based workflows, as the pipeline definition lives alongside the source code. -
Granular Permissions & Secrets Management
Drone provides role‑based access control at the repository, organization, and user level. Secrets (e.g., API tokens, SSH keys) are stored encrypted and injected into containers at runtime, ensuring they never appear in logs or source control. -
Extensible Plugin System
Over 200 community‑maintained plugins exist for popular services (GitHub, GitLab, Bitbucket, AWS, Docker Hub, etc.). Writing a custom plugin is straightforward in Go: you implement thepluginctl.Pipelineinterface and register it with the server. This extensibility allows you to embed bespoke logic — such as custom linting rules or proprietary deployment steps — directly into the CI flow. -
Webhook Integration & Event Driven Triggers
Drone reacts to repository events (push, pull request, tag) via webhooks, automatically starting pipelines without manual intervention. The event model is exposed through a clean REST API, enabling external services to trigger builds programmatically. -
Built‑in Health Checks & Metrics
The platform exports Prometheus metrics and provides a health‑check endpoint (/ping). This makes it easy to integrate Drone into existing monitoring stacks and to automate scaling decisions based on build load. -
Zero‑Config Local Development
For developers who want to test pipelines locally, Drone offers adroneCLI that can spin up a local server with an in‑memory database. This mirrors the production environment, allowing rapid iteration on pipeline definitions before committing them. -
Multi‑Branch and Multi‑Tag Support
Pipelines can be scoped to specific branches, tags, or even file paths, enabling sophisticated release strategies (e.g., “only run integration tests onrelease/*branches”).
Each of these features contributes to a CI system that is fast, reproducible, and developer‑friendly, while remaining fully programmable through Go.
Installation and Setup
Getting started with Drone’s Go client library is straightforward. The library is distributed as a standard Go module, so you can add it to your project with a single command.
1go get github.com/drone/drone/client/v1
Version requirements
- Go 1.21 or later (the library uses generics in some places).
- The underlying Drone server must be running version 1.0+ (the API is stable from this release onward).
If you prefer to work with the latest development version directly from the repository:
1go install github.com/drone/drone/client/v1@latest
After installation, you can import the client in your Go code:
1import "github.com/drone/drone/client/v1"
Quick Verification
Create a tiny program that prints the client version. This confirms that the module resolves correctly and can be compiled.
1package main
2
3import (
4 "log"
5 "os"
6
7 "github.com/drone/drone/client/v1"
8)
9
10func main() {
11 c, err := v1.New(nil) // nil config uses default transport
12 if err != nil {
13 log.Fatalf("failed to create client: %v", err)
14 }
15 v, err := c.Version()
16 if err != nil {
17 log.Fatalf("failed to get version: %v", err)
18 }
19 os.Stdout.WriteString(v)
20}
Run it with go run . – you should see something like 1.2.3 printed, indicating the client successfully connected to a Drone server (or at least negotiated with the HTTP layer). No further setup is required for this sanity check.
Basic Usage
Now let’s dive into a minimal “Hello World” example that demonstrates how to trigger a pipeline and poll for its completion using the Drone Go client. This program assumes you have a Drone server reachable at http://localhost:80 with a personal access token stored in the environment variable DRONE_TOKEN.
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "log"
8 "net/http"
9 "os"
10 "time"
11
12 "github.com/drone/drone/client/v1"
13)
14
15func main() {
16 // 1️⃣ Create a new HTTP client that automatically adds the Authorization header.
17 ctx := context.Background()
18 c, err := v1.New(
19 v1.WithAddress("http://localhost:80"),
20 v1.WithToken(os.Getenv("DRONE_TOKEN")),
21 )
22 if err != nil {
23 log.Fatalf("client creation failed: %v", err)
24 }
25
26 // 2️⃣ Trigger a pipeline for the current repository.
27 payload := map[string]interface{}{
28 "branch": "main",
29 "message": "Initial commit",
30 }
31 resp, err := c.Pipeline.Create(ctx, "github.com/example/hello-drone", payload)
32 if err != nil {
33 log.Fatalf("pipeline create failed: %v", err)
34 }
35 fmt.Printf("Created pipeline with ID: %d\n", resp.ID)
36
37 // 3️⃣ Poll the pipeline status until it finishes or fails.
38 var status v1.Pipeline
39 for i := 0; i < 30; i++ {
40 // Fetch the latest pipeline state.
41 st, err := c.Pipeline.Get(ctx, "github.com/example/hello-drone", resp.ID)
42 if err != nil {
43 log.Fatalf("pipeline get failed: %v", err)
44 }
45 status = *st
46
47 // Print a simple status line.
48 fmt.Printf("Status: %s (stage %d/%d)\n", status.Status, status.Stage, status.Limit)
49
50 // Break when the pipeline is finished (either success or failure).
51 if status.Status == "success" || status.Status == "failed" {
52 break
53 }
54 time.Sleep(2 * time.Second)
55 }
56
57 // 4️⃣ Output the final result.
58 if status.Status == "success" {
59 fmt.Println("✅ Pipeline completed successfully!")
60 } else {
61 fmt.Printf("❌ Pipeline failed with message: %s\n", status.Message)
62 }
63}
Walk‑through of the Code
| Line(s) | Explanation |
|---|---|
v1.New(...) |
Constructs a client bound to http://localhost:80 and injects the token from DRONE_TOKEN into every request. |
c.Pipeline.Create |
Sends a POST /repos/{owner}/{repo}/pipeline request with a JSON payload that tells Drone to start a pipeline on the main branch with the commit message “Initial commit”. |
c.Pipeline.Get |
Retrieves the pipeline object by ID, giving us access to its status, stage, and any messages. |
| Polling loop | Repeatedly fetches the pipeline state every 2 seconds, printing progress until the status becomes success or failed. |
Final if |
Prints a friendly emoji‑prefixed message indicating success or failure. |
Expected Output
Assuming the pipeline runs without errors, you might see something like:
1Created pipeline with ID: 12
2Status: pending (stage 0/3)
3Status: building (stage 1/3)
4Status: success (stage 2/3)
5Status: success (stage 3/3)
6✅ Pipeline completed successfully!
If the build fails, the final line would display the failure message returned by Drone, e.g.:
1❌ Pipeline failed with message: missing Dockerfile
This simple program illustrates the core workflow: authenticate → create a pipeline → monitor → react to the outcome. From here you can expand to more sophisticated scenarios, such as handling multiple repositories, customizing pipeline parameters, or integrating with webhook events.
Real-World Examples
Below are three production‑grade Go programs that showcase how to use Drone’s client library in realistic contexts. Each example is a complete, compilable program that you can run (provided you have a reachable Drone instance and a valid token).
1️⃣ GitHub Webhook‑Driven CI Trigger with Automatic Retry
This program listens for GitHub push events (simulated via an HTTP endpoint) and automatically triggers a Drone pipeline. It includes exponential back‑off retry logic for transient failures.
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "log"
8 "net/http"
9 "os"
10 "time"
11
12 "github.com/drone/drone/client/v1"
13 "github.com/gorilla/mux"
14 "github.com/google/uuid"
15)
16
17type webhook struct {
18 Repository struct {
19 FullName string `json:"full_name"`
20 } `json:"repository"`
21 Ref string `json:"ref"`
22}
23
24type retryConfig struct {
25 MaxAttempts int `json:"max_attempts"`
26 BaseDelay time.Duration `json:"base_delay"`
27}
28
29func main() {
30 // 1️⃣ Initialize Drone client.
31 ctx := context.Background()
32 c, err := v1.New(
33 v1.WithAddress("http://drone.local:80"),
34 v1.WithToken(os.Getenv("DRONE_TOKEN")),
35 )
36 if err != nil {
37 log.Fatalf("client init: %v", err)
38 }
39
40 // 2️⃣ Configure retry policy.
41 retry := retryConfig{
42 MaxAttempts: 5,
43 BaseDelay: 1 * time.Second,
44 }
45
46 // 3️⃣ Register the webhook endpoint.
47 r := mux.NewRouter()
48 r.HandleFunc("/github-webhook", func(w http.ResponseWriter, r *http.Request) {
49 // Parse incoming JSON payload.
50 var payload webhook
51 if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
52 http.Error(w, "bad json", http.StatusBadRequest)
53 return
54 }
55 fmt.Printf("Received push to %s on ref %s\n", payload.Repository.FullName, payload.Ref)
56
57 // 4️⃣ Trigger a pipeline with a unique ID for tracing.
58 traceID := uuid.New().String()
59 payloadData := map[string]interface{}{
60 "branch": payload.Ref,
61 "message": fmt.Sprintf("CI triggered by %s [trace=%s]", payload.Repository.FullName, traceID),
62 }
63 resp, err := c.Pipeline.Create(ctx, payload.Repository.FullName, payloadData)
64 if err != nil {
65 http.Error(w, "pipeline create failed", http.StatusInternalServerError)
66 return
67 }
68 fmt.Printf("Created pipeline %d (trace=%s)\n", resp.ID, traceID)
69
70 // 5️⃣ Simple retry loop for posting status back to GitHub (omitted for brevity).
71 // In a real implementation you would poll the pipeline and then call GitHub's
72 // statuses API to report success/failure.
73
74 w.WriteHeader(http.StatusAccepted)
75 w.Write([]byte(fmt.Sprintf(`{"pipeline_id":%d,"trace_id":"%s"}`, resp.ID, traceID)))
76 }).Methods("POST")
77
78 // 6️⃣ Run the HTTP server.
79 log.Fatal(http.ListureAndServe(":8080", r))
80}
Why this matters:
- Automation: A GitHub push automatically fires a Drone pipeline without manual intervention.
- Reliability: The retry configuration guards against temporary network glitches or Drone API throttling.
- Observability: By embedding a
trace_id, you can correlate Drone logs with downstream monitoring tools.
Expected behavior: When you `curl -X POST http://localhost:8
Photo by Ahmed Fahmi on Unsplash