Jsonnet lets you generate Drone CI/CD pipeline configurations on the fly, treating your pipeline as code.
Let’s see it in action. Imagine you have a project with multiple microservices, and each needs a similar build and deploy pipeline, but with slight variations like the service name and deployment tag.
Here’s a simple Jsonnet file (pipeline.jsonnet):
local services = [
{ name: "auth-service", tag: "v1.0.0" },
{ name: "user-service", tag: "v1.0.1" },
{ name: "product-service", tag: "v1.0.0" },
];
local base_pipeline(service_name, service_tag) = {
kind: "pipeline",
name: "build-and-deploy-" + service_name,
trigger: {
branch: ["main"],
},
steps: [
{
name: "build",
image: "plugins/docker",
settings: {
repo: "my-dockerhub-user/" + service_name,
tags: [service_tag, "latest"],
dockerfile: "Dockerfile",
},
},
{
name: "deploy",
image: "appleboy/drone-k8s",
settings: {
kubeconfig: "builtin.kubeconfig",
namespace: "production",
yaml: "k8s/deployment.yaml",
image: "my-dockerhub-user/" + service_name + ":" + service_tag,
},
},
],
services: ["docker"],
};
{
// Generate a pipeline for each service
pipelines: [base_pipeline(s.name, s.tag) for s in services],
}
When you run jsonnet pipeline.jsonnet from your terminal, it outputs a JSON file that Drone can understand. The output will look something like this:
{
"pipelines": [
{
"kind": "pipeline",
"name": "build-and-deploy-auth-service",
"trigger": {
"branch": [
"main"
]
},
"steps": [
{
"name": "build",
"image": "plugins/docker",
"settings": {
"repo": "my-dockerhub-user/auth-service",
"tags": [
"v1.0.0",
"latest"
],
"dockerfile": "Dockerfile"
}
},
{
"name": "deploy",
"image": "appleboy/drone-k8s",
"settings": {
"kubeconfig": "builtin.kubeconfig",
"namespace": "production",
"yaml": "k8s/deployment.yaml",
"image": "my-dockerhub-user/auth-service:v1.0.0"
}
}
],
"services": [
"docker"
]
},
{
"kind": "pipeline",
"name": "build-and-deploy-user-service",
"trigger": {
"branch": [
"main"
]
},
"steps": [
{
"name": "build",
"image": "plugins/docker",
"settings": {
"repo": "my-dockerhub-user/user-service",
"tags": [
"v1.0.1",
"latest"
],
"dockerfile": "Dockerfile"
}
},
{
"name": "deploy",
"image": "appleboy/drone-k8s",
"settings": {
"kubeconfig": "builtin.kubeconfig",
"namespace": "production",
"yaml": "k8s/deployment.yaml",
"image": "my-dockerhub-user/user-service:v1.0.1"
}
}
],
"services": [
"docker"
]
},
// ... and so on for product-service
]
}
This generated JSON is what you’d commit to your .drone.yml file (or have Drone fetch from a specific location).
The core problem Jsonnet solves is managing repetitive configurations. Instead of copying and pasting similar pipeline definitions, you define a template (base_pipeline) and then iterate over a data structure (services) to generate unique instances. This makes your pipeline definition DRY (Don’t Repeat Yourself), easier to read, and much simpler to update. If you need to change the Docker image prefix or add a new step, you change it in one place in the Jsonnet file.
Internally, Jsonnet is a data templating language. It’s statically scoped, has first-class functions, and supports object composition and inheritance. The local keyword defines variables within a scope. The for loop (for s in services) is used for iteration, and list comprehensions ([...]) allow you to build lists dynamically. The base_pipeline function takes arguments and returns a JSON object representing a single pipeline. The final output is a JSON object with a pipelines key, whose value is a list of these generated pipeline objects.
You control the generation by modifying the services list and the base_pipeline function. You can add more complex logic, conditional steps, or parameterize almost any aspect of your Drone configuration. For example, you could have different deployment targets based on environment variables or branch names.
The most surprising thing about Jsonnet is how it handles merging and inheritance, which can feel like object-oriented programming but for data. You can define a base object and then extend it with specific properties, or even merge multiple objects together using + operator. This allows for very sophisticated configurations where common settings are defined once and then specialized.
The next concept you’ll likely explore is how to manage secrets and sensitive information when generating pipelines dynamically.