Skip to content

Commit dc21211

Browse files
author
mirkobrombin
committed
feat: implement dynamic port allocation for Docker containers to avoid conflicts
1 parent 9f1ceaa commit dc21211

File tree

2 files changed

+35
-9
lines changed

2 files changed

+35
-9
lines changed

internal/tools/net.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package tools
2+
3+
import (
4+
"fmt"
5+
"net"
6+
)
7+
8+
// getFreePort returns an available port from a high range.
9+
func GetFreePort() (string, error) {
10+
const basePort = 30000
11+
const maxPort = 40000
12+
for port := basePort; port < maxPort; port++ {
13+
addr := fmt.Sprintf("127.0.0.1:%d", port)
14+
ln, err := net.Listen("tcp", addr)
15+
if err == nil {
16+
ln.Close()
17+
return fmt.Sprintf("%d", port), nil
18+
}
19+
}
20+
return "", fmt.Errorf("no free port available in range %d-%d", basePort, maxPort)
21+
}

plugins/docker_standard.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/mirkobrombin/goup/internal/config"
1414
"github.com/mirkobrombin/goup/internal/plugin"
15+
"github.com/mirkobrombin/goup/internal/tools"
1516
log "github.com/sirupsen/logrus"
1617
)
1718

@@ -29,6 +30,7 @@ type DockerStandardConfig struct {
2930

3031
type dockerStandardState struct {
3132
containerID string
33+
hostPort string // dynamically assigned host port
3234
config DockerStandardConfig
3335
}
3436

@@ -123,8 +125,8 @@ func (d *DockerStandardPlugin) HandleRequest(w http.ResponseWriter, r *http.Requ
123125
return false
124126
}
125127
}
126-
// If proxy path is "/" use the container's root.
127-
targetURL := fmt.Sprintf("http://0.0.0.0:%s", state.config.ContainerPort)
128+
// Build target URL using the assigned host port.
129+
targetURL := fmt.Sprintf("http://0.0.0.0:%s", state.hostPort)
128130
if len(state.config.ProxyPaths) == 1 && state.config.ProxyPaths[0] == "/" {
129131
d.proxyToContainer(targetURL, w, r)
130132
return true
@@ -151,6 +153,7 @@ func (d *DockerStandardPlugin) OnExit() error {
151153
out, err := RunDockerCLI(state.config.CLICommand, state.config.DockerfilePath, "rm", "-f", state.containerID)
152154
d.PluginLogger.Infof("Stopped container for domain %s: %s (err=%v)", domain, out, err)
153155
state.containerID = ""
156+
state.hostPort = ""
154157
}
155158
}
156159
return nil
@@ -170,12 +173,6 @@ func (d *DockerStandardPlugin) ensureContainer(domain string) error {
170173
// Generate unique container name using domain and container port.
171174
containerName := fmt.Sprintf("goup_%s_%s", domain, state.config.ContainerPort)
172175

173-
// Checking if a container with this unique name is already running.
174-
existingID, err := RunDockerCLI(state.config.CLICommand, state.config.DockerfilePath, "ps", "--filter", fmt.Sprintf("name=%s", containerName), "--format", "{{.ID}}")
175-
if err == nil && strings.TrimSpace(existingID) != "" {
176-
state.containerID = strings.TrimSpace(existingID)
177-
return nil
178-
}
179176
d.DomainLogger.Infof("[DockerStandardPlugin] Starting container for domain=%s with tag %s", domain, containerName)
180177
cliCmd := state.config.CLICommand
181178
if cliCmd == "" {
@@ -211,7 +208,14 @@ func (d *DockerStandardPlugin) ensureContainer(domain string) error {
211208
}
212209
d.PluginLogger.Infof("Pull output: %s", pullOutput)
213210
}
214-
runArgs := []string{"run", "-d", "--name", containerName, "-p", fmt.Sprintf("%s:%s", state.config.ContainerPort, state.config.ContainerPort)}
211+
// Allocate a free host port from a high range.
212+
hostPort, err := tools.GetFreePort()
213+
if err != nil {
214+
return fmt.Errorf("failed to get free port: %v", err)
215+
}
216+
217+
// Run container with host port mapping using the free port.
218+
runArgs := []string{"run", "-d", "--name", containerName, "-p", fmt.Sprintf("%s:%s", hostPort, state.config.ContainerPort)}
215219
runArgs = append(runArgs, state.config.RunArgs...)
216220
runArgs = append(runArgs, state.config.ImageName)
217221
d.PluginLogger.Infof("[DockerStandardPlugin] Running container with command: %s %s", cliCmd, strings.Join(runArgs, " "))
@@ -220,6 +224,7 @@ func (d *DockerStandardPlugin) ensureContainer(domain string) error {
220224
return fmt.Errorf("run error: %v, output: %s", err, runOutput)
221225
}
222226
state.containerID = strings.TrimSpace(runOutput)
227+
state.hostPort = hostPort
223228
return nil
224229
}
225230

0 commit comments

Comments
 (0)