Development Setup

Verifying Your Development Setup

Assuming you have Go installed, you can quickly verify many elements of your development setup by running:

$ make dev-doctor


You need to have the following tools available in order to effectively contribute to Cilium:


Version / Commit ID

Download Command



N/A (OS-specific)


>= 10.0 (latest recommended)

N/A (OS-specific)


>= 10.0 (latest recommended)

N/A (OS-specific)



N/A (OS-specific)


>= 1.4.0 and < 2.0.0

go install


>= v1.27

N/A (OS-specific)


>= v1.6.0

go install


>= v1.6.0

go install



N/A (OS-specific)



N/A (OS-specific)



N/A (OS-specific)


>= v3.6.0

N/A (OS-specific)

For Integration Testing, you will need to run docker without privileges. You can usually achieve this by adding your current user to the docker group.

Finally, in order to run Cilium locally on VMs, you need:


Version / Commit ID

Download Command


>= 2.0

Vagrant Install Instructions


>= 5.2

N/A (OS-specific)

Vagrant Setup

The setup for the Vagrantfile in the root of the Cilium tree depends on a number of environment variables and network setup that are managed via contrib/vagrant/

Option 2 - Manual Installation

Alternatively you can import the vagrant box cilium/ubuntu directly and manually install Cilium:

$ vagrant init cilium/ubuntu
$ vagrant up
$ vagrant ssh [...]
$ go get
$ cd go/src/
$ make
$ sudo make install
$ sudo mkdir -p /etc/sysconfig/
$ sudo cp contrib/systemd/cilium.service /etc/systemd/system/
$ sudo cp contrib/systemd/cilium-docker.service /etc/systemd/system/
$ sudo cp contrib/systemd/cilium-consul.service /etc/systemd/system/
$ sudo cp contrib/systemd/cilium  /etc/sysconfig/cilium
$ sudo usermod -a -G cilium vagrant
$ sudo systemctl enable cilium-docker
$ sudo systemctl restart cilium-docker
$ sudo systemctl enable cilium-consul
$ sudo systemctl restart cilium-consul
$ sudo systemctl enable cilium
$ sudo systemctl restart cilium


Your Cilium tree is mapped to the VM so that you do not need to keep manually copying files between your host and the VM. Folders are by default synced automatically using VirtualBox Shared Folders with NFS. Note that your host firewall must have a variety of ports open. The Vagrantfile will inform you of the configuration of these addresses and ports to enable NFS.


OSX file system is by default case insensitive, which can confuse git. At the writing of this Cilium repo has no file names that would be considered referring to the same file on a case insensitive file system. Regardless, it may be useful to create a disk image with a case sensitive file system for holding your git repos.


VirtualBox for OSX currently (version 5.1.22) always reports host-only networks’ prefix length as 64. Cilium needs this prefix to be 16, and the startup script will check for this. This check always fails when using VirtualBox on OSX, but it is safe to let the startup script to reset the prefix length to 16.


Make sure your host NFS configuration is setup to use tcp:

# cat /etc/nfs.conf
# grace-time=90
# vers2=n
# vers3=y


Linux 5.18 on newer Intel CPUs which support Intel CET (11th and 12th gen) has a bug that prevents the VMs from starting. If you see a stacktrace with kernel BUG at arch/x86/kernel/traps.c and traps: Missing ENDBR messages in dmesg, that means you are affected. A workaround for now is to pass ibt=off to the kernel command line.

If for some reason, running of the provisioning script fails, you should bring the VM down before trying again:

$ vagrant halt

Local Development in Vagrant Box

See Development Setup for information on how to setup the development environment.

