diff --git a/cmd/hepto/root.go b/cmd/hepto/root.go
index 54a4ed286277369d1324778a5274607c8b3f1982..b16cb83eac6846bd250ed5abe93bbafb5dc47cc6 100644
--- a/cmd/hepto/root.go
+++ b/cmd/hepto/root.go
@@ -21,56 +21,69 @@ var rootCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		logrus.SetLevel(logrus.DebugLevel)
 		if config.Contained {
-			currentIP := WaitForIP()
-			logrus.Debug("Current IP is: ", currentIP.String())
-			c := cluster.New(
-				config.ClusterName, config.NodeName,
-				currentIP, config.DiscoveryPort,
-				config.Anchors.IPSlice(),
-				config.Key,
-			)
-			err := c.Run()
-			if err != nil {
-				logrus.Fatal(err)
-			}
+			run()
 		} else {
-			c, err := selfcontain.New(
-				config.NodeName,
-				config.LibcontainerPath(),
-				config.RootPath(),
-				append(os.Args[1:], "--contained"),
-			)
-			if err != nil {
-				logrus.Fatal(err)
-			}
-			dns := config.DNS.IPSlice()
-			// Use default DNS if required
-			if len(dns) == 0 {
-				dns = defaultDNS
-			}
-			err = c.SetupNetworking(
-				config.Master,
-				*config.IP.IPNet(), config.Gw.IP, dns,
-				config.RootPath(),
-			)
-			if err != nil {
-				logrus.Fatal(err)
-			}
-			s := make(chan os.Signal, 1)
-			signal.Notify(s, os.Interrupt)
-			go func() {
-				<-s
-				c.Destroy()
-			}()
-			err = c.Run()
-			if err != nil {
-				logrus.Fatal(err)
-			}
+			containerize()
 		}
 	},
 }
 
