diff --git a/applications/rtdt-manager/app/app.go b/applications/rtdt-manager/app/app.go
index 43572373a2d1ee83559c9ce2a6212fc61b7bbe1f..e0d03fd72380fa578559a85a766e2fd0c0d3e3d3 100644
--- a/applications/rtdt-manager/app/app.go
+++ b/applications/rtdt-manager/app/app.go
@@ -3,52 +3,30 @@ package app
 import (
 	"fmt"
 	"os"
-	"os/signal"
-	"syscall"
 
 	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
 	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
-	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
 	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-manager"
-	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
-	"google.golang.org/grpc"
 )
 
-// An App manages one realnet instance of gosdn and one (or potentially more) twin
+// An App wraps an RtdtManager instance and makes sure the program stays open
 type App struct {
-	gosdnPath string           // Absolute root of gosdn repo on system
-	conn      *grpc.ClientConn // connection to realnet gosdn
-	stopChan  chan os.Signal
-	managers  []*rtdtmanager.RtdtManager // future consideration: launch multiple siblings
+	manager  *rtdtmanager.RtdtManager
+	stopChan <-chan struct{}
 }
 
-func NewApp(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth) *App {
-	gosdnPath, err := util.GenerateGosdnPath()
-	if err != nil {
-		return nil
-	}
+func NewApp(manager *rtdtmanager.RtdtManager, stopChan chan os.Signal) *App {
 	return &App{
-		gosdnPath: gosdnPath,
-		conn:      conn,
-		stopChan:  make(chan os.Signal), // TODO: Should this be shared globally?
+		manager: manager,
 	}
 }
 
-func (app *App) AddManager(rMan *rtdtmanager.RtdtManager) {
-	app.managers = append(app.managers, rMan)
-}
-
-func (app App) GetGosdnPath() string {
-	return app.gosdnPath
-}
-
 // Run the app with manager, catch events and react to them
 func (app *App) Run() error {
-	if len(app.managers) == 0 {
-		fmt.Println("You did not register any managers, please do so before executing App.Run()")
+	if app.manager == nil {
+		fmt.Println("You did not register a manager, please do so before executing App.Run()")
 		return nil
 	}
-	signal.Notify(app.stopChan, os.Interrupt, syscall.SIGSTOP)
 
 	// Run until SIGINT, SIGTERM is received
 	// Based on "code.fbi.h-da.de/danet/gosdn/applications/hostname-checker/app.go"
diff --git a/applications/rtdt-manager/clab-config/clab-config.go b/applications/rtdt-manager/clab-config/clab-config.go
index 04b694c576b46b3657a47a7a3045c10e00746147..2640398dd4ed478c6699d0e053ade4d7f45fb1cb 100644
--- a/applications/rtdt-manager/clab-config/clab-config.go
+++ b/applications/rtdt-manager/clab-config/clab-config.go
@@ -183,23 +183,22 @@ func ClabDestroy(fullPath string) error {
 	fmt.Println("Output of containerlab: ", string(output))
 	time.Sleep(20 * time.Second)
 	return err
-
 }
 
 // Launch a containerlab environment, pass in absolute path of clab yaml
 func ClabDeploy(fullPath string) error {
+    fmt.Println("Deploying file: ", fullPath)
 	cmd := exec.Command("sudo", "containerlab", "deploy", "-t", fullPath, "--reconfigure")
-
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
 	// Run the command in a Goroutine
 	done := make(chan error, 1)
 	stopdeploy := make(chan os.Signal, 1)
 	signal.Notify(stopdeploy, os.Interrupt, syscall.SIGTERM)
 	go func() {
-		output, err := cmd.CombinedOutput() // Use CombinedOutput to capture stdout and stderr
+		err := cmd.Run() // Use CombinedOutput to capture stdout and stderr
 		if err != nil {
 			fmt.Printf("Error during deployment: %s\n", err)
-		} else {
-			fmt.Println(string(output))
 		}
 		done <- err
 		close(done) // Ensure the channel is closed after sending the result
@@ -208,7 +207,7 @@ func ClabDeploy(fullPath string) error {
 	// Wait for the deployment to finish or a signal to stop
 	select {
 	case err := <-done: // Command finished
-		fmt.Println("Successfully deployed the twin")
+		fmt.Println("Successfully deployed containerlab environment")
 		return err
 	case <-stopdeploy: // Signal received to interrupt
 		if err := cmd.Process.Kill(); err != nil {
diff --git a/applications/rtdt-manager/gosdnutil/gosdnutil.go b/applications/rtdt-manager/gosdnutil/gosdnutil.go
new file mode 100644
index 0000000000000000000000000000000000000000..80cc1a36f89d985346fed182aa62dd4bfd2a35e2
--- /dev/null
+++ b/applications/rtdt-manager/gosdnutil/gosdnutil.go
@@ -0,0 +1,27 @@
+package gosdnutil
+
+import (
+	pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
+	rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
+	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
+
+	"fmt"
+	"google.golang.org/grpc"
+)
+
+func FetchPnd(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth) (*pnd.PrincipalNetworkDomain, error) {
+	var p *pnd.PrincipalNetworkDomain
+	pndService := pnd.NewPndServiceClient(conn)
+	ctx := auth.CreateContextWithAuthorization()
+	pndResponse, err := pndService.GetPndList(ctx, &pnd.GetPndListRequest{Timestamp: util.Now()})
+	if err != nil {
+		return nil, fmt.Errorf("Failed to retrieve PND information: %w", err)
+	}
+	if pndList := pndResponse.GetPnd(); pndList != nil {
+		fmt.Println("pndUUID is now: ", pndList[0].Id)
+		p = pndList[0]                                 // TODO: Only supports one pnd for now
+		return p, nil
+	} else {
+		return nil, fmt.Errorf("Failed to retrieve PND from Pnd List: %w", err)
+	}
+}
diff --git a/applications/rtdt-manager/main.go b/applications/rtdt-manager/main.go
index f78258b0b22c16404d9f4a9362a9cd6dc93b20b1..aa3d10578d1ff23a97b963a261f3331364f94946 100644
--- a/applications/rtdt-manager/main.go
+++ b/applications/rtdt-manager/main.go
@@ -3,58 +3,69 @@ package main
 import (
 	"flag"
 	"fmt"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
 
-	app "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/app"
-	rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
 	RtdtMan "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-manager"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials/insecure"
+	venv "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv"
 )
 
 func main() {
 
-	var url string
+	// Global stop channel, should be passed to all venvs and App
+	stopChan := make(chan os.Signal, 1)
+	signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM)
+	var wg sync.WaitGroup
+
+	var address string
 	var pass string
 	var user string
 	var topology_file string
-	flag.StringVar(&url, "url", "172.100.0.5:55055", "Address of the gosdn controller")
-	flag.StringVar(&pass, "p", "TestPassword", "Password for admin user")
-	flag.StringVar(&user, "u", "admin", "Username")
+	var generate bool
+	flag.StringVar(&address, "address", "172.100.0.5:55055", "Address of the gosdn controller")
+	flag.StringVar(&address, "a", "172.100.0.5:55055", "Address of the gosdn controller (shorthand)")
+	flag.StringVar(&pass, "password", "TestPassword", "Password for admin user")
+	flag.StringVar(&pass, "p", "TestPassword", "Password for admin user (shorthand)")
+	flag.StringVar(&user, "user", "admin", "Username")
+	flag.StringVar(&user, "u", "admin", "Username (shorthand)")
+	flag.BoolVar(&generate, "generate", false, "Whether to start the realnet containerlab environment ourselves")
+	flag.BoolVar(&generate, "g", false, "Whether to start the realnet containerlab environment ourselves (shorthand)")
+
 	flag.StringVar(&topology_file, "topology", "data/clab.yaml", "Containerlab file on the basis of which to create topo")
+	flag.StringVar(&topology_file, "t", "data/clab.yaml", "Containerlab file on the basis of which to create topo (shorthand)")
+
+	flag.Usage = func() {
+		fmt.Println("--address, -a: Address of the gosdn controller (realnet)")
+		fmt.Println("--user, -u: User to log into realnet as")
+		fmt.Println("--password, -p: Password for the user to log into realnet as")
+		fmt.Println("--topology, -t: Topology .yaml file to use to generate realnet and twins")
+		fmt.Println("--generate, -g: Whether to start the realnet containerlab environment ourselves")
+	}
 	flag.Parse()
-	fmt.Println("Trying to connect to gosdn controller at ", url)
 	fmt.Println("Topology file path: ", topology_file)
-
-	dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
-	conn, err := grpc.NewClient(url, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))
-	if err != nil {
-		fmt.Println("Failed to create connection")
-		return
+	var realnet *venv.VEnv
+	if generate {
+		fmt.Println("Generate flag is set, starting clab environment for realnet")
+		realnet = venv.NewVEnv("REALNET", topology_file, user, pass, &wg)
+		if realnet == nil {
+			fmt.Println("ERROR: Couldn't deploy the physical network")
+		} else {
+			fmt.Println("Successfully deployed physical network")
+			return
+		}
+	} else {
+		fmt.Println("Generate flag not set!")
 	}
-	fmt.Println("Created connection successfully")
-	defer conn.Close()
 
-	auth := rtdt_auth.NewRtdtAuth(user, url, pass, conn) // logs in and stores token
-	if auth == nil {
-		fmt.Println("Couldn't log in to gosdn, quitting!")
-        return
-	}
-	rtdt_app := app.NewApp(conn, auth)
-	rtdtMan := RtdtMan.NewRtdtManager(conn, auth, topology_file)
+	// Register realnet with rtdt-manager
+	rtdtMan := RtdtMan.NewRtdtManager(realnet, &wg)
 	if rtdtMan == nil {
 		fmt.Println("Couldn't initialize rtdt-manager, quitting!")
 		return
 	}
-	// Register manager with app and run
-
-	rtdt_app.AddManager(rtdtMan)
-	// If twin should be launched, uncomment:
-	//rtdtMan.LaunchTwin("172.101.0.0/16", "2001:db9::/64", "twin")
-
-	err = rtdt_app.Run()
-	if err != nil {
-		fmt.Println("Encountered error in app.Run(): ", err)
-	} else {
-		fmt.Println("Ran without errors")
+	if err := rtdtMan.Run(); err != nil {
+		fmt.Println("Program exited with errors: %w", err)
 	}
 }
diff --git a/applications/rtdt-manager/mneadd/main.go b/applications/rtdt-manager/mneadd/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..9a5fc663cba73b58fe59de96840e59a429697e9a
--- /dev/null
+++ b/applications/rtdt-manager/mneadd/main.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
+	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/danet/gosdn/controller/api"
+	"github.com/google/uuid"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+	"google.golang.org/grpc/metadata"
+)
+
+func main() {
+	gosdn_addr := "172.100.0.5:55055"
+	addr := "172.100.0.11:7030"
+	var sessionToken string
+
+	// Create Connection
+	dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
+	conn, err := grpc.NewClient(gosdn_addr, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))
+	// Log in to controller
+	// Register
+	loginResponse, err := api.Login(context.Background(), gosdn_addr, "admin", "TestPassword")
+	if err != nil {
+		fmt.Println("Couldn't log in to gosdn controller:", err)
+		return
+	} else {
+		fmt.Println("Successully logged in to gosdn controller")
+		time.Sleep(time.Second)
+	}
+	sessionToken = loginResponse.GetToken()
+
+	// Create Context with Authorization
+	md := metadata.Pairs("authorize", sessionToken)
+	ctx := metadata.NewOutgoingContext(context.Background(), md)
+
+	// PND
+	var p *pnd.PrincipalNetworkDomain
+	pndService := pnd.NewPndServiceClient(conn)
+	pndResponse, err := pndService.GetPndList(ctx, &pnd.GetPndListRequest{Timestamp: int64(time.Now().Nanosecond())})
+	if err != nil {
+		fmt.Println("Failed to fetch PND")
+		return
+	} else {
+		fmt.Println("Successfully got PND")
+		time.Sleep(time.Second)
+	}
+
+	// Add MNE
+	p = pndResponse.GetPnd()[0]
+	pndID, _ := uuid.Parse(p.GetId())
+	//fmt.Println("pndID: ", pndID)
+	//pluginUUID, _ := uuid.Parse("823aad29-69be-42f0-b279-90f2c1b6a94d")
+	//pluginID0, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
+
+	pluginID, _ := uuid.Parse("e2c358b3-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
+	//fmt.Println("pluginID: ", pluginID)
+
+	opt := &tpb.TransportOption{
+		Address:  addr,
+		Username: "admin",
+		Password: "admin",
+		Tls:      true,
+		TransportOption: &tpb.TransportOption_GnmiTransportOption{
+			GnmiTransportOption: &tpb.GnmiTransportOption{},
+		},
+	}
+	listResponse, err := api.AddNetworkElement(ctx, gosdn_addr, "1234", "", opt, pluginID, pndID, nil)
+	if err != nil {
+		fmt.Println("Failed to add network element: ", err)
+		return
+	} else {
+		fmt.Println("Successfylly added network element!")
+	}
+	_ = listResponse
+}
diff --git a/applications/rtdt-manager/rtdt-auth/rtdt-auth.go b/applications/rtdt-manager/rtdt-auth/rtdt-auth.go
index e024051d835702305d97583959c34947998eb654..ae15f67b5d3ce7934ed7c978faf46f8fc5cade0d 100644
--- a/applications/rtdt-manager/rtdt-auth/rtdt-auth.go
+++ b/applications/rtdt-manager/rtdt-auth/rtdt-auth.go
@@ -12,7 +12,7 @@ import (
 type RtdtAuth struct {
 	userName   string
 	password   string
-	url        string
+	address    string
 	sessionTok string
 }
 
@@ -29,7 +29,7 @@ func NewRtdtAuth(userName, url, password string, conn *grpc.ClientConn) *RtdtAut
 	return &RtdtAuth{
 		userName:   userName,
 		password:   password,
-		url:        url,
+		address:    url,
 		sessionTok: loginResponse.GetToken(),
 	}
 }
@@ -41,9 +41,15 @@ func (r *RtdtAuth) CreateContextWithAuthorization() context.Context {
 }
 
 // Silly {g,s}etters
-func (r *RtdtAuth) GetSessionToken() string {
+func (r RtdtAuth) GetSessionToken() string {
 	return r.sessionTok
 }
-func (r *RtdtAuth) GetURL() string {
-	return r.url
+func (r RtdtAuth) GetAddress() string {
+	return r.address
+}
+func (r RtdtAuth) GetUsername() string {
+    return r.userName
+}
+func (r RtdtAuth) GetPassword() string {
+    return r.password
 }
diff --git a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go
index fd94b4fe7152412b1a3d4b09f061375e83cec027..78b267a6432f1ee049ab29ff8321c3cb58956e7c 100644
--- a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go
+++ b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go
@@ -3,154 +3,39 @@ package rtdtmanager
 import (
 	"fmt"
 	"os"
-	"os/signal"
 	"path/filepath"
-	"strings"
-	"syscall"
-	"time"
+	"sync"
 
 	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
-	"code.fbi.h-da.de/danet/gosdn/application-framework/registration"
-	"code.fbi.h-da.de/danet/gosdn/controller/api"
-	uuid "github.com/google/uuid"
 
 	//"code.fbi.h-da.de/danet/gosdn/csbi/cmd"
 
-	confManPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement"
-	pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
-	topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
-	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 	clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
-	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
-	util "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
-	"google.golang.org/grpc"
+	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv"
 )
 
+// Manages an environment containing one physical network (realnet) and a digital twin
 type RtdtManager struct {
-	auth             *rtdt_auth.RtdtAuth         // auth struct for realnet gosdn
-	conn             *grpc.ClientConn            // connection to twin's gosdn instance
-	Pnd              *pnd.PrincipalNetworkDomain // PND for realnet gosdn
-	topo             *topoPb.Topology            // Topology with which to create clab.yaml
-	sdnConfig        string                      // TODO
-	clabFilename     string                      // Clab file that exemplifies the topology of realnet
-	clabTwinFilename string
-	clabData         *clabconfig.ClabConfig // Represents the yaml config file as data structure
-	eventService     event.ServiceInterface // Receive events from realnet gosdn (not used yet)
-	stopChan         chan os.Signal         // Global stop channel TODO Can I use that like this?
+	realnet      *venv.VEnv
+	rtdt_twins   []*venv.VEnv
+	eventService event.ServiceInterface // Receive events from realnet gosdn (not used yet)
+	waitGroup    *sync.WaitGroup
+	stopChan     chan os.Signal // Global stop channel TODO Can I use that like this?
 	// TODO auth and conn for virtual net?
 }
 
-func NewRtdtManager(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth, clabFilename string) *RtdtManager {
+// needs to be passed a running realnet VEnv
+func NewRtdtManager(realnet *venv.VEnv, wg *sync.WaitGroup) *RtdtManager {
 	rMan := RtdtManager{
-		conn:         conn,
-		auth:         auth,
-		clabFilename: clabFilename,
-		stopChan:     make(chan os.Signal, 1),
-	}
-	signal.Notify(rMan.stopChan, os.Interrupt, syscall.SIGTERM)
-
-	if err := rMan.fetchPndUUID(); err != nil {
-		fmt.Println(err)
-		return nil
-	}
-	// Event system for realnet gosdn
-	if err := rMan.initEventSystem(); err != nil {
-		fmt.Println("Failed to initialize event system: ", err)
-		return nil
-	}
-
-	// Set up the twin here
-	// The topology of the currently running realnet needs to be parsed. We get this
-	// from the clab.yaml file that was used to create it, that means that --topolog needs to point
-	// to it
-	// TODO Implement
-	var err error
-	if rMan.clabData, err = clabconfig.LoadConfig(rMan.clabFilename); err != nil {
-		fmt.Println(err)
-		return nil
+		realnet:   realnet,
+		waitGroup: wg,
+		stopChan:  make(chan os.Signal, 1),
 	}
 	fmt.Println("Success: RtdtManager created")
 	return &rMan
 }
 
-func (rMan *RtdtManager) fetchPndUUID() error {
-	pndService := pnd.NewPndServiceClient(rMan.conn)
-	ctx := rMan.auth.CreateContextWithAuthorization()
-	pndResponse, err := pndService.GetPndList(ctx, &pnd.GetPndListRequest{Timestamp: util.Now()})
-	if err != nil {
-		return fmt.Errorf("Failed to retrieve PND information: %w", err)
-	}
-	if pndList := pndResponse.GetPnd(); pndList != nil {
-		fmt.Println("pndUUID is now: ", pndList[0].Id) // TODO If more than 1, let user choose
-		rMan.Pnd = pndList[0]
-		return nil
-	} else {
-		return fmt.Errorf("Failed to retrieve PND from Pnd List: %w", err)
-	}
-}
-
-func (rMan *RtdtManager) applyTopology() error {
-	topoService := topoPb.NewTopologyServiceClient(rMan.conn)
-	ctx := rMan.auth.CreateContextWithAuthorization()
-	topoService.AddLink(ctx, &topoPb.AddLinkRequest{Timestamp: util.Now()})
-
-	return nil
-}
-
-// \cite venv-manager
-// Keep for later
-// Write a clab.yaml file to launch a different
-// func (rMan *RtdtManager) writeModifiedTopologyToFile(clabStruct *containerlab.YamlStruct) error {
-// 	rMan.topoData.Mgmt.Network = "gosdn-csbi-arist-twin-net"
-//
-// 	splitMainNetwork := strings.Split(rMan.topoData.Mgmt.Ipv4Subnet, ".")
-// 	splitMainNetwork[1] = "101"
-// 	rMan.topoData.Mgmt.Ipv4Subnet = strings.Join(splitMainNetwork, ".")
-//
-// 	splitMainNetworkIPv6 := strings.Split(rMan.topoData.Mgmt.Ipv6Subnet, ":")
-// 	splitMainNetworkIPv6[1] = "db9"
-// 	rMan.topoData.Mgmt.Ipv6Subnet = strings.Join(splitMainNetworkIPv6, ":")
-//
-// 	// Different network for our twin
-// 	for i, node := range rMan.topoData.Topology.Nodes {
-// 		splitIPv4 := strings.Split(node.MgmtIpv4, ".")
-// 		splitIPv4[1] = "101"
-// 		node.MgmtIpv4 = strings.Join(splitIPv4, ".")
-// 		rMan.topoData.Topology.Nodes[i] = node
-// 	}
-// 	yaml, err := yaml.Marshal(clabStruct)
-// 	if err != nil {
-// 		return err
-// 	}
-//
-// 	fname := "./topo.clab.tmp.yaml"
-// 	err = os.WriteFile(fname, yaml, 0600)
-// 	if err != nil {
-// 		return err
-// 	}
 //
-// 	return nil
-// }
-
-// This retrieves the topology from the running realnet gosdn instance
-// This is needed to generate the clab file to be used with the virtual net
-// TODO Solve this not returning anything
-func (rMan *RtdtManager) fetchTopology() error {
-	topoService := topoPb.NewTopologyServiceClient(rMan.conn)
-	ctx := rMan.auth.CreateContextWithAuthorization()
-	topoResponse, err := topoService.GetTopology(ctx, &topoPb.GetTopologyRequest{Timestamp: util.Now()})
-	if err != nil {
-		return fmt.Errorf("Failed to retrieve Topology: %w", err)
-	}
-	fmt.Printf("Successfully read topology, state is: %s\n", topoResponse.String())
-	fmt.Printf("Length of Links: %d\n", len(topoResponse.Toplogy.Links))
-	fmt.Printf("String of Topology: %s\n", topoResponse.Toplogy.String())
-	for i, link := range topoResponse.Toplogy.GetLinks() {
-		fmt.Printf("index %d, linkID: %s, linkName: %s\n", i, link.GetId(), link.GetName())
-	}
-	rMan.topo = topoResponse.Toplogy
-	return nil
-}
 
 // To launch a new twin, this runs through the following steps:
 // - Load the clab config for current realnet gosdn (passed on cli)
@@ -163,129 +48,89 @@ func (r *RtdtManager) LaunchTwin(twinSubnetIPv4, twinSubnetIPv6, twinName string
 	var derivedConfig *clabconfig.ClabConfig
 	var clabConfigPath string
 	var err error
+	baseConfig := r.realnet.GetClabData()
 
-	if r.clabData == nil {
-		err = fmt.Errorf("Can't launch a twin without loading a clab yaml file for physical network first")
-		return err
-	}
-	if derivedConfig, err = clabconfig.DeriveConfig(r.clabData, twinSubnetIPv4, twinSubnetIPv6, twinName); err != nil {
+	if derivedConfig, err = clabconfig.DeriveConfig(baseConfig, twinSubnetIPv4, twinSubnetIPv6, twinName); err != nil {
 		return fmt.Errorf("Failed to derive config for twin: %w", err)
 	}
+	// Construct filepath for derived clab yaml
 	clabConfigPath, err = clabconfig.ClabConfigPath()
 	clabConfigFullPath := filepath.Join(clabConfigPath, "twin-clab.yaml")
 	if err = clabconfig.WriteConfig(clabConfigFullPath, derivedConfig); err != nil {
 		return fmt.Errorf("Failed to write modified twin clab config to disk: %w", err)
 	}
+
 	// Now run deploy with new config file
 	clabconfig.ClabDeploy(clabConfigFullPath)
-
-	// TODO For each switch in the topology, run mne create
-	for node, val := range r.clabData.Topology.Nodes {
-		if strings.HasPrefix(node, "gnmi-target-") {
-			fmt.Println("Found target: ", node)
-			opt := &tpb.TransportOption{
-				Address:  val.MgmtIPv4,
-				Username: "admin",
-				Password: "admin",
-				TransportOption: &tpb.TransportOption_GnmiTransportOption{
-					GnmiTransportOption: &tpb.GnmiTransportOption{},
-				},
-			}
-			ctx := r.auth.CreateContextWithAuthorization()
-			mneUUID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b")
-			pid, _ := uuid.Parse(r.Pnd.GetId())
-			//			addListResponse, err := api.AddNetworkElement(ctx, val.MgmtIPv4, node, nil, uuid, opt, r.Pnd.GetId(), []string{"/"})
-			api.AddNetworkElement(ctx, val.MgmtIPv4, node, "", opt, mneUUID, pid, []string{"/"})
-		}
-	}
+	user := r.realnet.GetAuth().GetUsername()
+	password := r.realnet.GetAuth().GetPassword()
+	twin := venv.NewVEnv(twinName, clabConfigFullPath, user, password, r.waitGroup)
+	r.rtdt_twins = append(r.rtdt_twins, twin)
 	return nil
 }
 
-// TODO research what is actually returned from the api call
-func (rMan *RtdtManager) manageConfig(op string) error {
-	switch op {
-	// Import the configuration into rtdt-manager (by exporting from gosdn)
-	case "import":
-		fmt.Println("Now importing sdnConfig into rtdt-manager")
-		confManService := confManPb.NewConfigurationManagementServiceClient(rMan.conn)
-		ctx := rMan.auth.CreateContextWithAuthorization()
-		exportSdnConfigResponse, err := confManService.ExportSDNConfig(ctx, &confManPb.ExportSDNConfigRequest{Timestamp: int64(time.Now().Nanosecond())})
-		if err != nil {
-			return fmt.Errorf("Failed to retrieve config file of running goSDN instance: %w", err)
-		}
-		rMan.sdnConfig = exportSdnConfigResponse.SdnConfigData
+// Start the eventsystem for each venv, manage clean up
+func (r *RtdtManager) Run() error {
+	if r.realnet == nil {
+		fmt.Println("You did not start the physical network, please do so before executing App.Run()")
 		return nil
-	// Export from rtdt-manager into gosdn controller
-	case "export":
-		fmt.Println("Now exporting sdnConfig into gosdn controller")
-		//TODO Implement me some day
-		return nil
-	default:
-		return fmt.Errorf("Unknown config operation")
-	}
-}
-
-// Each Manager must have its own event system
-// It receives events based on topics
-func (rMan *RtdtManager) initEventSystem() error {
-	ctx := rMan.auth.CreateContextWithAuthorization()
-	queueCredentials, err := registration.Register(ctx, rMan.auth.GetURL(), "basic-interface-monitoring", "SecurePresharedToken")
-	fmt.Println("Acquired queueCredentials: ", queueCredentials)
-	// TODO: Find out how I can receive the ip address here (it returns rabbitmq)
-	queueCredentials = "amqp://guest:guest@172.100.0.15:5672/" // fix by setting manually
-	if err != nil {
-		return fmt.Errorf("Encountered error while trying to register event system: %w", err)
-	}
-	fmt.Println("Trying to register with amqp with following queueCredentials: ", queueCredentials)
-	// You have to have one event service for a topic
-	eventServiceMNE, err := event.NewEventService(queueCredentials, []event.Topic{event.ManagedNetworkElement})
-	if err != nil {
-		return fmt.Errorf("Failed to attach to event system: %w", err)
 	}
-	eventServiceUser, err := event.NewEventService(queueCredentials, []event.Topic{event.User})
-	if err != nil {
-		return fmt.Errorf("Failed to attach to event system: %w", err)
-	}
-
-	// Can have different callback per type per topic (e.g. adding mne or updating mne)
-	eventServiceMNE.SubscribeToEventType([]event.TypeToCallbackTuple{
-		{Type: event.Type(event.Update), Callback: rMan.updateMNECallback},
-		{Type: event.Type(event.Add), Callback: rMan.updateMNECallback},
-		{Type: event.Type(event.Delete), Callback: rMan.updateMNECallback},
-		{Type: event.Type(event.Subscribe), Callback: rMan.updateMNECallback},
-	})
-	eventServiceUser.SubscribeToEventType([]event.TypeToCallbackTuple{
-		{Type: event.Type(event.Update), Callback: rMan.userEventCallback},
-		{Type: event.Type(event.Add), Callback: rMan.userEventCallback},
-		{Type: event.Type(event.Delete), Callback: rMan.userEventCallback},
-		{Type: event.Type(event.Subscribe), Callback: rMan.userEventCallback},
-	})
-	// Now iterate over all topics of service and create goRoutines
-	// that consumes queue
-	// This function is supposed to be removed in the future?
-	eventServiceMNE.SetupEventReciever(make(chan os.Signal, 1))  // doesn't seem to use stop channel internally..
-	eventServiceUser.SetupEventReciever(make(chan os.Signal, 1)) // doesn't seem to use stop channel internally..
-	fmt.Println("Subscribed to events user and mne")
+	if len(r.rtdt_twins) < 1 {
+		fmt.Println("You did not start any twins, do so first")
+	}
+
+	// Run until SIGINT, SIGTERM is received
+	// Based on "code.fbi.h-da.de/danet/gosdn/applications/hostname-checker/app.go"
+	go func() {
+		for {
+			select {
+			// idea: make channels for different events?
+			case stop := <-r.stopChan:
+				fmt.Print("Received SIGINT/SIGSTOP, quitting..\n")
+				// this takes full path now
+				for _, twin := range r.rtdt_twins {
+					twin_path := twin.GetClabFilePath()
+					err := clabconfig.ClabDestroy(twin_path)
+					if err != nil {
+						fmt.Printf("Error occured while cleaning up twin with name %s: %v\n", twin.GetName(), err)
+					}
+					twin.GetWaitgroup().Done() // indicate twin is done
+				}
+				err := clabconfig.ClabDestroy(r.realnet.GetClabFilePath())
+				if err != nil {
+					fmt.Printf("Couldn't clean up physical network: %v\n", err)
+				}
+				r.realnet.GetWaitgroup().Done()
+				_ = stop
+				return
+			}
+		}
+	}()
+	// This waits for as long as not all venvs have called waitGroup.Done()
+	r.waitGroup.Wait()
 	return nil
 }
 
-func (rMan *RtdtManager) updateMNECallback(event *event.Event) {
-	fmt.Println("MNE Event has happened (type: update), hurray!")
-	fmt.Println("EventID: ", event.ID.ID())
-	fmt.Println("Event Type: ", event.Type)
-}
-
-func (rMan *RtdtManager) userEventCallback(event *event.Event) {
-	fmt.Println("User Event has happened (type: update), hurray!")
-	fmt.Println("EventID: ", event.ID.ID())
-	fmt.Println("Event Type: ", event.Type)
-}
-
-// {Get,Set}ers
-func (r RtdtManager) GetClabFilename() string {
-	return r.clabFilename
-}
-
-func (r RtdtManager) GetClabTwinFilename() string {
-	return r.clabTwinFilename
-}
+// TODO research what is actually returned from the api call
+// func (rMan *RtdtManager) manageConfig(op string) error {
+// 	switch op {
+// 	// Import the configuration into rtdt-manager (by exporting from gosdn)
+// 	case "import":
+// 		fmt.Println("Now importing sdnConfig into rtdt-manager")
+// 		confManService := confManPb.NewConfigurationManagementServiceClient(rMan.conn)
+// 		ctx := rMan.auth.CreateContextWithAuthorization()
+// 		exportSdnConfigResponse, err := confManService.ExportSDNConfig(ctx, &confManPb.ExportSDNConfigRequest{Timestamp: int64(time.Now().Nanosecond())})
+// 		if err != nil {
+// 			return fmt.Errorf("Failed to retrieve config file of running goSDN instance: %w", err)
+// 		}
+// 		rMan.sdnConfig = exportSdnConfigResponse.SdnConfigData
+// 		return nil
+// 	// Export from rtdt-manager into gosdn controller
+// 	case "export":
+// 		fmt.Println("Now exporting sdnConfig into gosdn controller")
+// 		//TODO Implement me some day
+// 		return nil
+// 	default:
+// 		return fmt.Errorf("Unknown config operation")
+// 	}
+// }
diff --git a/applications/rtdt-manager/venv/venv.go b/applications/rtdt-manager/venv/venv.go
new file mode 100644
index 0000000000000000000000000000000000000000..24d1ed2b6f5f69da193e1dcf78f297f2150b6b6c
--- /dev/null
+++ b/applications/rtdt-manager/venv/venv.go
@@ -0,0 +1,239 @@
+package venv
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+	"time"
+
+	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
+	topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
+	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
+	"code.fbi.h-da.de/danet/gosdn/application-framework/registration"
+	clab "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
+	clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
+	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/gosdnutil"
+	rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
+	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
+	"code.fbi.h-da.de/danet/gosdn/controller/api"
+	uuid "github.com/google/uuid"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+)
+
+type VEnv struct {
+	Name         string
+	auth         *rtdt_auth.RtdtAuth
+	conn         *grpc.ClientConn // The connection to this specific environment's gosdn
+	pnd          *pnd.PrincipalNetworkDomain
+	clabData     *clabconfig.ClabConfig // Represents yaml file that was used to deploy
+	clabFilename string                 // This is the name of the yaml file clabData is based on
+	StopChan     <-chan struct{}
+	waitGroup    *sync.WaitGroup
+}
+
+// Accepts a yaml filename to deploy a container lab environment
+func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv {
+	fmt.Println("Creating new virtual environment with name: ", name)
+	wg.Add(1) // Register the venv and run atleast until it calls wg.Done()
+	var err error
+	if err = clab.ClabDeploy(topoYamlFile); err != nil {
+		fmt.Println("Failed to deploy the physical network")
+		return nil
+	}
+
+	// After having deployed it, load the config into clabData
+	var clabData *clab.ClabConfig
+	clabData, err = clab.LoadConfig(topoYamlFile)
+	if err != nil {
+		fmt.Println("Failed to load config from yaml file")
+		return nil
+	}
+
+	// get gosdn address inside clab environment
+	var gosdnAddress string
+	for nodename, val := range clabData.Topology.Nodes {
+		if strings.HasPrefix(nodename, "gosdn") {
+			fmt.Println("Found gosdn ipv4: ", val.MgmtIPv4)
+			gosdnAddress = val.MgmtIPv4 + ":55055"
+		}
+	}
+
+	// Now log into gosdn physical network
+	dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
+	conn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))
+	if err != nil {
+		fmt.Println("Failed to create connection to network")
+		return nil
+	} else {
+		fmt.Println("Successfully created connection to gosdn")
+	}
+	auth := rtdt_auth.NewRtdtAuth(user, gosdnAddress, pass, conn) // logs in and stores token
+	if auth == nil {
+		fmt.Println("Couldn't log in to gosdn physical network, quitting!")
+		return nil
+	} else {
+		fmt.Println("Successfully logged into gosdn")
+	}
+	// Get PND of gosdn in created venv
+	gosdn_pnd, err := gosdnutil.FetchPnd(conn, auth)
+	if err != nil {
+		fmt.Println("Failed to fetch pnd from venv")
+		return nil
+	}
+
+	// Sleep before adding devices because otherwise it won't work
+	// Apply the topoYamlFile: we need to register the switches with the controller
+	for node, val := range clabData.Topology.Nodes {
+		time.Sleep(time.Second * 6)
+		if strings.HasPrefix(node, "gnmi-target-") {
+			//ports := strings.Split(val.Ports[0], ":")
+			//port := ports[0]
+			addr := val.MgmtIPv4 + ":" + "7030"
+
+			opt := &tpb.TransportOption{
+				Address:  addr,
+				Username: "admin",
+				Password: "admin",
+				Tls:      true,
+				TransportOption: &tpb.TransportOption_GnmiTransportOption{
+					GnmiTransportOption: &tpb.GnmiTransportOption{},
+				},
+			}
+			// Openconfig pluginid
+			pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
+			pndID, _ := uuid.Parse(gosdn_pnd.GetId())
+
+			fmt.Println("Found target: ", node, " with addr: ", addr)
+			fmt.Println("Gosdn controller at ", gosdnAddress)
+			ctx := auth.CreateContextWithAuthorization()
+			//listResponse, err := api.AddNetworkElement(ctx, val.MgmtIPv4+":7030", node, "", opt, mneUUID, pid, []string{"/"})
+			listResponse, err := api.AddNetworkElement(ctx, gosdnAddress, "TEST", "", opt, pluginID, pndID, nil)
+			if err != nil {
+				fmt.Println("Failed to add network elements: ", err)
+				return nil
+			} else {
+				fmt.Println("Successfully created network element!!")
+			}
+			fmt.Printf("Got response from AddNetworkElement: %v\n", listResponse)
+			_ = listResponse // TODO: Might need the id's (are they the UUIDs?)
+			fmt.Println("Success: registered mne with gosdn controller")
+		}
+	}
+	return &VEnv{auth: auth,
+		pnd:          gosdn_pnd,
+		conn:         conn,
+		clabData:     clabData,
+		clabFilename: topoYamlFile,
+	}
+}
+func (v *VEnv) initEventSystem() error {
+
+	realnet_auth := v.auth
+	ctx := realnet_auth.CreateContextWithAuthorization()
+	queueCredentials, err := registration.Register(ctx, realnet_auth.GetAddress(), "basic-interface-monitoring", "SecurePresharedToken")
+	fmt.Println("Acquired queueCredentials: ", queueCredentials)
+	// TODO: Find out how I can receive the ip address here (it returns rabbitmq)
+	queueCredentials = "amqp://guest:guest@172.100.0.15:5672/" // TODO: retrieve this from clab struct
+	if err != nil {
+		return fmt.Errorf("Encountered error while trying to register event system: %w", err)
+	}
+	fmt.Println("Trying to register with amqp with following queueCredentials: ", queueCredentials)
+	// You have to have one event service for a topic
+	eventServiceMNE, err := event.NewEventService(queueCredentials, []event.Topic{event.ManagedNetworkElement})
+	if err != nil {
+		return fmt.Errorf("Failed to attach to event system: %w", err)
+	}
+	eventServiceUser, err := event.NewEventService(queueCredentials, []event.Topic{event.User})
+	if err != nil {
+		return fmt.Errorf("Failed to attach to event system: %w", err)
+	}
+
+	// Can have different callback per type per topic (e.g. adding mne or updating mne)
+	eventServiceMNE.SubscribeToEventType([]event.TypeToCallbackTuple{
+		{Type: event.Type(event.Update), Callback: v.updateMNECallback},
+		{Type: event.Type(event.Add), Callback: v.updateMNECallback},
+		{Type: event.Type(event.Delete), Callback: v.updateMNECallback},
+		{Type: event.Type(event.Subscribe), Callback: v.updateMNECallback},
+	})
+	eventServiceUser.SubscribeToEventType([]event.TypeToCallbackTuple{
+		{Type: event.Type(event.Update), Callback: v.userEventCallback},
+		{Type: event.Type(event.Add), Callback: v.userEventCallback},
+		{Type: event.Type(event.Delete), Callback: v.userEventCallback},
+		{Type: event.Type(event.Subscribe), Callback: v.userEventCallback},
+	})
+	// Now iterate over all topics of service and create goRoutines
+	// that consumes queue
+	// This function is supposed to be removed in the future?
+	eventServiceMNE.SetupEventReciever(make(chan os.Signal, 1))  // doesn't seem to use stop channel internally..
+	eventServiceUser.SetupEventReciever(make(chan os.Signal, 1)) // doesn't seem to use stop channel internally..
+	fmt.Println("Subscribed to events user and mne")
+	return nil
+}
+
+func (v *VEnv) updateMNECallback(event *event.Event) {
+	fmt.Println("MNE Event has happened (type: update), hurray!")
+	fmt.Println("EventID: ", event.ID.ID())
+	fmt.Println("Event Type: ", event.Type)
+}
+
+func (v *VEnv) userEventCallback(event *event.Event) {
+	fmt.Println("User Event has happened (type: update), hurray!")
+	fmt.Println("EventID: ", event.ID.ID())
+	fmt.Println("Event Type: ", event.Type)
+}
+
+// For later, topology stuff
+// func (rMan *RtdtManager) applyTopology() error {
+// 	topoService := topoPb.NewTopologyServiceClient(rMan.conn)
+// 	ctx := rMan.auth.CreateContextWithAuthorization()
+// 	topoService.AddLink(ctx, &topoPb.AddLinkRequest{Timestamp: util.Now()})
+//
+// 	return nil
+// }
+
+// This retrieves the topology from the running realnet gosdn instance
+// This is needed to generate the clab file to be used with the virtual net
+// TODO Solve this not returning anything
+func (v *VEnv) fetchTopology() error {
+	topoService := topoPb.NewTopologyServiceClient(v.conn)
+	ctx := v.auth.CreateContextWithAuthorization()
+	topoResponse, err := topoService.GetTopology(ctx, &topoPb.GetTopologyRequest{Timestamp: util.Now()})
+	if err != nil {
+		return fmt.Errorf("Failed to retrieve Topology: %w", err)
+	}
+	fmt.Printf("Successfully read topology, state is: %s\n", topoResponse.String())
+	fmt.Printf("Length of Links: %d\n", len(topoResponse.Toplogy.Links))
+	fmt.Printf("String of Topology: %s\n", topoResponse.Toplogy.String())
+	for i, link := range topoResponse.Toplogy.GetLinks() {
+		fmt.Printf("index %d, linkID: %s, linkName: %s\n", i, link.GetId(), link.GetName())
+	}
+	// TODO: Needs to set topo
+	return nil
+}
+
+// {G,S}ETTERS
+func (v VEnv) GetName() string {
+	return v.Name
+}
+func (v VEnv) GetClabData() *clab.ClabConfig {
+	return v.clabData
+}
+
+func (v VEnv) GetClabFilePath() string {
+	return v.clabFilename
+}
+func (v *VEnv) GetConn() *grpc.ClientConn {
+	return v.conn
+}
+func (v VEnv) GetPnd() *pnd.PrincipalNetworkDomain {
+	return v.pnd
+}
+func (v VEnv) GetAuth() *rtdt_auth.RtdtAuth {
+	return v.auth
+}
+func (v VEnv) GetWaitgroup() *sync.WaitGroup {
+	return v.waitGroup
+}