feat: NPM remote mode for cross-machine deployments

- Add npm_remote setting: when enabled, proxy forwards to server_ip with
  published host ports instead of Docker container names
- Deployer looks up assigned host port via InspectContainerPort in remote mode
- Auto-remove stale containers with same name before creating new ones
- Add Remote NPM toggle with warning on NPM settings page
- DB migration + schema for npm_remote column
This commit is contained in:
2026-04-05 02:18:06 +03:00
parent f71f2275a2
commit 195ef3e7e5
10 changed files with 77 additions and 12 deletions
+1 -1
View File
@@ -162,7 +162,7 @@ func (d *Deployer) blueGreenDeploy(
}
d.publishDeployStatus(deployID, project.ID, stage.ID, imageTag, "configuring_proxy", "")
proxyRouteID, err = d.configureProxy(ctx, deployID, settings, containerName, project.Port, subdomain)
proxyRouteID, err = d.configureProxy(ctx, deployID, settings, containerID, containerName, project.Port, subdomain)
if err != nil {
return containerID, "", instanceID, fmt.Errorf("configure proxy: %w", err)
}
+28 -3
View File
@@ -306,6 +306,10 @@ func (d *Deployer) executeDeploy(
subdomain := d.buildSubdomain(project, stage, settings, imageTag)
containerName := docker.ContainerName(project.Name, stage.Name, imageTag)
// Remove any stale container with the same name (e.g., from a previous failed deploy).
_ = d.docker.RemoveContainer(ctx, containerName, true)
portStr := fmt.Sprintf("%d/tcp", project.Port)
envVars := d.mergeEnvVars(project, stage.ID)
mounts := d.computeVolumeMounts(project.ID, project.Name, stage.Name, imageTag, settings.BaseVolumePath)
@@ -385,7 +389,7 @@ func (d *Deployer) executeDeploy(
}
d.publishDeployStatus(deployID, project.ID, stage.ID, imageTag, "configuring_proxy", "")
proxyRouteID, err = d.configureProxy(ctx, deployID, settings, containerName, project.Port, subdomain)
proxyRouteID, err = d.configureProxy(ctx, deployID, settings, containerID, containerName, project.Port, subdomain)
if err != nil {
return containerID, proxyRouteID, instanceID, fmt.Errorf("configure proxy: %w", err)
}
@@ -431,19 +435,40 @@ func (d *Deployer) executeDeploy(
// configureProxy creates or updates a proxy route for the deployed container.
// Uses the configured proxy.Provider (NPM, Traefik, or None).
// In NPM remote mode, uses server_ip + published host port instead of container name.
// Returns the proxy route ID string.
func (d *Deployer) configureProxy(
ctx context.Context,
deployID string,
settings store.Settings,
containerID string,
containerName string,
containerPort int,
subdomain string,
) (string, error) {
fqdn := subdomain + "." + settings.Domain
d.logDeploy(deployID, fmt.Sprintf("Configuring proxy (%s): %s -> %s:%d", d.proxy.Name(), fqdn, containerName, containerPort), "info")
routeID, err := d.proxy.ConfigureRoute(ctx, fqdn, containerName, containerPort, proxy.RouteOptions{
forwardHost := containerName
forwardPort := containerPort
// In NPM remote mode, use server_ip and the published host port.
if settings.NpmRemote && settings.ProxyProvider == "npm" {
if settings.ServerIP == "" {
return "", fmt.Errorf("NPM remote mode requires Server IP to be configured in settings")
}
forwardHost = settings.ServerIP
hostPort, err := d.docker.InspectContainerPort(ctx, containerID, fmt.Sprintf("%d/tcp", containerPort))
if err != nil {
return "", fmt.Errorf("look up host port for remote NPM: %w", err)
}
forwardPort = int(hostPort)
d.logDeploy(deployID, fmt.Sprintf("NPM remote mode: using %s:%d (host port)", forwardHost, forwardPort), "info")
}
d.logDeploy(deployID, fmt.Sprintf("Configuring proxy (%s): %s -> %s:%d", d.proxy.Name(), fqdn, forwardHost, forwardPort), "info")
routeID, err := d.proxy.ConfigureRoute(ctx, fqdn, forwardHost, forwardPort, proxy.RouteOptions{
SSLCertificateID: settings.SSLCertificateID,
})
if err != nil {