Skip to content
2 changes: 2 additions & 0 deletions constants/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ const (
NodeMgmtNetBr = "clab-mgmt-net-bridge"
Owner = "clab-owner"
ToolType = "tool-type"
// IsInfrastructure marks containers that are infrastructure components (not user nodes)
IsInfrastructure = "clab-is-infrastructure"
)
66 changes: 66 additions & 0 deletions core/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,3 +702,69 @@ func (c *CLab) CheckConnectivity(ctx context.Context) error {

return nil
}

// DeployInfrastructure deploys infrastructure components like Tailscale VPN
// after the network is created but before nodes are deployed.
func (c *CLab) DeployInfrastructure(ctx context.Context) error {
// Get the docker runtime and deploy infrastructure
if dockerRuntime, ok := c.Runtimes[clabruntimedocker.RuntimeName]; ok {
if dr, ok := dockerRuntime.(*clabruntimedocker.DockerRuntime); ok {
// Create LabContext with the necessary information
labCtx := &clabruntimedocker.LabContext{
Name: c.Config.Name,
Prefix: *c.Config.Prefix,
Owner: clabutils.GetOwner(),
LabDir: c.TopoPaths.TopologyLabDir(),
TopoFile: c.TopoPaths.TopologyFilenameAbsPath(),
}

// Call DeployTailscale with the lab context
// DeployTailscale will check if Tailscale is configured and enabled
if err := dr.DeployTailscale(ctx, labCtx); err != nil {
return fmt.Errorf("failed to deploy Tailscale: %w", err)
}
}
}

return nil
}

// DestroyInfrastructure removes infrastructure components like Tailscale VPN.
func (c *CLab) DestroyInfrastructure(ctx context.Context) error {
// Get the docker runtime and destroy infrastructure
if dockerRuntime, ok := c.Runtimes[clabruntimedocker.RuntimeName]; ok {
if dr, ok := dockerRuntime.(*clabruntimedocker.DockerRuntime); ok {

// Call DestroyTailscale with the lab name
// DestroyTailscale will check if Tailscale is configured and enabled
// We don't return errors here to allow cleanup to continue
if err := dr.DestroyTailscale(ctx, c.Config.Name); err != nil {
log.Warnf("errors during Tailscale destruction: %v", err)
}
}
}

return nil
}

// UpdateInfrastructureDNS updates DNS records in infrastructure components (like Tailscale DNS).
// This should be called after nodes are deployed to populate DNS with node information.
func (c *CLab) UpdateInfrastructureDNS(ctx context.Context) error {
// Get the docker runtime and update DNS
if dockerRuntime, ok := c.Runtimes[clabruntimedocker.RuntimeName]; ok {
if dr, ok := dockerRuntime.(*clabruntimedocker.DockerRuntime); ok {
// Convert Nodes map to interface{} map for UpdateTailscaleDNS
nodesMap := make(map[string]interface{})
for name, node := range c.Nodes {
nodesMap[name] = node
}

// Update Tailscale DNS with node records
if err := dr.UpdateTailscaleDNS(ctx, c.Config.Name, nodesMap); err != nil {
log.Warnf("Failed to update Tailscale DNS: %v", err)
}
}
}

return nil
}
4 changes: 4 additions & 0 deletions core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,10 @@ func (c *CLab) verifyContainersUniqueness(ctx context.Context) error {
// the lab name of a currently deploying lab
// this ensures lab uniqueness
for idx := range containers {
// Skip infrastructure containers (e.g., Tailscale, future VPN/DNS/proxy containers)
if containers[idx].Labels[clabconstants.IsInfrastructure] == "true" {
continue
}
if containers[idx].Labels[clabconstants.Containerlab] == c.Config.Name {
return fmt.Errorf(
"the '%s' lab has already been deployed. Destroy the lab before deploying a "+
Expand Down
10 changes: 10 additions & 0 deletions core/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ func (c *CLab) Deploy( //nolint: funlen
return nil, err
}

// Deploy infrastructure components (like Tailscale) after network creation but before nodes
if err := c.DeployInfrastructure(ctx); err != nil {
return nil, err
}

err = clablinks.SetMgmtNetUnderlyingBridge(c.Config.Mgmt.Bridge)
if err != nil {
return nil, err
Expand Down Expand Up @@ -151,6 +156,11 @@ func (c *CLab) Deploy( //nolint: funlen

execCollection.Log()

// Update infrastructure DNS with deployed node records
if err := c.UpdateInfrastructureDNS(ctx); err != nil {
log.Warnf("Failed to update infrastructure DNS: %v", err)
}

if err := c.GenerateInventories(); err != nil {
return nil, err
}
Expand Down
5 changes: 5 additions & 0 deletions core/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ func (c *CLab) destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) e
}
}

// destroy infrastructure components (like Tailscale) before deleting the network
if err := c.DestroyInfrastructure(ctx); err != nil {
log.Warnf("errors during infrastructure destruction: %v", err)
}

// delete lab management network
if c.Config.Mgmt.Network != "bridge" && !keepMgmtNet {
log.Debugf("Calling DeleteNet method. *CLab.Config.Mgmt value is: %+v", c.Config.Mgmt)
Expand Down
15 changes: 15 additions & 0 deletions docs/manual/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,21 @@ topology:

1. When set to `false`, containerlab will not touch iptables rules. On most docker installations this will result in restricted external access.

#### Tailscale VPN

Containerlab supports automatic deployment of [Tailscale](https://tailscale.com/) VPN to enable secure remote access to your lab's management network. Simply add a `tailscale` section under your management network configuration with your Tailscale auth key, and containerlab will automatically deploy and configure a Tailscale container that advertises your management network routes.

```yaml
name: mylab
mgmt:
network: clab
ipv4-subnet: 172.20.20.0/24
tailscale:
authkey: "tskey-auth-xxxxx-yyyyy"
```

For comprehensive documentation including advanced configuration options, 1:1 NAT support, security considerations, and troubleshooting, see the [Tailscale VPN guide](tailscale.md)

///details | Errors and warnings
External access feature requires nftables kernel API to be present. Kernels newer than v4 typically have this API enabled by default. To understand which API is in use one can issue the following command:

Expand Down
Loading