Docker containers have become a big part in any developer’s life and help in many scenarios from development to production. When I decided to containerize Grafana, my main purpose was to isolate the developer environment from the OS, but it ended up bringing other benefits as well. For example, an easy way of changing Grafana versions in scenarios where I needed to test plugin functionality on different Grafana iterations.
In this post, I’ll describe how to set up a Docker developer environment that lets you attach a debugger to your backend plugin. You’ll extend the official Grafana Docker image with delve, a popular debugger for Go applications, and connect to it remotely from VSCode.
An enjoying debugging experience when developing backend plugins using Docker demands that you address two main challenges:
- Attaching to a process inside a Docker container from an IDE on the host.
- Reattaching to the new process whenever the plugin restarts.
This post mostly focuses on the former, with the latter still requiring some manual work.
You might also be interested in other posts on the topic, such as How to use Docker for plugin development and How to add live reload for panel plugins.
Set up the environment
To debug a Go application in a Docker container, you need to install the debugger (delve) inside the container. In this step, you’ll create a Grafana Dockerfile with delve installed, and a Docker Compose configuration that allows you to debug remotely from the host.
This post assumes the following folder structure:
docker-grafana
│ Dockerfile
│ docker-compose.yaml
│
└───plugins
│ │ grafana-k6-app
│ │ github-datasource
│
└───config
│
│ config.ini
- The
Dockerfile
anddocker-compose.yaml
in the top-level folder. - The
plugins
folder contains the Grafana plugins. - The
config
folder contains theconfig.ini
and any custom configuration.
The Dockerfile
extends the Grafana image with the following:
- mage, for building the backend plugin.
- delve, for debugging.
- Go, for building both mage and delve.
- A
README
with instructions on how to startdelve
.
Dockerfile
FROM grafana/grafana:8.4.5-ubuntu
USER root
WORKDIR /root
RUN apt-get -y update
RUN apt-get -y install git build-essential
RUN curl -L https://golang.org/dl/go1.18.linux-amd64.tar.gz > go1.18.linux-amd64.tar.gz
RUN rm -rf /usr/local/go && \
tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
RUN touch README; printf "~~~~~~ START THE DLV SERVER WITH THIS COMMAND BEFORE RUNNING IDE DEBUGGER ~~~~~~ \r\ndlv attach --headless --listen=:2345 PID\r\n\r\n" >> README
RUN echo "export PATH=$PATH:/usr/local/go/bin:~/go/bin" >> ~/.bashrc
RUN echo "cat ~/README" >> ~/.bashrc
RUN /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@latest
RUN git clone https://github.com/magefile/mage; \
cd mage; \
export PATH=$PATH:/usr/local/go/bin; \
go run bootstrap.go
The docker-compose.yaml
configures the environment, such as Docker network and the folders to mount inside the container.
docker-compose.yaml
version: "3"
networks:
grafana:
services:
grafana-oss:
build: .
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 3000:3000
- 2345:2345
networks:
- grafana
container_name: grafana-oss
command: --config /var/lib/grafana/config.ini
volumes:
- ./data/grafana-oss:/var/lib/grafana
- ./config/config.ini:/var/lib/grafana/config.ini
- ./plugins/grafana-k6-app:/var/lib/grafana/plugins/grafana-k6-app
- ./plugins/github-datasource:/var/lib/grafana/plugins/github-datasource
-
The following allows debugging within the container:
cap_add: - SYS_PTRACE security_opt: - seccomp:unconfined
-
extra_hosts
allows the container to connect to services on the host machine. This allows Grafana to connect to, for example, an Oracle SQL Server located on the host machine when setting up the Oracle data source. -
ports
exposes3000
for the Grafana instance and2345
fordelve
. -
volumes
mounts the plugins found in theplugins
folder, the configuration file, and the license file. Keeping- ./data/grafana-oss:/var/lib/grafana
as a volume saves any progress in the Grafana instance so that everything will still be in place when you restart the container.
In the config.ini file, you can add any configuration you need for your Grafana instance.
config.ini
app_mode = development
instance_name = grafana-oss
[plugins]
enable_alpha = true
app_tls_skip_verify_insecure = false
allow_loading_unsigned_plugins = github-datasource,grafana-k6-app
[auth]
login_cookie_name = grafana_oss_session
[panels]
disable_sanitize_html = false
The environment is ready, and you can now start developing your plugins! Go ahead and fire up the environment by running the following:
docker-compose up -d
You can now access Grafana on localhost:3000.
Build your plugin with debug information
Now that you have a developer environment running, the next step is to build the plugin with debug information. By including debug information in your build, you’ll drastically improve your debugging experience.
To build your plugin with debug information:
-
Connect to bash inside the running container:
docker exec -ti grafana-oss bash
-
Navigate to the plugin folder:
cd /var/lib/grafana/plugins/github-datasource
-
Build the backend plugin with debug information:
mage build:debug
-
Reload the backend plugin:
mage reloadPlugin
Your backend plugin is now running with debug information!
Attach the debugger to the plugin
Before you can start debugging, you need to attach delve to the plugin you want to debug.
To attach delve to a process, you need to know its process ID (PID).
- In your terminal, run
top
. - In the list of processes, look for your plugin, e.g.
gpx_github-datasource
. - Copy the PID, and then exit
top
.
To attach the debugger to the plugin process, run the following command:
dlv attach --headless --listen=:2345 <PID>
- The
headless
flag tells delve to attach to the backend plugin process in headless mode, which lets VS Code remotely connect to delve through a container port (:2345).
You’re now ready to return to your IDE and connect to the delve debugger!
Connect to the debugger from VSCode
The following instructions are for VSCode, but many other IDEs support debugging Go applications using delve.
-
In the root folder of your plugin, create a file called
.vscode/launch.json
with the following content:{ "version": "0.2.0", "configurations": [ { "name": "Debug in Container", "type": "go", "request": "attach", "mode": "remote", "remotePath": "/var/lib/grafana/plugins/github-datasource/", "port": 2345, "host": "127.0.0.1", "apiVersion": 1, "trace": "verbose" } ] }
-
Replace
github-datasource
in theremotePath
to the name of your plugin folder. This needs to be a path within the container. -
In VSCode, click Run and Debug → Debug in Container to start debugging. Note that the plugin will hang whenever it arrives at a breakpoint until you resume execution.
For more information on how to debug an application using VSCode, refer to Debugging.
Improving the debugging experience for Grafana plugins
In this post, you’ve learned how to debug a running backend plugin using VSCode, using Docker and the delve debugger.
The main limitation of this workflow is the lack of support for hot reloading. I imagine that you could build some sort of watchdog that detects changes in your code and runs a script that rebuilds and reloads the plugin and then automatically reattaches delve to it. Or even better: a way for delve to manage the process lifecycle itself based on code changes. I’ve tried the latter approach in the past, without success.
If you have an idea of how to enable hot reloading, please let me know!
Happy debugging!