Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/config_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"encoding/base64"
"sort"
"strings"

"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/registry"
Expand Down Expand Up @@ -42,6 +43,15 @@ type DockerNetworkConfiguration struct {
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
}

// IsContainerNetworkMode returns true if the network mode shares another container's network namespace.
// When using "container:<name>" mode, the container inherits the target container's network stack,
// including hostname, DNS, and network interfaces.
func (c DockerNetworkConfiguration) IsContainerNetworkMode() bool {
// Must have "container:" prefix and at least one character for the container name.
// Docker rejects "container:" without a name with "invalid container format container:".
return strings.HasPrefix(c.Mode, "container:") && len(c.Mode) > len("container:")
}

// DockerConfiguration defines the docker configuration used by the daemon when
// interacting with containers and networks on the system.
type DockerConfiguration struct {
Expand Down
32 changes: 32 additions & 0 deletions config/config_docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package config

import "testing"

// TestDockerNetworkConfiguration_IsContainerNetworkMode tests the IsContainerNetworkMode
// method to ensure it correctly identifies when the network mode is set to share another
// container's network namespace (i.e., "container:<name>" format).
func TestDockerNetworkConfiguration_IsContainerNetworkMode(t *testing.T) {
tests := []struct {
name string
mode string
expected bool
}{
{"container mode with name", "container:caddy", true},
{"container mode with different name", "container:some-vpn-container", true},
{"container mode empty name", "container:", false}, // Docker rejects "container:" without a name
{"default pelican network", "pelican_nw", false},
{"bridge network", "bridge", false},
{"host network", "host", false},
{"empty string", "", false},
{"partial match", "containers", false}, // Should not match without colon
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := DockerNetworkConfiguration{Mode: tt.mode}
if got := c.IsContainerNetworkMode(); got != tt.expected {
t.Errorf("IsContainerNetworkMode() = %v, want %v for mode %q", got, tt.expected, tt.mode)
}
})
}
}
55 changes: 48 additions & 7 deletions environment/docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"

"github.com/pelican-dev/wings/config"
"github.com/pelican-dev/wings/environment"
Expand Down Expand Up @@ -177,15 +178,34 @@ func (e *Environment) Create() error {
labels["Service"] = "Pelican"
labels["ContainerType"] = "server_process"

// Only set hostname/domainname if not using container network mode.
// Containers sharing another container's network namespace inherit that container's
// hostname and domainname, so setting them would cause a Docker API error.
var hostname, domainname string
if !cfg.Docker.Network.IsContainerNetworkMode() {
hostname = e.Id
domainname = cfg.Docker.Domainname
} else {
e.log().WithField("network_mode", cfg.Docker.Network.Mode).
Debug("environment/docker: using container network mode, skipping hostname/domainname configuration")
}

// Port exposure is not allowed when using container network mode since the network
// stack is inherited from the target container. Ports must be exposed on that container instead.
var exposedPorts nat.PortSet
if !cfg.Docker.Network.IsContainerNetworkMode() {
exposedPorts = a.Exposed()
}

conf := &container.Config{
Hostname: e.Id,
Domainname: cfg.Docker.Domainname,
Hostname: hostname,
Domainname: domainname,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
OpenStdin: true,
Tty: true,
ExposedPorts: a.Exposed(),
ExposedPorts: exposedPorts,
Image: strings.TrimPrefix(e.meta.Image, "~"),
Env: e.Configuration.EnvironmentVariables(),
Labels: labels,
Expand All @@ -199,9 +219,14 @@ func (e *Environment) Create() error {
}

networkMode := container.NetworkMode(cfg.Docker.Network.Mode)

// ForceOutgoingIP is incompatible with container network mode since the network
// stack is inherited from the target container. Skip this logic entirely.
if a.ForceOutgoingIP {
// We can't use ForceOutgoingIP if we made a server with no allocation
if a.DefaultMapping.Port != 0 {
if cfg.Docker.Network.IsContainerNetworkMode() {
e.log().WithField("network_mode", cfg.Docker.Network.Mode).
Warn("environment/docker: ForceOutgoingIP is enabled but will be ignored when using container network mode")
} else if a.DefaultMapping.Port != 0 {
enableIPv6 := false
e.log().Debug("environment/docker: forcing outgoing IP address")
networkName := "ip-" + strings.ReplaceAll(strings.ReplaceAll(a.DefaultMapping.Ip, ".", "-"), ":", "-")
Expand Down Expand Up @@ -233,8 +258,24 @@ func (e *Environment) Create() error {
}
}

// DNS settings are inherited when using container network mode.
var dns []string
if !cfg.Docker.Network.IsContainerNetworkMode() {
dns = cfg.Docker.Network.Dns
}

// Port bindings are not allowed when using container network mode since the network
// stack is inherited from the target container. Ports must be published on that container instead.
var portBindings nat.PortMap
if !cfg.Docker.Network.IsContainerNetworkMode() {
portBindings = a.DockerBindings()
} else {
e.log().WithField("network_mode", cfg.Docker.Network.Mode).
Debug("environment/docker: using container network mode, skipping port bindings configuration")
}

hostConf := &container.HostConfig{
PortBindings: a.DockerBindings(),
PortBindings: portBindings,

// Configure the mounts for this container. First mount the server data directory
// into the container as an r/w bind.
Expand All @@ -250,7 +291,7 @@ func (e *Environment) Create() error {
// from the Panel.
Resources: e.Configuration.Limits().AsContainerResources(),

DNS: cfg.Docker.Network.Dns,
DNS: dns,

// Configure logging for the container to make it easier on the Daemon to grab
// the server output. Ensure that we don't use too much space on the host machine
Expand Down
24 changes: 21 additions & 3 deletions server/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,21 @@ func (ip *InstallationProcess) Execute() (string, error) {
ctx, cancel := context.WithCancel(ip.Server.Context())
defer cancel()

// Get config first - must be available before container.Config struct
cfg := config.Get()

// Only set hostname if not using container network mode.
// Containers sharing another container's network namespace inherit that container's hostname.
var hostname string
if !cfg.Docker.Network.IsContainerNetworkMode() {
hostname = "installer"
} else {
ip.Server.Log().WithField("network_mode", cfg.Docker.Network.Mode).
Debug("server/install: using container network mode, skipping hostname configuration")
}

conf := &container.Config{
Hostname: "installer",
Hostname: hostname,
AttachStdout: true,
AttachStderr: true,
AttachStdin: true,
Expand All @@ -431,7 +444,12 @@ func (ip *InstallationProcess) Execute() (string, error) {
},
}

cfg := config.Get()
// DNS settings are inherited when using container network mode.
var dns []string
if !cfg.Docker.Network.IsContainerNetworkMode() {
dns = cfg.Docker.Network.Dns
}

tmpfsSize := strconv.Itoa(int(cfg.Docker.TmpfsSize))
hostConf := &container.HostConfig{
Mounts: []mount.Mount{
Expand All @@ -452,7 +470,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
Tmpfs: map[string]string{
"/tmp": "rw,exec,nosuid,size=" + tmpfsSize + "M",
},
DNS: cfg.Docker.Network.Dns,
DNS: dns,
LogConfig: cfg.Docker.ContainerLogConfig(),
NetworkMode: container.NetworkMode(cfg.Docker.Network.Mode),
UsernsMode: container.UsernsMode(cfg.Docker.UsernsMode),
Expand Down