-func WaitForIP() net.IP {
+// Actually run hepto
+func run() {
+	currentIP := waitForIP()
+	logrus.Debug("Current IP is: ", currentIP.String())
+	c := cluster.New(
+		config.ClusterName, config.NodeName,
+		currentIP, config.DiscoveryPort,
+		config.Anchors.IPSlice(),
+		config.Key,
+	)
+	err := c.Run()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+}
+
+// Run ourselves inside a container
+func containerize() {
+	c, err := selfcontain.New(
+		config.NodeName,
+		config.LibcontainerPath(),
+		config.RootPath(),
+		append(os.Args[1:], "--contained"),
+	)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+	dns := config.DNS.IPSlice()
+	// Use default DNS if required
+	if len(dns) == 0 {
+		dns = defaultDNS
+	}
+	err = c.SetupNetworking(
+		config.Master,
+		*config.IP.IPNet(), config.Gw.IP, dns,
+		config.RootPath(),
+	)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+	// Make sure we are notified and that we destroy the
+	// container upon being interrupted
+	s := make(chan os.Signal, 1)
+	signal.Notify(s, os.Interrupt)
+	go func() {
+		<-s
+		c.Destroy()
+	}()
+	err = c.Run()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+}
+
+// Guess the current IP address
+func waitForIP() net.IP {
 	for {
 		logrus.Debug("Determining current IP address")
 		conn, err := net.Dial("udp", fmt.Sprintf("[%s]:53", defaultDNS[0].String()))
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 810552cfdc7280512ff7b4b1943e774f698d6448..f985c4f2b75265a76226f85f87b2d635e6176449 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -1,3 +1,5 @@
+// Hepto cluster implements the main clustering logics, based on
+// sml (Simple Memberlist) for cluster bootstraping and node discovery
 package cluster
 
 import (
diff --git a/pkg/cluster/meta.go b/pkg/cluster/meta.go
index 3bd9f37795875d77dafec6a1424b9b35bf735d61..1fb459122d1fba130c68b51de8fc0f09c8e6310e 100644
--- a/pkg/cluster/meta.go
+++ b/pkg/cluster/meta.go
@@ -6,8 +6,9 @@ import (
 	"forge.tedomum.net/acides/hepto/hepto/pkg/sml"
 )
 
-// Represents a node metadata metadata
+// Represents a node metadata
 type HeptoMeta struct {
+	// Public key for the wireguard mesh VPN
 	VpnKey []byte `json:"vpnKey"`
 }
 
diff --git a/pkg/selfcontain/defaults.go b/pkg/selfcontain/defaults.go
index 39926238d27f1089e8abccc546cb1f391f3d0f5f..c303ee38a3da4b26c791654f275d9288c7e0c6f8 100644
--- a/pkg/selfcontain/defaults.go
+++ b/pkg/selfcontain/defaults.go
@@ -102,7 +102,7 @@ var readOnlyPath = []string{
 	"/proc/bus",
 }
 
-// Make a container configuration
+// Make a container configuration using defaults
 func makeConfig(name string, newRoot string) *configs.Config {
 	deviceRules := []*devices.Rule{}
 	for _, device := range allowedDevices {
diff --git a/pkg/selfcontain/net.go b/pkg/selfcontain/net.go
index 1fa4b74b71505b24b0a5c0b760038d06d6789b4d..9fe86f5be68548a8a8c51fb5e0d0c3440ee106af 100644
--- a/pkg/selfcontain/net.go
+++ b/pkg/selfcontain/net.go
@@ -17,6 +17,9 @@ import (
 const ACCEPT_PINFO = "net.ipv6.conf.eth0.accept_ra_pinfo"
 const ACCEPT_DFTRTR = "net.ipv6.conf.eth0.accept_ra_defrtr"
 
+// Setup networking inside the container
+// This must be called from outside the container, since it requires both access to the
+// host networking stack and the namespace networking stack
 func (c *Container) SetupNetworking(master string, ip net.IPNet, gw net.IP, dns []net.IP, root string) error {
 	ifaceName, err := c.setupIPVlan(master, 1500)
 	if err != nil {
diff --git a/pkg/sml/memberlist.go b/pkg/sml/memberlist.go
index 45374a5d124f5554d688cfc05413d35e17059e97..174311539c25787a65a08636c3a285fa60b5f48d 100644
--- a/pkg/sml/memberlist.go
+++ b/pkg/sml/memberlist.go
@@ -1,3 +1,8 @@
+// Simple Memberlist is a wrapper around hashicorp memberlist, that
+// leverages the gossip protocol and exposes a simpler interface.
+// Main features include: automatic rejoining a list of cluster anchors,
+// providing a channel of node changes, encoding and decoding of node
+// metadata, plus caching of decoded metadata.
 package sml
 
 import (
@@ -24,7 +29,7 @@ type Memberlist struct {
 
 func New(nodeName string, nodeIP net.IP, port int, anchors []net.IP, key []byte, newMeta func() NodeMeta) *Memberlist {
 	config := memberlist.DefaultLANConfig()
-	config.LogOutput = logrus.StandardLogger().Writer()
+	config.LogOutput = logrus.StandardLogger().WriterLevel(logrus.DebugLevel)
 	config.SecretKey = key
 	config.BindAddr = nodeIP.String()
 	config.BindPort = port
@@ -51,6 +56,8 @@ func New(nodeName string, nodeIP net.IP, port int, anchors []net.IP, key []byte,
 	return m
 }
 
+// Start the memberlist cluster main loop, that awaits cluster changes, maintains
+// the cluster state and propagates information to channels
 func (m *Memberlist) Run() error {
 	// Initialize memberlist
 	ml, err := memberlist.Create(m.config)
@@ -64,26 +71,31 @@ func (m *Memberlist) Run() error {
 		case <-ticker:
 			go m.join()
 		case <-m.nodeChanges:
-			logrus.Info("Network topology changed")
+			logrus.Debug("Network topology changed, a node just joined, left or was updated")
 			for _, node := range m.Nodes() {
-				logrus.Debugf("Node %s: %s ; %v\n", node.Name, string(node.Meta), node.NodeMeta)
+				logrus.Debugf("* Node %s: %s ; %v\n", node.Name, string(node.Meta), node.NodeMeta)
 			}
 		}
 	}
 }
 
+// Get the list of current cluster nodes
 func (m *Memberlist) Nodes() []Node {
 	return m.nodeCache
 }
 
+// Build a local node representation from an upstream memberlist node, using
+// empty metadata (before decoding)
 func (m *Memberlist) newNode(mlNode *memberlist.Node) Node {
 	return Node{mlNode, m.newMeta()}
 }
 
+// Update the node cache after a network change, goes through all the
+// nodes and decodes metadata
 func (m *Memberlist) updateCache() {
 	// Do not update the node cache until memberlist is setup
 	if m.ml == nil {
-		logrus.Debug("Not updating the node cache before ml is ready")
+		logrus.Debug("Not updating the node cache before memberlist is ready")
 		return
 	}
 	members := m.ml.Members()
@@ -97,6 +109,7 @@ func (m *Memberlist) updateCache() {
 	m.nodeChanges <- struct{}{}
 }
 
+// Try and join any anchor that is not currently a cluster member
 func (m *Memberlist) join() error {
 	addrs := []string{}
 	for _, candidate := range m.anchors {
@@ -113,7 +126,7 @@ func (m *Memberlist) join() error {
 		addrs = append(addrs, candidate.String())
 	}
 	if len(addrs) > 0 {
-		logrus.Info("Joining nodes: ", addrs)
+		logrus.Info("Joining cluster nodes: ", addrs)
 	}
 	_, err := m.ml.Join(addrs)
 	return err