When the development VM is provisioned, it builds and installs Cilium. After the initial build and install you can do further building and testing incrementally inside the VM. vagrant ssh takes you to the Cilium source tree directory (/home/vagrant/go/src/ by default, and the following commands assume that you are working within that directory.

Build Cilium

When you make changes, the tree is automatically kept in sync via NFS. You can issue a build as follows:

$ make

Install to dev environment

After a successful build and test you can re-install Cilium by:

$ sudo -E make install

Restart Cilium service

To run the newly installed version of Cilium, restart the service:

$ sudo systemctl restart cilium

You can verify the service and cilium-agent status by the following commands, respectively:

$ sudo systemctl status cilium
$ cilium status

Simple smoke-test with HTTP policies

After Cilium daemon has been restarted, you may want to verify that it boots up properly and integration with Envoy still works. To do this, run this bash test script:

$ test/envoy/

This test launches three docker containers (one curl client, and two httpd servers) and tests various simple network policies with them. These containers should be automatically removed when the test finishes.

Making Changes

  1. Make sure the master branch of your fork is up-to-date:

    git fetch upstream master:master
  2. Create a PR branch with a descriptive name, branching from master:

    git switch -c pr/changes-to-something master
  3. Make the changes you want.

  4. Separate the changes into logical commits.

    1. Describe the changes in the commit messages. Focus on answering the question why the change is required and document anything that might be unexpected.

    2. If any description is required to understand your code changes, then those instructions should be code comments instead of statements in the commit description.


    For submitting PRs, all commits need be to signed off (git commit -s). See the section Developer’s Certificate of Origin.

  5. Make sure your changes meet the following criteria:

    1. New code is covered by Integration Testing.

    2. End to end integration / runtime tests have been extended or added. If not required, mention in the commit message what existing test covers the new code.

    3. Follow-up commits are squashed together nicely. Commits should separate logical chunks of code and not represent a chronological list of changes.

  6. Run git diff --check to catch obvious white space violations

  7. Run make to build your changes. This will also run make lint and error out on any golang linting errors. The rules are configured in .golangci.yaml

  8. Run make -C bpf checkpatch to validate against your changes coding style and commit messages.

  9. See Integration Testing on how to run integration tests.

  10. See End-To-End Testing Framework for information how to run the end to end integration tests

  11. If you are making documentation changes, you can generate documentation files and serve them locally on http://localhost:9081 by running make render-docs. This make target works both inside and outside the Vagrant VM, assuming that docker is running in the environment.

Add/update a golang dependency

Let’s assume we want to add version v0.5.2:

$ go get
$ go mod tidy
$ go mod vendor
$ git add go.mod go.sum vendor/

For a first run, it can take a while as it will download all dependencies to your local cache but the remaining runs will be faster.

Updating k8s is a special case which requires updating k8s libraries in a single change:

$ # get the tag we are updating (for example ``v0.17.3`` corresponds to k8s ``v1.17.3``)
$ # open go.mod and search and replace all ``v0.17.3`` with the version
$ # that we are trying to upgrade with, for example: ``v0.17.4``.
$ # Close the file and run:
$ go mod tidy
$ go mod vendor
$ make generate-k8s-api
$ git add go.mod go.sum vendor/

Add/update a new Kubernetes version

Let’s assume we want to add a new Kubernetes version v1.19.0:

  1. Follow the above instructions to update the Kubernetes libraries.

  2. Follow the next instructions depending on if it is a minor update or a patch update.

Minor version

  1. Check if it is possible to remove the last supported Kubernetes version from Kubernetes Compatibility, Requirements, Testing matrix, Running Kubernetes Tests, Getting Started Using Istio and add the new Kubernetes version to that list.

  2. If the minimal supported version changed, leave a note in the upgrade guide stating the minimal supported Kubernetes version.

  3. If the minimal supported version changed, search over the code, more likely under pkg/k8s, if there is code that can be removed which specifically exists for the compatibility of the previous Kubernetes minimal version supported.

  4. If the minimal supported version changed, update the field MinimalVersionConstraint in pkg/k8s/version/version.go

  5. Sync all “slim” types by following the instructions in pkg/k8s/slim/ The overall goal is to update changed fields or deprecated fields from the upstream code. New functions / fields / structs added in upstream that are not used in Cilium, can be removed.

  6. Open files jenkinsfiles/{kubernetes-upstream,ginkgo-kernel}.Jenkinsfile, and bump the versions being tested. More important is to make sure the pipeline used on all PRs are running with the new Kubernetes version by default. Make sure the files contributing/testing/{ci,e2e}.rst are up to date with these changes.

  7. Update documentation files: - Documentation/contributing/testing/e2e.rst - Documentation/network/istio.rst - Documentation/network/kubernetes/compatibility.rst - Documentation/network/kubernetes/requirements.rst

  8. Update the Kubernetes version with the newer version in test/Vagrantfile, test/test_suite_test.go and test/

  9. Add the new coredns files specific for the Kubernetes version, for 1.19 is test/provision/manifest/1.19. The coredns deployment files can be found upstream as mentioned in the previous k8s version coredns files. Perform a diff with the previous versions to check which changes are required for our CI and which changes were added upstream.

  10. If necessary, update the coredns files from contrib/vagrant/deployments with newer the file versions from upstream.

  11. Update the constraint in the function getK8sSupportedConstraints, that exists in the test/helpers/utils.go, with the new Kubernetes version that Cilium supports. It is possible that a new IsCiliumV1* var in that file is required as well.

  12. Add the new version in test/provision/, if it is an RC install it using binaries.

  13. Bump the Kubernetes version in contrib/vagrant/scripts/helpers.bash and the etcd version to the latest version.

  14. Run ./contrib/scripts/

  15. Run go mod vendor && go mod tidy

  16. Run ./contrib/scripts/ (again)

  17. Run make -C Documentation update-helm-values

  18. Compile the code locally to make sure all the library updates didn’t removed any used code.

  19. Provision a new dev VM to check if the provisioning scripts work correctly with the new k8s version.

  20. Run git add vendor/ test/provision/manifest/ Documentation/ && git commit -sam "Update k8s tests and libraries to v1.27.0-rc.0"

  21. Submit all your changes into a new PR.

  22. Ping the CI team to make changes in Jenkins (adding new pipeline and dedicated test trigger /test-X.XX-4.19 where X.XX is the new Kubernetes version).

  23. Run /test-upstream-k8s and the new /test-X.XX-4.19 from the PR once Jenkins is up-to-date.

  24. Once CI is green and PR has been merged, ping the CI team again so that they: #. Rotate the Jenkins pipelines and triggers due to removed/added K8s versions.

    1. Update the Cilium CI matrix, .github/maintainers-little-helper.yaml, and GitHub required PR checks accordingly.

Patch version

  1. Bump the Kubernetes version in contrib/vagrant/scripts/helpers.bash.

  2. Bump the Kubernetes version in test/provision/

  3. Submit all your changes into a new PR.

Optional: Docker and IPv6

Note that these instructions are useful to you if you care about having IPv6 addresses for your Docker containers.

If you’d like IPv6 addresses, you will need to follow these steps:

  1. Edit /etc/docker/daemon.json and set the ipv6 key to true.

      "ipv6": true

    If that doesn’t work alone, try assigning a fixed range. Many people have reported trouble with IPv6 and Docker. Source here.

      "ipv6": true,
      "fixed-cidr-v6": "2001:db8:1::/64"

    And then:

    ip -6 route add 2001:db8:1::/64 dev docker0
    sysctl net.ipv6.conf.default.forwarding=1
    sysctl net.ipv6.conf.all.forwarding=1
  2. Restart the docker daemon to pick up the new configuration.

  3. The new command for creating a network managed by Cilium:

    $ docker network create --ipv6 --driver cilium --ipam-driver cilium cilium-net

Now new containers will have an IPv6 address assigned to them.


Datapath code

The tool cilium monitor can also be used to retrieve debugging information from the eBPF based datapath. To enable all log messages:

  • Start the cilium-agent with --debug-verbose=datapath, or

  • Run cilium config debug=true debugLB=true from an already running agent.

These options enable logging functions in the datapath: cilium_dbg(), cilium_dbg_lb() and printk().


The printk() logging function is used by the developer to debug the datapath outside of the cilium monitor. In this case, bpftool prog tracelog can be used to retrieve debugging information from the eBPF based datapath. Both cilium_dbg() and printk() functions are available from the bpf/lib/dbg.h header file.

The image below shows the options that could be used as startup options by cilium-agent (see upper blue box) or could be changed at runtime by running cilium config <option(s)> for an already running agent (see lower blue box). Along with each option, there is one or more logging function associated with it: cilium_dbg() and printk(), for DEBUG and cilium_dbg_lb() for DEBUG_LB.

Cilium debug datapath options


If you need to enable the LB_DEBUG for an already running agent by running cilium config debugLB=true, you must pass the option debug=true along.

Debugging of an individual endpoint can be enabled by running cilium endpoint config ID debug=true. Running cilium monitor -v will print the normal form of monitor output along with debug messages:

$ cilium endpoint config 731 debug=true
Endpoint 731 configuration updated successfully
$ cilium monitor -v
Press Ctrl-C to quit
level=info msg="Initializing dissection cache..." subsys=monitor
<- endpoint 745 flow 0x6851276 identity 4->0 state new ifindex 0 orig-ip 8e:3c:a3:67:cc:1e -> 16:f9:cd:dc:87:e5 ARP
-> lxc_health: 16:f9:cd:dc:87:e5 -> 8e:3c:a3:67:cc:1e ARP
CPU 00: MARK 0xbbe3d555 FROM 0 DEBUG: Inheriting identity=1 from stack
<- host flow 0xbbe3d555 identity 1->0 state new ifindex 0 orig-ip -> tcp ACK
CPU 00: MARK 0xbbe3d555 FROM 0 DEBUG: Successfully mapped addr= to identity=1
CPU 00: MARK 0xbbe3d555 FROM 0 DEBUG: Attempting local delivery for container id 745 from seclabel 1
CPU 00: MARK 0xbbe3d555 FROM 745 DEBUG: Conntrack lookup 1/2: src= dst=
CPU 00: MARK 0xbbe3d555 FROM 745 DEBUG: Conntrack lookup 2/2: nexthdr=6 flags=0
CPU 00: MARK 0xbbe3d555 FROM 745 DEBUG: CT entry found lifetime=21925, revnat=0
CPU 00: MARK 0xbbe3d555 FROM 745 DEBUG: CT verdict: Established, revnat=0
-> endpoint 745 flow 0xbbe3d555 identity 1->4 state established ifindex lxc_health orig-ip -> tcp ACK

Passing -v -v supports deeper detail, for example:

$ cilium endpoint config 3978 debug=true
Endpoint 3978 configuration updated successfully
$ cilium monitor -v -v --hex
Listening for events on 2 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
CPU 00: MARK 0x1c56d86c FROM 3978 DEBUG: 70 bytes Incoming packet from container ifindex 85
00000000  33 33 00 00 00 02 ae 45  75 73 11 04 86 dd 60 00  |33.....Eus....`.|
00000010  00 00 00 10 3a ff fe 80  00 00 00 00 00 00 ac 45  |....:..........E|
00000020  75 ff fe 73 11 04 ff 02  00 00 00 00 00 00 00 00  |u..s............|
00000030  00 00 00 00 00 02 85 00  15 b4 00 00 00 00 01 01  |................|
00000040  ae 45 75 73 11 04 00 00  00 00 00 00              |.Eus........|
CPU 00: MARK 0x1c56d86c FROM 3978 DEBUG: Handling ICMPv6 type=133
CPU 00: MARK 0x1c56d86c FROM 3978 Packet dropped 131 (Invalid destination mac) 70 bytes ifindex=0 284->0
00000000  33 33 00 00 00 02 ae 45  75 73 11 04 86 dd 60 00  |33.....Eus....`.|
00000010  00 00 00 10 3a ff fe 80  00 00 00 00 00 00 ac 45  |....:..........E|
00000020  75 ff fe 73 11 04 ff 02  00 00 00 00 00 00 00 00  |u..s............|
00000030  00 00 00 00 00 02 85 00  15 b4 00 00 00 00 01 01  |................|
00000040  00 00 00 00                                       |....|
CPU 00: MARK 0x7dc2b704 FROM 3978 DEBUG: 86 bytes Incoming packet from container ifindex 85
00000000  33 33 ff 00 8a d6 ae 45  75 73 11 04 86 dd 60 00  |33.....Eus....`.|
00000010  00 00 00 20 3a ff fe 80  00 00 00 00 00 00 ac 45  |... :..........E|
00000020  75 ff fe 73 11 04 ff 02  00 00 00 00 00 00 00 00  |u..s............|
00000030  00 01 ff 00 8a d6 87 00  20 40 00 00 00 00 fd 02  |........ @......|
00000040  00 00 00 00 00 00 c0 a8  21 0b 00 00 8a d6 01 01  |........!.......|
00000050  ae 45 75 73 11 04 00 00  00 00 00 00              |.Eus........|
CPU 00: MARK 0x7dc2b704 FROM 3978 DEBUG: Handling ICMPv6 type=135
CPU 00: MARK 0x7dc2b704 FROM 3978 DEBUG: ICMPv6 neighbour soliciation for address b21a8c0:d68a0000

One of the most common issues when developing datapath code is that the eBPF code cannot be loaded into the kernel. This frequently manifests as the endpoints appearing in the “not-ready” state and never switching out of it:

$ cilium endpoint list
ENDPOINT   POLICY        IDENTITY   LABELS (source:key[=value])   IPv6                     IPv4            STATUS
48896      Disabled      266        container:id.server           fd02::c0a8:210b:0:bf00     not-ready
60670      Disabled      267        container:id.client           fd02::c0a8:210b:0:ecfe   not-ready

Running cilium endpoint get for one of the endpoints will provide a description of known state about it, which includes eBPF verification logs.

The files under /var/run/cilium/state provide context about how the eBPF datapath is managed and set up. The .h files describe specific configurations used for eBPF program compilation. The numbered directories describe endpoint-specific state, including header configuration files and eBPF binaries.

Current eBPF map state for particular programs is held under /sys/fs/bpf/, and the bpf-map utility can be useful for debugging what is going on inside them, for example:

# ls /sys/fs/bpf/tc/globals/
cilium_calls_15124  cilium_calls_48896        cilium_ct4_global       cilium_lb4_rr_seq       cilium_lb6_services  cilium_policy_25729  cilium_policy_60670       cilium_proxy6
cilium_calls_25729  cilium_calls_60670        cilium_ct6_global       cilium_lb4_services     cilium_lxc           cilium_policy_3978   cilium_policy_reserved_1  cilium_reserved_policy
cilium_calls_3978   cilium_calls_netdev_ns_1  cilium_events           cilium_lb6_reverse_nat  cilium_policy        cilium_policy_4314   cilium_policy_reserved_2  cilium_tunnel_map
cilium_calls_4314   cilium_calls_overlay_2    cilium_lb4_reverse_nat  cilium_lb6_rr_seq       cilium_policy_15124  cilium_policy_48896  cilium_proxy4
# bpf-map info /sys/fs/bpf/tc/globals/cilium_policy_15124
Type:           Hash
Key size:       8
Value size:     24
Max entries:    1024
Flags:          0x0
# bpf-map dump /sys/fs/bpf/tc/globals/cilium_policy_15124
00000000  6a 01 00 00 82 23 06 00                           |j....#..|
00000000  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00                           |........|