feat: add support for cap_add and cap_drop compose keys (#238)

This commit is contained in:
Andrey Viktorov
2026-01-10 15:13:03 +11:00
committed by GitHub
parent 1ea1d15845
commit 6c2f85d38a
8 changed files with 68 additions and 0 deletions
+2
View File
@@ -605,6 +605,8 @@ func (s *Server) CreateServiceContainer(
} }
} }
hostConfig := &container.HostConfig{ hostConfig := &container.HostConfig{
CapAdd: spec.Container.CapAdd,
CapDrop: spec.Container.CapDrop,
Binds: spec.Container.Volumes, Binds: spec.Container.Volumes,
Init: spec.Container.Init, Init: spec.Container.Init,
Mounts: mounts, Mounts: mounts,
+12
View File
@@ -225,6 +225,10 @@ func (s *ServiceSpec) Clone() ServiceSpec {
// ContainerSpec defines the desired state of a container in a service. // ContainerSpec defines the desired state of a container in a service.
// ATTENTION: after changing this struct, verify if deploy.EvalContainerSpecChange needs to be updated. // ATTENTION: after changing this struct, verify if deploy.EvalContainerSpecChange needs to be updated.
type ContainerSpec struct { type ContainerSpec struct {
// Specifies which additional capabilities should be added for the container.
CapAdd []string
// Specifies which capabilities should be dropped from the container.
CapDrop []string
// Command overrides the default CMD of the image to be executed when running a container. // Command overrides the default CMD of the image to be executed when running a container.
Command []string Command []string
// Entrypoint overrides the default ENTRYPOINT of the image. // Entrypoint overrides the default ENTRYPOINT of the image.
@@ -343,6 +347,14 @@ func (s *ContainerSpec) Clone() ContainerSpec {
spec.ConfigMounts[i] = cm.Clone() spec.ConfigMounts[i] = cm.Clone()
} }
} }
if s.CapAdd != nil {
spec.CapAdd = make([]string, len(s.CapAdd))
copy(spec.CapAdd, s.CapAdd)
}
if s.CapDrop != nil {
spec.CapDrop = make([]string, len(s.CapDrop))
copy(spec.CapDrop, s.CapDrop)
}
return spec return spec
} }
+6
View File
@@ -209,6 +209,8 @@ func TestServiceSpec_Validate_CaddyAndPorts(t *testing.T) {
func TestContainerSpec_Clone(t *testing.T) { func TestContainerSpec_Clone(t *testing.T) {
mode := os.FileMode(0o644) mode := os.FileMode(0o644)
original := ContainerSpec{ original := ContainerSpec{
CapAdd: []string{"NET_ADMIN"},
CapDrop: []string{"ALL"},
Command: []string{"sh", "-c", "echo hello"}, Command: []string{"sh", "-c", "echo hello"},
Entrypoint: []string{"/bin/bash"}, Entrypoint: []string{"/bin/bash"},
Env: EnvVars{ Env: EnvVars{
@@ -247,6 +249,8 @@ func TestContainerSpec_Clone(t *testing.T) {
// Verify deep copy by modifying the original // Verify deep copy by modifying the original
stringModified := "modified" stringModified := "modified"
original.CapAdd[0] = stringModified
original.CapDrop[0] = stringModified
original.Command[0] = stringModified original.Command[0] = stringModified
original.Entrypoint[0] = stringModified original.Entrypoint[0] = stringModified
original.Env["FOO"] = stringModified original.Env["FOO"] = stringModified
@@ -258,6 +262,8 @@ func TestContainerSpec_Clone(t *testing.T) {
assert.False(t, original.Equals(cloned)) assert.False(t, original.Equals(cloned))
// Assert cloned values are unchanged // Assert cloned values are unchanged
assert.Equal(t, "NET_ADMIN", cloned.CapAdd[0])
assert.Equal(t, "ALL", cloned.CapDrop[0])
assert.Equal(t, "sh", cloned.Command[0]) assert.Equal(t, "sh", cloned.Command[0])
assert.Equal(t, "/bin/bash", cloned.Entrypoint[0]) assert.Equal(t, "/bin/bash", cloned.Entrypoint[0])
assert.Equal(t, "bar", cloned.Env["FOO"]) assert.Equal(t, "bar", cloned.Env["FOO"])
+2
View File
@@ -43,6 +43,8 @@ func ServiceSpecFromCompose(project *types.Project, serviceName string) (api.Ser
spec := api.ServiceSpec{ spec := api.ServiceSpec{
Container: api.ContainerSpec{ Container: api.ContainerSpec{
CapAdd: service.CapAdd,
CapDrop: service.CapDrop,
Command: service.Command, Command: service.Command,
Entrypoint: service.Entrypoint, Entrypoint: service.Entrypoint,
Env: env, Env: env,
+2
View File
@@ -100,6 +100,8 @@ func TestServiceSpecFromCompose(t *testing.T) {
Name: "test", Name: "test",
Mode: api.ServiceModeReplicated, Mode: api.ServiceModeReplicated,
Container: api.ContainerSpec{ Container: api.ContainerSpec{
CapAdd: []string{"NET_ADMIN"},
CapDrop: []string{"ALL"},
Command: []string{"nginx", "updated", "command"}, Command: []string{"nginx", "updated", "command"},
Entrypoint: []string{"/updated-docker-entrypoint.sh"}, Entrypoint: []string{"/updated-docker-entrypoint.sh"},
Env: map[string]string{ Env: map[string]string{
+4
View File
@@ -1,5 +1,9 @@
services: services:
test: test:
cap_add:
- NET_ADMIN
cap_drop:
- ALL
command: ["nginx", "updated", "command"] command: ["nginx", "updated", "command"]
cpus: 0.5 cpus: 0.5
entrypoint: ["/updated-docker-entrypoint.sh"] entrypoint: ["/updated-docker-entrypoint.sh"]
+38
View File
@@ -9,6 +9,44 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEvalContainerSpecChange_ContainerCapAdd(t *testing.T) {
t.Parallel()
currentSpec := api.ServiceSpec{
Container: api.ContainerSpec{
Image: "nginx:latest",
},
}
newSpec := api.ServiceSpec{
Container: api.ContainerSpec{
Image: "nginx:latest",
CapAdd: []string{"NET_ADMIN"},
},
}
assert.Equal(t, ContainerNeedsRecreate, EvalContainerSpecChange(currentSpec, newSpec))
assert.Equal(t, ContainerNeedsRecreate, EvalContainerSpecChange(newSpec, currentSpec))
}
func TestEvalContainerSpecChange_ContainerCapDrop(t *testing.T) {
t.Parallel()
currentSpec := api.ServiceSpec{
Container: api.ContainerSpec{
Image: "nginx:latest",
},
}
newSpec := api.ServiceSpec{
Container: api.ContainerSpec{
Image: "nginx:latest",
CapDrop: []string{"ALL"},
},
}
assert.Equal(t, ContainerNeedsRecreate, EvalContainerSpecChange(currentSpec, newSpec))
assert.Equal(t, ContainerNeedsRecreate, EvalContainerSpecChange(newSpec, currentSpec))
}
func TestEvalContainerSpecChange_ContainerResources(t *testing.T) { func TestEvalContainerSpecChange_ContainerResources(t *testing.T) {
t.Parallel() t.Parallel()
@@ -7,6 +7,8 @@ The following table shows the support status for main Compose features:
|--------------------|--------------------|---------------------------------------------------------------------------------------| |--------------------|--------------------|---------------------------------------------------------------------------------------|
| **Services** | | | | **Services** | | |
| `build` | ⚠️ Limited | Build context and Dockerfile | | `build` | ⚠️ Limited | Build context and Dockerfile |
| `cap_add` | ✅ Supported | Additional kernel capabilities |
| `cap_drop` | ✅ Supported | Which capabilities to drop |
| `command` | ✅ Supported | Override container command | | `command` | ✅ Supported | Override container command |
| `configs` | ✅ Supported | File-based and inline configs | | `configs` | ✅ Supported | File-based and inline configs |
| `cpus` | ✅ Supported | CPU limit | | `cpus` | ✅ Supported | CPU limit |