Repository: firecracker-microvm/firecracker-containerd
Pull request #297 (2019-10-17 22:34:57)

Support DriveMount API in CreateVM.
This implements the proposal found in docs/drive-mounts-proposal.md, with the
exception for supporting RateLimiters and IsReadOnly in the DriveMount objects,
due to the need for further refactoring of the internal stub drive code (will
be followed up in #296).

Signed-off-by: Erik Sipsma <sipsma@amazon.com>

This change will require a fair bit of non-trivial rebasing with the jailer PR, so I'd like to let that PR get merged first and then rebase this one on top if at all possible.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


sipsma: Have the approvals now, but will be waiting until the Jailer PR is merged and do a rebase on top of that before merging this one. Any more comments in the meantime are welcome though of course.
sipsma: Rebased on the Jailer PR. It ended up being straightforward to resolve conflicts, nothing substantive had to change in the drive mount logic or the jailer logic.
sipsma: I just realized that because the jailer config is not enabled by default there currently aren't integ-tests that use both drive mounts and the jailer, I'm gonna update with that before merging too. Looking at the jailer code again I think this will require some updates to the "ExposeDeviceToJail" method in order to handle exposing flat files in addition to exposing block devices.

Patch:
diff --git a/agent/drive_handler.go b/agent/drive_handler.go
index bbb8997..aeec3ac 100644
--- a/agent/drive_handler.go
+++ b/agent/drive_handler.go
@@ -14,12 +14,20 @@
 package main
 
 import (
+	"context"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
+	"github.com/containerd/containerd/log"
+	"github.com/containerd/containerd/mount"
 	"github.com/firecracker-microvm/firecracker-containerd/internal"
+	drivemount "github.com/firecracker-microvm/firecracker-containerd/proto/service/drivemount/ttrpc"
+	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/pkg/errors"
 )
 
 const (
@@ -28,6 +36,14 @@ const (
 	blockMajorMinor = "dev"
 )
 
+var (
+	bannedSystemDirs = []string{
+		"/proc",
+		"/sys",
+		"/dev",
+	}
+)
+
 type drive struct {
 	Name       string
 	DriveID    string
@@ -45,6 +61,8 @@ type driveHandler struct {
 	DrivePath string
 }
 
+var _ drivemount.DriveMounterService = &driveHandler{}
+
 func newDriveHandler(blockPath, drivePath string) (*driveHandler, error) {
 	d := &driveHandler{
 		drives:    map[string]drive{},
@@ -146,3 +164,107 @@ func isStubDrive(d drive) bool {
 
 	return internal.IsStubDrive(f)
 }
+
+func (dh driveHandler) MountDrive(ctx context.Context, req *drivemount.MountDriveRequest) (*empty.Empty, error) {
+	logger := log.G(ctx)
+	logger.Debugf("%+v", req.String())
+	logger = logger.WithField("drive_id", req.DriveID)
+
+	drive, ok := dh.GetDrive(req.DriveID)
+	if !ok {
+		return nil, fmt.Errorf("drive %q could not be found", req.DriveID)
+	}
+	logger = logger.WithField("drive_path", drive.Path())
+
+	// Do a basic check that we won't be mounting over any important system directories
+	resolvedDest, err := evalAnySymlinks(req.DestinationPath)
+	if err != nil {
+		return nil, errors.Wrapf(err,
+			"failed to evaluate any symlinks in drive mount destination %q", req.DestinationPath)
+	}
+
+	for _, systemDir := range bannedSystemDirs {
+		if isOrUnderDir(resolvedDest, systemDir) {
+			return nil, errors.Errorf(
+				"drive mount destination %q resolves to path %q under banned system directory %q",
+				req.DestinationPath, resolvedDest, systemDir,
+			)
+		}
+	}
+
+	err = os.MkdirAll(req.DestinationPath, 0700)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to create drive mount destination %q", req.DestinationPath)
+	}
+
+	// Retry the mount in the case of failure a fixed number of times. This works around a rare issue
+	// where we get to this mount attempt before the guest OS has realized a drive was patched:
+	// https://github.com/firecracker-microvm/firecracker-containerd/issues/214
+	const (
+		maxRetries = 100
+		retryDelay = 10 * time.Millisecond
+	)
+
+	for i := 0; i < maxRetries; i++ {
+		err := mount.All([]mount.Mount{{
+			Source:  drive.Path(),
+			Type:    req.FilesytemType,
+			Options: req.Options,
+		}}, req.DestinationPath)
+		if err == nil {
+			return &empty.Empty{}, nil
+		}
+
+		if isRetryableMountError(err) {
+			logger.WithError(err).Warnf("retryable failure mounting drive")
+			time.Sleep(retryDelay)
+			continue
+		}
+
+		return nil, errors.Wrapf(err, "non-retryable failure mounting drive from %q to %q",
+			drive.Path(), req.DestinationPath)
+	}
+
+	return nil, errors.Errorf("exhausted retries mounting drive from %q to %q",
+		drive.Path(), req.DestinationPath)
+}
+
+// evalAnySymlinks is similar to filepath.EvalSymlinks, except it will not return an error if part of the
+// provided path does not exist. It will evaluate symlinks present in the path up to a component that doesn't
+// exist, at which point it will just append the rest of the provided path to what has been resolved so far.
+// We validate earlier that input to this function is an absolute path.
+func evalAnySymlinks(path string) (string, error) {
+	curPath := "/"
+	pathSplit := strings.Split(filepath.Clean(path), "/")
+	for len(pathSplit) > 0 {
+		curPath = filepath.Join(curPath, pathSplit[0])
+		pathSplit = pathSplit[1:]
+
+		resolvedPath, err := filepath.EvalSymlinks(curPath)
+		if os.IsNotExist(err) {
+			return filepath.Join(append([]string{curPath}, pathSplit...)...), nil
+		}
+		if err != nil {
+			return "", err
+		}
+		curPath = resolvedPath
+	}
+
+	return curPath, nil
+}
+
+// returns whether the given path is the provided baseDir or is under it
+func isOrUnderDir(path, baseDir string) bool {
+	path = filepath.Clean(path)
+	baseDir = filepath.Clean(baseDir)
+
+	if baseDir == "/" {
+		return true
+	}
+
+	if path == baseDir {
+		return true
+	}
+
+	return strings.HasPrefix(path, baseDir+"/")
+}
diff --git a/agent/main.go b/agent/main.go
index f2018bc..d6b2dd1 100644
--- a/agent/main.go
+++ b/agent/main.go
@@ -34,6 +34,8 @@ import (
 	"github.com/firecracker-microvm/firecracker-containerd/eventbridge"
 	"github.com/firecracker-microvm/firecracker-containerd/internal/event"
 	"github.com/firecracker-microvm/firecracker-containerd/internal/vm"
+
+	drivemount "github.com/firecracker-microvm/firecracker-containerd/proto/service/drivemount/ttrpc"
 )
 
 const (
@@ -77,19 +79,25 @@ func main() {
 
 	log.G(shimCtx).Info("creating task service")
 
+	server, err := ttrpc.NewServer()
+	if err != nil {
+		log.G(shimCtx).WithError(err).Fatal("failed to create ttrpc server")
+	}
+
 	eventExchange := &event.ExchangeCloser{Exchange: exchange.NewExchange()}
+	eventbridge.RegisterGetterService(server, eventbridge.NewGetterService(shimCtx, eventExchange))
+
 	taskService, err := NewTaskService(shimCtx, shimCancel, eventExchange)
 	if err != nil {
 		log.G(shimCtx).WithError(err).Fatal("failed to create task service")
 	}
+	taskAPI.RegisterTaskService(server, taskService)
 
-	server, err := ttrpc.NewServer()
+	dh, err := newDriveHandler(blockPath, drivePath)
 	if err != nil {
-		log.G(shimCtx).WithError(err).Fatal("failed to create ttrpc server")
+		log.G(shimCtx).WithError(err).Fatal("failed to create drive handler")
 	}
-
-	taskAPI.RegisterTaskService(server, taskService)
-	eventbridge.RegisterGetterService(server, eventbridge.NewGetterService(shimCtx, eventExchange))
+	drivemount.RegisterDriveMounterService(server, dh)
 
 	// Run ttrpc over vsock
 
diff --git a/agent/service.go b/agent/service.go
index 31e3b8b..3d0484a 100644
--- a/agent/service.go
+++ b/agent/service.go
@@ -17,10 +17,7 @@ import (
 	"context"
 	"fmt"
 	"os"
-	"path/filepath"
 	"runtime/debug"
-	"strings"
-	"time"
 
 	"github.com/containerd/containerd/cio"
 	"github.com/containerd/containerd/log"
@@ -28,18 +25,17 @@ import (
 	"github.com/containerd/containerd/runtime/v2/shim"
 	taskAPI "github.com/containerd/containerd/runtime/v2/task"
 	"github.com/gogo/protobuf/types"
+	"github.com/hashicorp/go-multierror"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	"golang.org/x/sys/unix"
 
+	"github.com/containerd/containerd/mount"
 	"github.com/firecracker-microvm/firecracker-containerd/internal/bundle"
 	"github.com/firecracker-microvm/firecracker-containerd/internal/vm"
 	"github.com/firecracker-microvm/firecracker-containerd/proto"
 )
 
-const (
-	containerRootDir = "/container"
-)
-
 // TaskService represents inner shim wrapper over runc in order to:
 // - Add default namespace to ctx as it's not passed by ttrpc over vsock
 // - Add debug logging to simplify debugging
@@ -48,6 +44,9 @@ type TaskService struct {
 	taskManager vm.TaskManager
 	runcService taskAPI.TaskService
 
+	// map of (exec,task id, as returned by taskExecID func) -> (callback for cleaning up state for the exec)
+	execCleanups map[string][]func() error
+
 	publisher shim.Publisher
 
 	// Normally, it's ill-advised to store a context object in a struct. However,
@@ -64,13 +63,21 @@ type TaskService struct {
 	//
 	// This approach is also taken by containerd's current reference runc shim
 	// v2 implementation
-	shimCtx      context.Context
-	shimCancel   context.CancelFunc
-	driveHandler *driveHandler
+	shimCtx    context.Context
+	shimCancel context.CancelFunc
+}
+
+// provides a unique string for a given (taskID, execID) pair
+func taskExecID(taskID, execID string) string {
+	return fmt.Sprintf("exec-%s-task-%s", execID, taskID)
 }
 
 // NewTaskService creates new runc shim wrapper
-func NewTaskService(shimCtx context.Context, shimCancel context.CancelFunc, publisher shim.Publisher) (taskAPI.TaskService, error) {
+func NewTaskService(
+	shimCtx context.Context,
+	shimCancel context.CancelFunc,
+	publisher shim.Publisher,
+) (taskAPI.TaskService, error) {
 	// We provide an empty string for "id" as the service manages multiple tasks; there is no single
 	// "id" being managed. As noted in the comments of the called code, the "id" arg is only used by
 	// the Cleanup function, so it will never be invoked as part of the task service API, which is all
@@ -80,19 +87,14 @@ func NewTaskService(shimCtx context.Context, shimCancel context.CancelFunc, publ
 		return nil, err
 	}
 
-	dh, err := newDriveHandler(blockPath, drivePath)
-	if err != nil {
-		return nil, err
-	}
-
 	return &TaskService{
-		taskManager: vm.NewTaskManager(shimCtx, log.G(shimCtx)),
-		runcService: runcService,
+		taskManager:  vm.NewTaskManager(shimCtx, log.G(shimCtx)),
+		runcService:  runcService,
+		execCleanups: make(map[string][]func() error),
 
-		publisher:    publisher,
-		shimCtx:      shimCtx,
-		shimCancel:   shimCancel,
-		driveHandler: dh,
+		publisher:  publisher,
+		shimCtx:    shimCtx,
+		shimCancel: shimCancel,
 	}, nil
 }
 
@@ -113,12 +115,26 @@ func unmarshalExtraData(marshalled *types.Any) (*proto.ExtraData, error) {
 	return extraData, nil
 }
 
-func fifoName(taskID, execID string) string {
-	return fmt.Sprintf("exec-%s-task-%s", execID, taskID)
+func (ts *TaskService) addCleanup(taskID, execID string, cleanup func() error) {
+	id := taskExecID(taskID, execID)
+	ts.execCleanups[id] = append(ts.execCleanups[id], cleanup)
+}
+
+func (ts *TaskService) doCleanup(taskID, execID string) error {
+	id := taskExecID(taskID, execID)
+
+	var err *multierror.Error
+	// iterate in reverse order so changes are "unwound" (similar to a defer)
+	for i := len(ts.execCleanups[id]) - 1; i >= 0; i-- {
+		err = multierror.Append(err, ts.execCleanups[id][i]())
+	}
+
+	delete(ts.execCleanups, id)
+	return err.ErrorOrNil()
 }
 
 // Create creates a new initial process and container using runc
-func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTaskRequest) (*taskAPI.CreateTaskResponse, error) {
+func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTaskRequest) (_ *taskAPI.CreateTaskResponse, err error) {
 	defer logPanicAndDie(log.G(requestCtx))
 	taskID := req.ID
 	execID := "" // the exec ID of the initial process in a task is an empty string by containerd convention
@@ -126,6 +142,15 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas
 	logger := log.G(requestCtx).WithField("TaskID", taskID).WithField("ExecID", execID)
 	logger.Info("create")
 
+	defer func() {
+		if err != nil {
+			cleanupErr := ts.doCleanup(taskID, execID)
+			if cleanupErr != nil {
+				logger.WithError(cleanupErr).Error("failed to cleanup task")
+			}
+		}
+	}()
+
 	extraData, err := unmarshalExtraData(req.Options)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to unmarshal extra data")
@@ -134,69 +159,48 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas
 	// Just provide runc the options it knows about, not our wrapper
 	req.Options = extraData.RuncOptions
 
-	// Override the bundle dir and rootfs paths, which were set on the Host and thus not valid here in the Guest
-	bundleDir := bundle.Dir(filepath.Join(containerRootDir, taskID))
-	err = bundleDir.Create()
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to create bundle dir")
-	}
-
-	defer func() {
+	bundleDir := bundle.Dir(req.Bundle)
+	ts.addCleanup(taskID, execID, func() error {
+		err := os.RemoveAll(bundleDir.RootPath())
 		if err != nil {
-			removeErr := os.RemoveAll(bundleDir.RootPath())
-			if removeErr != nil {
-				logger.WithError(removeErr).Error("failed to cleanup bundle dir")
-			}
+			return errors.Wrapf(err, "failed to remove bundle path %q", bundleDir.RootPath())
 		}
-	}()
+		return nil
+	})
 
-	err = bundleDir.OCIConfig().Write(extraData.JsonSpec)
+	// check the rootfs dir has been created (presumed to be by a previous MountDrive call)
+	rootfsStat, err := os.Stat(bundleDir.RootfsPath())
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to write oci config file")
+		return nil, errors.Wrapf(err, "failed to stat bundle's rootfs path %q", bundleDir.RootfsPath())
 	}
-
-	driveID := strings.TrimSpace(extraData.DriveID)
-	drive, ok := ts.driveHandler.GetDrive(driveID)
-	if !ok {
-		return nil, fmt.Errorf("Drive %q could not be found", driveID)
+	if !rootfsStat.IsDir() {
+		return nil, errors.Errorf("bundle's rootfs path %q is not a dir", bundleDir.RootfsPath())
 	}
-
-	const (
-		maxRetries = 100
-		retryDelay = 10 * time.Millisecond
-	)
-
-	// We retry here due to guest kernel needing some time to populate the guest
-	// drive.
-	// https://github.com/firecracker-microvm/firecracker/issues/1159
-	for i := 0; i < maxRetries; i++ {
-		if err = bundleDir.MountRootfs(drive.Path(), "ext4", nil); isRetryableMountError(err) {
-			logger.WithError(err).Warnf("retrying to mount rootfs %q", drive.Path())
-
-			time.Sleep(retryDelay)
-			continue
+	ts.addCleanup(taskID, execID, func() error {
+		err := mount.UnmountAll(bundleDir.RootfsPath(), unix.MNT_DETACH)
+		if err != nil {
+			return errors.Wrapf(err, "failed to unmount bundle rootfs %q", bundleDir.RootfsPath())
 		}
+		return nil
+	})
 
-		break
-	}
-
+	err = bundleDir.OCIConfig().Write(extraData.JsonSpec)
 	if err != nil {
-		return nil, errors.Wrapf(err, "failed to mount rootfs %q", drive.Path())
+		return nil, errors.Wrap(err, "failed to write oci config file")
 	}
 
-	req.Bundle = bundleDir.RootPath()
-	req.Rootfs = nil
-
 	var ioConnectorSet vm.IOProxy
 
 	if vm.IsAgentOnlyIO(req.Stdout, logger) {
 		ioConnectorSet = vm.NewNullIOProxy()
 	} else {
 		// Override the incoming stdio FIFOs, which have paths from the host that we can't use
-		fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName(taskID, execID), req.Terminal)
+		fifoName := taskExecID(taskID, execID)
+		fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName, req.Terminal)
 		if err != nil {
-			logger.WithError(err).Error("failed opening stdio FIFOs")
-			return nil, errors.Wrap(err, "failed to open stdio FIFOs")
+			err = errors.Wrap(err, "failed to open stdio FIFOs")
+			logger.WithError(err).Error()
+			return nil, err
 		}
 
 		var stdinConnectorPair *vm.IOConnectorPair
@@ -231,7 +235,7 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas
 
 	resp, err := ts.taskManager.CreateTask(requestCtx, req, ts.runcService, ioConnectorSet)
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to create runc shim for task")
+		return nil, errors.Wrap(err, "failed to create runc shim")
 	}
 
 	logger.WithField("pid", resp.Pid).Debugf("create succeeded")
@@ -273,16 +277,23 @@ func (ts *TaskService) Start(requestCtx context.Context, req *taskAPI.StartReque
 	return resp, nil
 }
 
-// Delete deletes the initial process and container
+// Delete deletes the process with the provided exec ID
 func (ts *TaskService) Delete(requestCtx context.Context, req *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) {
 	defer logPanicAndDie(log.G(requestCtx))
-	log.G(requestCtx).WithFields(logrus.Fields{"id": req.ID, "exec_id": req.ExecID}).Debug("delete")
+	taskID := req.ID
+	execID := req.ExecID
+	log.G(requestCtx).WithFields(logrus.Fields{"id": taskID, "exec_id": execID}).Debug("delete")
 
 	resp, err := ts.taskManager.DeleteProcess(requestCtx, req, ts.runcService)
 	if err != nil {
 		return nil, err
 	}
 
+	err = ts.doCleanup(taskID, execID)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to cleanup task %q exec %q", taskID, execID)
+	}
+
 	log.G(requestCtx).WithFields(logrus.Fields{
 		"pid":         resp.Pid,
 		"exit_status": resp.ExitStatus,
@@ -366,7 +377,7 @@ func (ts *TaskService) Kill(requestCtx context.Context, req *taskAPI.KillRequest
 }
 
 // Exec runs an additional process inside the container
-func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcessRequest) (*types.Empty, error) {
+func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcessRequest) (_ *types.Empty, err error) {
 	defer logPanicAndDie(log.G(requestCtx))
 
 	taskID := req.ID
@@ -375,6 +386,15 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess
 	logger := log.G(requestCtx).WithField("TaskID", taskID).WithField("ExecID", execID)
 	logger.Debug("exec")
 
+	defer func() {
+		if err != nil {
+			cleanupErr := ts.doCleanup(taskID, execID)
+			if cleanupErr != nil {
+				logger.WithError(cleanupErr).Error("failed to cleanup task")
+			}
+		}
+	}()
+
 	extraData, err := unmarshalExtraData(req.Spec)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to unmarshal extra data")
@@ -383,7 +403,7 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess
 	// Just provide runc the options it knows about, not our wrapper
 	req.Spec = extraData.RuncOptions
 
-	bundleDir := bundle.Dir(filepath.Join(containerRootDir, taskID))
+	bundleDir := bundle.VMBundleDir(taskID)
 
 	var ioConnectorSet vm.IOProxy
 
@@ -391,10 +411,12 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess
 		ioConnectorSet = vm.NewNullIOProxy()
 	} else {
 		// Override the incoming stdio FIFOs, which have paths from the host that we can't use
-		fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName(taskID, execID), req.Terminal)
+		fifoName := taskExecID(taskID, execID)
+		fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName, req.Terminal)
 		if err != nil {
-			logger.WithError(err).Error("failed opening stdio FIFOs")
-			return nil, errors.Wrap(err, "failed to open stdio FIFOs")
+			err = errors.Wrap(err, "failed to open stdio FIFOs")
+			logger.WithError(err).Error()
+			return nil, err
 		}
 
 		var stdinConnectorPair *vm.IOConnectorPair
@@ -404,6 +426,9 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess
 				ReadConnector:  vm.VSockAcceptConnector(extraData.StdinPort),
 				WriteConnector: vm.FIFOConnector(fifoSet.Stdin),
 			}
+			ts.addCleanup(taskID, execID, func() error {
+				return os.RemoveAll(req.Stdin)
+			})
 		}
 
 		var stdoutConnectorPair *vm.IOConnectorPair
@@ -413,6 +438,9 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess
 				ReadConnector:  vm.FIFOConnector(fifoSet.Stdout),
 				WriteConnector: vm.VSockAcceptConnector(extraData.StdoutPort),
 			}
+			ts.addCleanup(taskID, execID, func() error {
+				return os.RemoveAll(req.Stdout)
+			})
 		}
 
 		var stderrConnectorPair *vm.IOConnectorPair
@@ -422,6 +450,9 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess
 				ReadConnector:  vm.FIFOConnector(fifoSet.Stderr),
 				WriteConnector: vm.VSockAcceptConnector(extraData.StderrPort),
 			}
+			ts.addCleanup(taskID, execID, func() error {
+				return os.RemoveAll(req.Stderr)
+			})
 		}
 
 		ioConnectorSet = vm.NewIOConnectorProxy(stdinConnectorPair, stdoutConnectorPair, stderrConnectorPair)
diff --git a/internal/bundle/bundle.go b/internal/bundle/bundle.go
index 775285a..428eb61 100644
--- a/internal/bundle/bundle.go
+++ b/internal/bundle/bundle.go
@@ -19,13 +19,21 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/containerd/containerd/mount"
-
 	"github.com/firecracker-microvm/firecracker-containerd/internal"
 	"github.com/firecracker-microvm/firecracker-containerd/runtime/firecrackeroci"
 	"github.com/pkg/errors"
 )
 
+const (
+	vmBundleRoot = "/container"
+)
+
+// VMBundleDir returns the directory inside a VM at which the bundle directory for
+// the provided taskID should exist.
+func VMBundleDir(taskID string) Dir {
+	return Dir(filepath.Join(vmBundleRoot, taskID))
+}
+
 // Dir represents the root of a container bundle dir. It's just a type wrapper
 // around a string, where the string is the path of the bundle dir.
 type Dir string
@@ -35,12 +43,6 @@ func (d Dir) RootPath() string {
 	return string(d)
 }
 
-// Create will mkdir the bundle RootPath with correct permissions (or no-op if it
-// already exists)
-func (d Dir) Create() error {
-	return os.MkdirAll(d.RootPath(), 0700)
-}
-
 // AddrFilePath is the path to the address file as found in the bundleDir.
 // Even though the shim address is set per-VM, not per-container, containerd expects
 // to find the shim addr file in the bundle dir, so we still have to create it or
@@ -60,21 +62,6 @@ func (d Dir) RootfsPath() string {
 	return filepath.Join(d.RootPath(), internal.BundleRootfsName)
 }
 
-// MountRootfs creates the RootfsPath if it does not exist and mounts the
-// provided sourcePath there.
-func (d Dir) MountRootfs(sourcePath string, mountType string, mountOpts []string) error {
-	err := os.MkdirAll(d.RootfsPath(), 0700)
-	if err != nil {
-		return err
-	}
-
-	return mount.All([]mount.Mount{{
-		Source:  sourcePath,
-		Type:    mountType,
-		Options: mountOpts,
-	}}, d.RootfsPath())
-}
-
 // OCIConfigPath returns the path to the bundle's config.json
 func (d Dir) OCIConfigPath() string {
 	return filepath.Join(d.RootPath(), internal.OCIConfigName)
diff --git a/internal/fsutil.go b/internal/fsutil.go
new file mode 100644
index 0000000..f90614d
--- /dev/null
+++ b/internal/fsutil.go
@@ -0,0 +1,115 @@
+// Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"). You may
+// not use this file except in compliance with the License. A copy of the
+// License is located at
+//
+//	http://aws.amazon.com/apache2.0/
+//
+// or in the "license" file accompanying this file. This file is distributed
+// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+package internal
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/require"
+)
+
+// CreateFSImg will create a file containing a filesystem image of the provided type containing
+// the provided files. It returns the path at which the image file can be found.
+func CreateFSImg(ctx context.Context, t *testing.T, fsType string, testFiles ...FSImgFile) string {
+	t.Helper()
+
+	switch fsType {
+	case "ext3", "ext4":
+		return createTestExtImg(ctx, t, fsType, testFiles...)
+	default:
+		require.FailNowf(t, "unsupported fs type %q", fsType)
+		return ""
+	}
+}
+
+// FSImgFile represents a file intended to be place in a filesystem image (at the provided subpath
+// with the provided contents)
+type FSImgFile struct {
+	Subpath  string
+	Contents string
+}
+
+func createTestExtImg(ctx context.Context, t *testing.T, extName string, testFiles ...FSImgFile) string {
+	t.Helper()
+
+	tempdir, err := ioutil.TempDir("", "")
+	require.NoError(t, err, "failed to create temp dir for ext img")
+
+	for _, testFile := range testFiles {
+		destPath := filepath.Join(tempdir, testFile.Subpath)
+
+		err = os.MkdirAll(filepath.Dir(destPath), 0750)
+		require.NoError(t, err, "failed to mkdir for contents of ext img file")
+
+		err = ioutil.WriteFile(destPath, []byte(testFile.Contents), 0750)
+		require.NoError(t, err, "failed to write file for contents of ext img")
+	}
+
+	imgFile, err := ioutil.TempFile("", "")
+	require.NoError(t, err, "failed to obtain temp file for ext img")
+
+	output, err := exec.CommandContext(ctx, "mkfs."+extName, "-d", tempdir, imgFile.Name(), "65536").CombinedOutput()
+	require.NoErrorf(t, err, "failed to create ext img, command output:\n %s", string(output))
+	return imgFile.Name()
+}
+
+// MountInfo holds data parsed from a line of /proc/mounts
+type MountInfo struct {
+	SourcePath string
+	DestPath   string
+	Type       string
+	Options    []string
+}
+
+// ParseProcMountLines converts the provided strings, presumed to be lines read from /proc/mounts
+// into MountInfo objects holding the parsed data about each mount.
+func ParseProcMountLines(lines ...string) ([]MountInfo, error) {
+	mountInfos := []MountInfo{}
+	for _, line := range lines {
+		if line == "" {
+			continue
+		}
+
+		// see man 5 fstab for the format of /proc/mounts
+		var (
+			source     string
+			dest       string
+			fstype     string
+			optionsStr string
+			dumpFreq   int
+			passno     int
+		)
+		_, err := fmt.Sscanf(line, "%s %s %s %s %d %d", &source, &dest, &fstype, &optionsStr, &dumpFreq, &passno)
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to parse /proc/mount line %q", line)
+		}
+
+		mountInfos = append(mountInfos, MountInfo{
+			SourcePath: source,
+			DestPath:   dest,
+			Type:       fstype,
+			Options:    strings.Split(optionsStr, ","),
+		})
+	}
+
+	return mountInfos, nil
+}
diff --git a/proto/Makefile b/proto/Makefile
index 40848b9..0798f39 100644
--- a/proto/Makefile
+++ b/proto/Makefile
@@ -21,9 +21,11 @@ $(PROTO_GEN_SRC): $(PROTO_SRC)
 
 proto: $(PROTO_GEN_SRC)
 	PROTOPATH=$(CURDIR) $(MAKE) -C service/fccontrol proto
+	PROTOPATH=$(CURDIR) $(MAKE) -C service/drivemount proto
 
 clean:
 	- rm -f $(PROTO_GEN_SRC)
 	- $(MAKE) -C service/fccontrol clean
+	- $(MAKE) -C service/drivemount clean
 
 .PHONY: clean proto
diff --git a/proto/events.pb.go b/proto/events.pb.go
index a191bfd..95486da 100644
--- a/proto/events.pb.go
+++ b/proto/events.pb.go
@@ -21,7 +21,7 @@ var _ = math.Inf
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
 type VMStart struct {
-	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -59,7 +59,7 @@ func (m *VMStart) GetVMID() string {
 }
 
 type VMStop struct {
-	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -104,11 +104,11 @@ func init() {
 func init() { proto.RegisterFile("events.proto", fileDescriptor_8f22242cb04491f9) }
 
 var fileDescriptor_8f22242cb04491f9 = []byte{
-	// 81 bytes of a gzipped FileDescriptorProto
+	// 84 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2d, 0x4b, 0xcd,
 	0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x92, 0xe5, 0x62, 0x0f, 0xf3, 0x0d, 0x2e,
 	0x49, 0x2c, 0x2a, 0x11, 0x12, 0xe2, 0x62, 0x09, 0xf3, 0xf5, 0x74, 0x91, 0x60, 0x54, 0x60, 0xd4,
-	0xe0, 0x0c, 0x02, 0xb3, 0x95, 0x64, 0xb8, 0xd8, 0x40, 0xd2, 0xf9, 0x05, 0xd8, 0x64, 0x93, 0xd8,
-	0xc0, 0x66, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf6, 0x83, 0xd8, 0x3c, 0x53, 0x00, 0x00,
-	0x00,
+	0xe0, 0x0c, 0x62, 0x29, 0xf3, 0xf5, 0x74, 0x51, 0x92, 0xe1, 0x62, 0x03, 0x49, 0xe7, 0x17, 0x60,
+	0x93, 0x4d, 0x62, 0x03, 0x9b, 0x61, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x8a, 0x85, 0x02,
+	0x53, 0x00, 0x00, 0x00,
 }
diff --git a/proto/firecracker.pb.go b/proto/firecracker.pb.go
index 3837f56..37af5ef 100644
--- a/proto/firecracker.pb.go
+++ b/proto/firecracker.pb.go
@@ -23,24 +23,24 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 // CreateVMRequest specifies creation parameters for a new FC instance
 type CreateVMRequest struct {
 	// VM identifier to assign
-	VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
 	// Specifies the machine configuration for the VM
-	MachineCfg *FirecrackerMachineConfiguration `protobuf:"bytes,2,opt,name=MachineCfg,proto3" json:"MachineCfg,omitempty"`
+	MachineCfg *FirecrackerMachineConfiguration `protobuf:"bytes,2,opt,name=MachineCfg,json=machineCfg,proto3" json:"MachineCfg,omitempty"`
 	// Specifies the file path where the kernel image is located
-	KernelImagePath string `protobuf:"bytes,3,opt,name=KernelImagePath,proto3" json:"KernelImagePath,omitempty"`
+	KernelImagePath string `protobuf:"bytes,3,opt,name=KernelImagePath,json=kernelImagePath,proto3" json:"KernelImagePath,omitempty"`
 	// Specifies the commandline arguments that should be passed to the kernel
-	KernelArgs string `protobuf:"bytes,4,opt,name=KernelArgs,proto3" json:"KernelArgs,omitempty"`
-	// Specifies the root block device for the VM
-	RootDrive *FirecrackerDrive `protobuf:"bytes,5,opt,name=RootDrive,proto3" json:"RootDrive,omitempty"`
-	// Specifies the additional block device config for the VM.
-	AdditionalDrives []*FirecrackerDrive `protobuf:"bytes,6,rep,name=AdditionalDrives,proto3" json:"AdditionalDrives,omitempty"`
+	KernelArgs string `protobuf:"bytes,4,opt,name=KernelArgs,json=kernelArgs,proto3" json:"KernelArgs,omitempty"`
+	// Specifies drive containing the rootfs of the VM
+	RootDrive *FirecrackerRootDrive `protobuf:"bytes,5,opt,name=RootDrive,json=rootDrive,proto3" json:"RootDrive,omitempty"`
+	// Specifies additional drives whose contents will be mounted inside the VM on boot.
+	DriveMounts []*FirecrackerDriveMount `protobuf:"bytes,6,rep,name=DriveMounts,json=driveMounts,proto3" json:"DriveMounts,omitempty"`
 	// Specifies the networking configuration for a VM
-	NetworkInterfaces []*FirecrackerNetworkInterface `protobuf:"bytes,7,rep,name=NetworkInterfaces,proto3" json:"NetworkInterfaces,omitempty"`
+	NetworkInterfaces []*FirecrackerNetworkInterface `protobuf:"bytes,7,rep,name=NetworkInterfaces,json=networkInterfaces,proto3" json:"NetworkInterfaces,omitempty"`
 	// The number of dummy drives to reserve in advance before running FC instance.
-	ContainerCount int32 `protobuf:"varint,8,opt,name=ContainerCount,proto3" json:"ContainerCount,omitempty"`
+	ContainerCount int32 `protobuf:"varint,8,opt,name=ContainerCount,json=containerCount,proto3" json:"ContainerCount,omitempty"`
 	// Whether the VM should exit after all tasks running in it have been deleted.
-	ExitAfterAllTasksDeleted bool          `protobuf:"varint,9,opt,name=ExitAfterAllTasksDeleted,proto3" json:"ExitAfterAllTasksDeleted,omitempty"`
-	JailerConfig             *JailerConfig `protobuf:"bytes,10,opt,name=JailerConfig,proto3" json:"JailerConfig,omitempty"`
+	ExitAfterAllTasksDeleted bool          `protobuf:"varint,9,opt,name=ExitAfterAllTasksDeleted,json=exitAfterAllTasksDeleted,proto3" json:"ExitAfterAllTasksDeleted,omitempty"`
+	JailerConfig             *JailerConfig `protobuf:"bytes,10,opt,name=JailerConfig,json=jailerConfig,proto3" json:"JailerConfig,omitempty"`
 	XXX_NoUnkeyedLiteral     struct{}      `json:"-"`
 	XXX_unrecognized         []byte        `json:"-"`
 	XXX_sizecache            int32         `json:"-"`
@@ -98,16 +98,16 @@ func (m *CreateVMRequest) GetKernelArgs() string {
 	return ""
 }
 
-func (m *CreateVMRequest) GetRootDrive() *FirecrackerDrive {
+func (m *CreateVMRequest) GetRootDrive() *FirecrackerRootDrive {
 	if m != nil {
 		return m.RootDrive
 	}
 	return nil
 }
 
-func (m *CreateVMRequest) GetAdditionalDrives() []*FirecrackerDrive {
+func (m *CreateVMRequest) GetDriveMounts() []*FirecrackerDriveMount {
 	if m != nil {
-		return m.AdditionalDrives
+		return m.DriveMounts
 	}
 	return nil
 }
@@ -141,8 +141,8 @@ func (m *CreateVMRequest) GetJailerConfig() *JailerConfig {
 }
 
 type StopVMRequest struct {
-	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
-	TimeoutSeconds       uint32   `protobuf:"varint,2,opt,name=TimeoutSeconds,proto3" json:"TimeoutSeconds,omitempty"`
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
+	TimeoutSeconds       uint32   `protobuf:"varint,2,opt,name=TimeoutSeconds,json=timeoutSeconds,proto3" json:"TimeoutSeconds,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -187,7 +187,7 @@ func (m *StopVMRequest) GetTimeoutSeconds() uint32 {
 }
 
 type GetVMInfoRequest struct {
-	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -225,11 +225,11 @@ func (m *GetVMInfoRequest) GetVMID() string {
 }
 
 type GetVMInfoResponse struct {
-	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
-	ContextID            uint32   `protobuf:"varint,2,opt,name=ContextID,proto3" json:"ContextID,omitempty"`
-	SocketPath           string   `protobuf:"bytes,3,opt,name=SocketPath,proto3" json:"SocketPath,omitempty"`
-	LogFifoPath          string   `protobuf:"bytes,4,opt,name=LogFifoPath,proto3" json:"LogFifoPath,omitempty"`
-	MetricsFifoPath      string   `protobuf:"bytes,5,opt,name=MetricsFifoPath,proto3" json:"MetricsFifoPath,omitempty"`
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
+	ContextID            uint32   `protobuf:"varint,2,opt,name=ContextID,json=contextID,proto3" json:"ContextID,omitempty"`
+	SocketPath           string   `protobuf:"bytes,3,opt,name=SocketPath,json=socketPath,proto3" json:"SocketPath,omitempty"`
+	LogFifoPath          string   `protobuf:"bytes,4,opt,name=LogFifoPath,json=logFifoPath,proto3" json:"LogFifoPath,omitempty"`
+	MetricsFifoPath      string   `protobuf:"bytes,5,opt,name=MetricsFifoPath,json=metricsFifoPath,proto3" json:"MetricsFifoPath,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -295,8 +295,8 @@ func (m *GetVMInfoResponse) GetMetricsFifoPath() string {
 }
 
 type SetVMMetadataRequest struct {
-	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
-	Metadata             string   `protobuf:"bytes,2,opt,name=Metadata,proto3" json:"Metadata,omitempty"`
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
+	Metadata             string   `protobuf:"bytes,2,opt,name=Metadata,json=metadata,proto3" json:"Metadata,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -382,35 +382,36 @@ func init() {
 func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_a73317e9fb8da571) }
 
 var fileDescriptor_a73317e9fb8da571 = []byte{
-	// 467 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0xcf, 0x6f, 0xd3, 0x30,
-	0x14, 0xc7, 0x15, 0xd6, 0x6e, 0xed, 0x2b, 0xed, 0x56, 0x8b, 0x83, 0x35, 0x4d, 0x28, 0xca, 0xa1,
-	0xea, 0xa9, 0x08, 0xb8, 0x21, 0x21, 0x51, 0x5a, 0x8a, 0xba, 0x11, 0x84, 0xdc, 0xa9, 0x07, 0x6e,
-	0x26, 0x79, 0xc9, 0xac, 0x66, 0x76, 0xb1, 0x5f, 0x61, 0xdc, 0xf9, 0x87, 0xf8, 0x0f, 0x51, 0x5c,
-	0xb6, 0x66, 0xd9, 0xe8, 0x29, 0xf1, 0xe7, 0x7d, 0xdf, 0x0f, 0x3d, 0x7f, 0x0d, 0xfd, 0x4c, 0x59,
-	0x4c, 0xac, 0x4c, 0x56, 0x68, 0x47, 0x6b, 0x6b, 0xc8, 0x9c, 0x76, 0xe8, 0xd7, 0x1a, 0xdd, 0xf6,
-	0x10, 0xfd, 0x6e, 0xc0, 0xf1, 0xc4, 0xa2, 0x24, 0x5c, 0xc6, 0x02, 0xbf, 0x6f, 0xd0, 0x11, 0x63,
-	0xd0, 0x58, 0xc6, 0xf3, 0x29, 0x0f, 0xc2, 0x60, 0xd8, 0x16, 0xfe, 0x9f, 0xbd, 0x03, 0x88, 0x65,
-	0x72, 0xa5, 0x34, 0x4e, 0xb2, 0x9c, 0x3f, 0x09, 0x83, 0x61, 0xe7, 0x55, 0x38, 0x9a, 0xed, 0x8a,
-	0xdf, 0x46, 0x8d, 0xce, 0x54, 0xbe, 0xb1, 0x92, 0x94, 0xd1, 0xa2, 0x92, 0xc3, 0x86, 0x70, 0x7c,
-	0x81, 0x56, 0x63, 0x31, 0xbf, 0x96, 0x39, 0x7e, 0x91, 0x74, 0xc5, 0x0f, 0x7c, 0x83, 0x3a, 0x66,
-	0xcf, 0x01, 0xb6, 0x68, 0x6c, 0x73, 0xc7, 0x1b, 0x5e, 0x54, 0x21, 0xec, 0x05, 0xb4, 0x85, 0x31,
-	0x34, 0xb5, 0xea, 0x07, 0xf2, 0xa6, 0x1f, 0xa5, 0x5f, 0x1d, 0xc5, 0x07, 0xc4, 0x4e, 0xc3, 0xde,
-	0xc2, 0xc9, 0x38, 0x4d, 0x55, 0x39, 0x92, 0x2c, 0x3c, 0x72, 0xfc, 0x30, 0x3c, 0x78, 0x3c, 0xef,
-	0x81, 0x94, 0x9d, 0x43, 0xff, 0x33, 0xd2, 0x4f, 0x63, 0x57, 0x73, 0x4d, 0x68, 0x33, 0x99, 0xa0,
-	0xe3, 0x47, 0x3e, 0xff, 0xac, 0x9a, 0x5f, 0x17, 0x89, 0x87, 0x69, 0x6c, 0x00, 0xbd, 0x89, 0xd1,
-	0x24, 0x95, 0x46, 0x3b, 0x31, 0x1b, 0x4d, 0xbc, 0x15, 0x06, 0xc3, 0xa6, 0xa8, 0x51, 0xf6, 0x06,
-	0xf8, 0x87, 0x1b, 0x45, 0xe3, 0x8c, 0xd0, 0x8e, 0x8b, 0xe2, 0x52, 0xba, 0x95, 0x9b, 0x62, 0x81,
-	0x84, 0x29, 0x6f, 0x87, 0xc1, 0xb0, 0x25, 0xfe, 0x1b, 0x67, 0x2f, 0xe1, 0xe9, 0xb9, 0x54, 0x45,
-	0x59, 0xaa, 0xbc, 0x0c, 0x0e, 0x7e, 0x45, 0xdd, 0x51, 0x15, 0x8a, 0x7b, 0x92, 0xe8, 0x02, 0xba,
-	0x0b, 0x32, 0xeb, 0xfd, 0x1e, 0x18, 0x40, 0xef, 0x52, 0x5d, 0xa3, 0xd9, 0xd0, 0x02, 0x13, 0xa3,
-	0x53, 0xe7, 0x7d, 0xd0, 0x15, 0x35, 0x1a, 0x0d, 0xe0, 0xe4, 0x23, 0xd2, 0x32, 0x9e, 0xeb, 0xcc,
-	0xec, 0xa9, 0x17, 0xfd, 0x09, 0xa0, 0x5f, 0x11, 0xba, 0xb5, 0xd1, 0x0e, 0x1f, 0xed, 0x7c, 0x06,
-	0xed, 0x72, 0x3f, 0x78, 0x43, 0xf3, 0xe9, 0xbf, 0xa6, 0x3b, 0x50, 0xfa, 0x65, 0x61, 0x92, 0x15,
-	0x52, 0xc5, 0x54, 0x15, 0xc2, 0x42, 0xe8, 0x7c, 0x32, 0xf9, 0x4c, 0x65, 0xc6, 0x0b, 0xb6, 0x86,
-	0xaa, 0xa2, 0xd2, 0x9b, 0x31, 0x92, 0x55, 0x89, 0xbb, 0x53, 0x35, 0xb7, 0xde, 0xac, 0xe1, 0x68,
-	0x06, 0xcf, 0x16, 0xe5, 0xc8, 0x31, 0x92, 0x4c, 0x25, 0xc9, 0x7d, 0xfb, 0x3a, 0x85, 0xd6, 0xad,
-	0xcc, 0x0f, 0xdd, 0x16, 0x77, 0xe7, 0xa8, 0x77, 0xff, 0x8e, 0xde, 0x1f, 0x7d, 0x6d, 0xfa, 0x07,
-	0xf9, 0xed, 0xd0, 0x7f, 0x5e, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x98, 0x66, 0x19, 0x2a, 0xb9,
-	0x03, 0x00, 0x00,
+	// 495 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x41, 0x6f, 0xd3, 0x4c,
+	0x10, 0x95, 0xbf, 0x26, 0x6d, 0x3c, 0x6e, 0x93, 0x2f, 0x2b, 0x40, 0xab, 0xaa, 0x42, 0x96, 0x0f,
+	0x91, 0x4f, 0x91, 0x68, 0x2f, 0x88, 0x13, 0x21, 0x21, 0xc8, 0x2d, 0x46, 0x68, 0x53, 0xe5, 0xc0,
+	0x6d, 0x71, 0xc6, 0xa9, 0xb1, 0xb3, 0x1b, 0x76, 0x27, 0xa5, 0x9c, 0xf9, 0x45, 0xfc, 0x43, 0x64,
+	0xa7, 0x49, 0x9c, 0x0a, 0x72, 0xf2, 0xce, 0x9b, 0xf7, 0x66, 0x46, 0x33, 0xcf, 0xd0, 0x4d, 0x33,
+	0x83, 0x89, 0x91, 0x49, 0x8e, 0xa6, 0xbf, 0x34, 0x9a, 0xf4, 0xb9, 0x47, 0x3f, 0x97, 0x68, 0xd7,
+	0x41, 0xf0, 0xab, 0x01, 0x9d, 0xa1, 0x41, 0x49, 0x38, 0x8d, 0x05, 0x7e, 0x5f, 0xa1, 0x25, 0xc6,
+	0xa0, 0x31, 0x8d, 0xa3, 0x11, 0x77, 0x7c, 0x27, 0x74, 0x45, 0xe3, 0x3e, 0x8e, 0x46, 0xec, 0x2d,
+	0x40, 0x2c, 0x93, 0xbb, 0x4c, 0xe1, 0x30, 0x9d, 0xf3, 0xff, 0x7c, 0x27, 0xf4, 0x2e, 0xfd, 0xfe,
+	0x78, 0x57, 0x7c, 0x93, 0xd5, 0x2a, 0xcd, 0xe6, 0x2b, 0x23, 0x29, 0xd3, 0x4a, 0xc0, 0x62, 0xab,
+	0x61, 0x21, 0x74, 0x6e, 0xd0, 0x28, 0x2c, 0xa2, 0x85, 0x9c, 0xe3, 0x67, 0x49, 0x77, 0xfc, 0xa8,
+	0x6a, 0xd0, 0xc9, 0xf7, 0x61, 0xf6, 0x12, 0x60, 0xcd, 0x1c, 0x98, 0xb9, 0xe5, 0x8d, 0x8a, 0x04,
+	0xf9, 0x16, 0x61, 0x57, 0xe0, 0x0a, 0xad, 0x69, 0x64, 0xb2, 0x7b, 0xe4, 0xcd, 0x6a, 0x94, 0xe7,
+	0xf5, 0x51, 0xb6, 0x49, 0xe1, 0x9a, 0xcd, 0x93, 0xbd, 0x06, 0xaf, 0x7a, 0xc4, 0x7a, 0xa5, 0xc8,
+	0xf2, 0x63, 0xff, 0x28, 0xf4, 0x2e, 0x5f, 0xd4, 0x65, 0xbb, 0xb4, 0xf0, 0x66, 0x3b, 0x2a, 0xbb,
+	0x86, 0xee, 0x27, 0xa4, 0x1f, 0xda, 0xe4, 0x91, 0x22, 0x34, 0xa9, 0x4c, 0xd0, 0xf2, 0x93, 0x4a,
+	0x7f, 0x51, 0xd7, 0x3f, 0x25, 0x89, 0xae, 0x7a, 0x2a, 0x63, 0x3d, 0x68, 0x0f, 0xb5, 0x22, 0x99,
+	0x29, 0x34, 0xc3, 0xb2, 0x3c, 0x6f, 0xf9, 0x4e, 0xd8, 0x14, 0xed, 0x64, 0x0f, 0x65, 0x6f, 0x80,
+	0xbf, 0x7f, 0xc8, 0x68, 0x90, 0x12, 0x9a, 0x41, 0x51, 0xdc, 0x4a, 0x9b, 0xdb, 0x11, 0x16, 0x48,
+	0x38, 0xe3, 0xae, 0xef, 0x84, 0x2d, 0xc1, 0xf1, 0x1f, 0x79, 0xf6, 0x0a, 0x4e, 0xaf, 0x65, 0x56,
+	0x94, 0xa5, 0xca, 0x5b, 0x70, 0xa8, 0x36, 0x74, 0xd6, 0xaf, 0x83, 0xe2, 0xf4, 0x5b, 0x2d, 0x0a,
+	0x6e, 0xe0, 0x6c, 0x42, 0x7a, 0x79, 0xd8, 0x02, 0x3d, 0x68, 0xdf, 0x66, 0x0b, 0xd4, 0x2b, 0x9a,
+	0x60, 0xa2, 0xd5, 0xcc, 0x56, 0x36, 0x38, 0x13, 0x6d, 0xda, 0x43, 0x83, 0x1e, 0xfc, 0xff, 0x01,
+	0x69, 0x1a, 0x47, 0x2a, 0xd5, 0x07, 0xea, 0x05, 0xbf, 0x1d, 0xe8, 0xd6, 0x88, 0x76, 0xa9, 0x95,
+	0xc5, 0xbf, 0x76, 0xbe, 0x00, 0xb7, 0xdc, 0x1a, 0x3e, 0x50, 0x34, 0x7a, 0x6c, 0xea, 0x26, 0x1b,
+	0xa0, 0xb4, 0xcb, 0x44, 0x27, 0x39, 0x52, 0xcd, 0x53, 0x60, 0xb7, 0x08, 0xf3, 0xc1, 0xfb, 0xa8,
+	0xe7, 0xe3, 0x2c, 0xd5, 0x15, 0x61, 0xed, 0x27, 0xaf, 0xd8, 0x41, 0xa5, 0x35, 0x63, 0x24, 0x93,
+	0x25, 0x76, 0xcb, 0x6a, 0xae, 0xad, 0xb9, 0xd8, 0x87, 0x83, 0x31, 0x3c, 0x9b, 0x94, 0x23, 0xc7,
+	0x48, 0x72, 0x26, 0x49, 0x1e, 0xda, 0xd7, 0x39, 0xb4, 0x36, 0xb4, 0x6a, 0x68, 0x57, 0xb4, 0x16,
+	0x8f, 0x71, 0xd0, 0xde, 0xbf, 0xd1, 0xbb, 0x93, 0x2f, 0xcd, 0xea, 0x7f, 0xfc, 0x7a, 0x5c, 0x7d,
+	0xae, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x5d, 0x86, 0x33, 0xb8, 0x03, 0x00, 0x00,
 }
diff --git a/proto/firecracker.proto b/proto/firecracker.proto
index bff0d41..4034ff9 100644
--- a/proto/firecracker.proto
+++ b/proto/firecracker.proto
@@ -18,11 +18,11 @@ message CreateVMRequest {
     // Specifies the commandline arguments that should be passed to the kernel
     string KernelArgs = 4;
 
-    // Specifies the root block device for the VM
-    FirecrackerDrive RootDrive = 5;
+    // Specifies drive containing the rootfs of the VM
+    FirecrackerRootDrive RootDrive = 5;
 
-    // Specifies the additional block device config for the VM.
-    repeated FirecrackerDrive AdditionalDrives = 6;
+    // Specifies additional drives whose contents will be mounted inside the VM on boot.
+    repeated FirecrackerDriveMount DriveMounts = 6;
 
     // Specifies the networking configuration for a VM
     repeated FirecrackerNetworkInterface NetworkInterfaces = 7;
diff --git a/proto/service/drivemount/Makefile b/proto/service/drivemount/Makefile
new file mode 100644
index 0000000..a882e0d
--- /dev/null
+++ b/proto/service/drivemount/Makefile
@@ -0,0 +1,29 @@
+# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You may
+# not use this file except in compliance with the License. A copy of the
+# License is located at
+#
+# 	http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is distributed
+# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+# express or implied. See the License for the specific language governing
+# permissions and limitations under the License.
+
+PROTO_SRC := $(wildcard *.proto)
+PROTO_GEN_SRC := $(PROTO_SRC:.proto=.pb.go)
+PROTO_GEN_SRC_TTRPC := $(addprefix ttrpc/,$(PROTO_GEN_SRC))
+
+$(PROTO_GEN_SRC_TTRPC): $(PROTO_SRC)
+	protoc -I. -I$(PROTOPATH)\
+		--gogottrpc_out=plugins=ttrpc:ttrpc \
+		$^
+
+
+proto: $(PROTO_GEN_SRC_TTRPC)
+
+clean:
+	- rm -f $(PROTO_GEN_SRC_TTRPC)
+
+.PHONY: clean proto
diff --git a/proto/service/drivemount/drivemount.proto b/proto/service/drivemount/drivemount.proto
new file mode 100644
index 0000000..5ebe020
--- /dev/null
+++ b/proto/service/drivemount/drivemount.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+import "google/protobuf/empty.proto";
+
+option go_package = "drivemount";
+
+service DriveMounter {
+    rpc MountDrive(MountDriveRequest) returns (google.protobuf.Empty);
+}
+
+message MountDriveRequest {
+    string DriveID = 1;
+    string DestinationPath = 2;
+    string FilesytemType = 3;
+    repeated string Options = 4;
+}
\ No newline at end of file
diff --git a/proto/service/drivemount/ttrpc/drivemount.pb.go b/proto/service/drivemount/ttrpc/drivemount.pb.go
new file mode 100644
index 0000000..2beb99f
--- /dev/null
+++ b/proto/service/drivemount/ttrpc/drivemount.pb.go
@@ -0,0 +1,522 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: drivemount.proto
+
+package drivemount
+
+import (
+	context "context"
+	fmt "fmt"
+	github_com_containerd_ttrpc "github.com/containerd/ttrpc"
+	proto "github.com/gogo/protobuf/proto"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+	reflect "reflect"
+	strings "strings"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type MountDriveRequest struct {
+	DriveID              string   `protobuf:"bytes,1,opt,name=DriveID,json=driveID,proto3" json:"DriveID,omitempty"`
+	DestinationPath      string   `protobuf:"bytes,2,opt,name=DestinationPath,json=destinationPath,proto3" json:"DestinationPath,omitempty"`
+	FilesytemType        string   `protobuf:"bytes,3,opt,name=FilesytemType,json=filesytemType,proto3" json:"FilesytemType,omitempty"`
+	Options              []string `protobuf:"bytes,4,rep,name=Options,json=options,proto3" json:"Options,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *MountDriveRequest) Reset()      { *m = MountDriveRequest{} }
+func (*MountDriveRequest) ProtoMessage() {}
+func (*MountDriveRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_48455e9fea9a7da5, []int{0}
+}
+func (m *MountDriveRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *MountDriveRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_MountDriveRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *MountDriveRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MountDriveRequest.Merge(m, src)
+}
+func (m *MountDriveRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *MountDriveRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_MountDriveRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MountDriveRequest proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*MountDriveRequest)(nil), "MountDriveRequest")
+}
+
+func init() { proto.RegisterFile("drivemount.proto", fileDescriptor_48455e9fea9a7da5) }
+
+var fileDescriptor_48455e9fea9a7da5 = []byte{
+	// 240 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x29, 0xca, 0x2c,
+	0x4b, 0xcd, 0xcd, 0x2f, 0xcd, 0x2b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x97, 0x92, 0x4e, 0xcf,
+	0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0xf3, 0x92, 0x4a, 0xd3, 0xf4, 0x53, 0x73, 0x0b, 0x4a, 0x2a,
+	0x21, 0x92, 0x4a, 0xd3, 0x19, 0xb9, 0x04, 0x7d, 0x41, 0x8a, 0x5d, 0x40, 0xda, 0x82, 0x52, 0x0b,
+	0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x24, 0xb8, 0xd8, 0xc1, 0x7c, 0x4f, 0x17, 0x09, 0x46, 0x05, 0x46,
+	0x0d, 0xce, 0x20, 0xf6, 0x14, 0x08, 0x57, 0x48, 0x83, 0x8b, 0xdf, 0x25, 0xb5, 0xb8, 0x24, 0x33,
+	0x2f, 0xb1, 0x24, 0x33, 0x3f, 0x2f, 0x20, 0xb1, 0x24, 0x43, 0x82, 0x09, 0xac, 0x82, 0x3f, 0x05,
+	0x55, 0x58, 0x48, 0x85, 0x8b, 0xd7, 0x2d, 0x33, 0x27, 0xb5, 0xb8, 0xb2, 0x24, 0x35, 0x37, 0xa4,
+	0xb2, 0x20, 0x55, 0x82, 0x19, 0xac, 0x8e, 0x37, 0x0d, 0x59, 0x10, 0x64, 0x93, 0x7f, 0x01, 0x48,
+	0x4f, 0xb1, 0x04, 0x8b, 0x02, 0x33, 0xc8, 0xa6, 0x7c, 0x08, 0xd7, 0xc8, 0x83, 0x8b, 0x07, 0xec,
+	0x06, 0xb0, 0xeb, 0x52, 0x8b, 0x84, 0x2c, 0xb8, 0xb8, 0x10, 0x0e, 0x15, 0x12, 0xd2, 0xc3, 0x70,
+	0xb5, 0x94, 0x98, 0x1e, 0xc4, 0xa7, 0x7a, 0x30, 0x9f, 0xea, 0xb9, 0x82, 0x7c, 0xea, 0xa4, 0x72,
+	0xe2, 0xa1, 0x1c, 0xc3, 0x8d, 0x87, 0x72, 0x0c, 0x0d, 0x8f, 0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63,
+	0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x28, 0x2e, 0x44, 0x60, 0x25, 0xb1, 0x81,
+	0x75, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc1, 0xad, 0x30, 0xb3, 0x41, 0x01, 0x00, 0x00,
+}
+
+func (m *MountDriveRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *MountDriveRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MountDriveRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Options) > 0 {
+		for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- {
+			i -= len(m.Options[iNdEx])
+			copy(dAtA[i:], m.Options[iNdEx])
+			i = encodeVarintDrivemount(dAtA, i, uint64(len(m.Options[iNdEx])))
+			i--
+			dAtA[i] = 0x22
+		}
+	}
+	if len(m.FilesytemType) > 0 {
+		i -= len(m.FilesytemType)
+		copy(dAtA[i:], m.FilesytemType)
+		i = encodeVarintDrivemount(dAtA, i, uint64(len(m.FilesytemType)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.DestinationPath) > 0 {
+		i -= len(m.DestinationPath)
+		copy(dAtA[i:], m.DestinationPath)
+		i = encodeVarintDrivemount(dAtA, i, uint64(len(m.DestinationPath)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.DriveID) > 0 {
+		i -= len(m.DriveID)
+		copy(dAtA[i:], m.DriveID)
+		i = encodeVarintDrivemount(dAtA, i, uint64(len(m.DriveID)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintDrivemount(dAtA []byte, offset int, v uint64) int {
+	offset -= sovDrivemount(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *MountDriveRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.DriveID)
+	if l > 0 {
+		n += 1 + l + sovDrivemount(uint64(l))
+	}
+	l = len(m.DestinationPath)
+	if l > 0 {
+		n += 1 + l + sovDrivemount(uint64(l))
+	}
+	l = len(m.FilesytemType)
+	if l > 0 {
+		n += 1 + l + sovDrivemount(uint64(l))
+	}
+	if len(m.Options) > 0 {
+		for _, s := range m.Options {
+			l = len(s)
+			n += 1 + l + sovDrivemount(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func sovDrivemount(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozDrivemount(x uint64) (n int) {
+	return sovDrivemount(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *MountDriveRequest) String() string {
+	if this == nil {
+		return "nil"
+	}
+	s := strings.Join([]string{`&MountDriveRequest{`,
+		`DriveID:` + fmt.Sprintf("%v", this.DriveID) + `,`,
+		`DestinationPath:` + fmt.Sprintf("%v", this.DestinationPath) + `,`,
+		`FilesytemType:` + fmt.Sprintf("%v", this.FilesytemType) + `,`,
+		`Options:` + fmt.Sprintf("%v", this.Options) + `,`,
+		`XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func valueToStringDrivemount(v interface{}) string {
+	rv := reflect.ValueOf(v)
+	if rv.IsNil() {
+		return "nil"
+	}
+	pv := reflect.Indirect(rv).Interface()
+	return fmt.Sprintf("*%v", pv)
+}
+
+type DriveMounterService interface {
+	MountDrive(ctx context.Context, req *MountDriveRequest) (*empty.Empty, error)
+}
+
+func RegisterDriveMounterService(srv *github_com_containerd_ttrpc.Server, svc DriveMounterService) {
+	srv.Register("DriveMounter", map[string]github_com_containerd_ttrpc.Method{
+		"MountDrive": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
+			var req MountDriveRequest
+			if err := unmarshal(&req); err != nil {
+				return nil, err
+			}
+			return svc.MountDrive(ctx, &req)
+		},
+	})
+}
+
+type driveMounterClient struct {
+	client *github_com_containerd_ttrpc.Client
+}
+
+func NewDriveMounterClient(client *github_com_containerd_ttrpc.Client) DriveMounterService {
+	return &driveMounterClient{
+		client: client,
+	}
+}
+
+func (c *driveMounterClient) MountDrive(ctx context.Context, req *MountDriveRequest) (*empty.Empty, error) {
+	var resp empty.Empty
+	if err := c.client.Call(ctx, "DriveMounter", "MountDrive", req, &resp); err != nil {
+		return nil, err
+	}
+	return &resp, nil
+}
+func (m *MountDriveRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowDrivemount
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: MountDriveRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: MountDriveRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field DriveID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowDrivemount
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.DriveID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field DestinationPath", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowDrivemount
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.DestinationPath = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field FilesytemType", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowDrivemount
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.FilesytemType = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Options", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowDrivemount
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Options = append(m.Options, string(dAtA[iNdEx:postIndex]))
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipDrivemount(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthDrivemount
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipDrivemount(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowDrivemount
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowDrivemount
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowDrivemount
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthDrivemount
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupDrivemount
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthDrivemount
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthDrivemount        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowDrivemount          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupDrivemount = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/proto/types.pb.go b/proto/types.pb.go
index 9c533be..9b73480 100644
--- a/proto/types.pb.go
+++ b/proto/types.pb.go
@@ -23,12 +23,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
 // Message to store bundle/config.json bytes
 type ExtraData struct {
-	JsonSpec             []byte     `protobuf:"bytes,1,opt,name=JsonSpec,proto3" json:"JsonSpec,omitempty"`
-	RuncOptions          *types.Any `protobuf:"bytes,2,opt,name=RuncOptions,proto3" json:"RuncOptions,omitempty"`
-	StdinPort            uint32     `protobuf:"varint,3,opt,name=StdinPort,proto3" json:"StdinPort,omitempty"`
-	StdoutPort           uint32     `protobuf:"varint,4,opt,name=StdoutPort,proto3" json:"StdoutPort,omitempty"`
-	StderrPort           uint32     `protobuf:"varint,5,opt,name=StderrPort,proto3" json:"StderrPort,omitempty"`
-	DriveID              string     `protobuf:"bytes,6,opt,name=DriveID,proto3" json:"DriveID,omitempty"`
+	JsonSpec             []byte     `protobuf:"bytes,1,opt,name=JsonSpec,json=jsonSpec,proto3" json:"JsonSpec,omitempty"`
+	RuncOptions          *types.Any `protobuf:"bytes,2,opt,name=RuncOptions,json=runcOptions,proto3" json:"RuncOptions,omitempty"`
+	StdinPort            uint32     `protobuf:"varint,3,opt,name=StdinPort,json=stdinPort,proto3" json:"StdinPort,omitempty"`
+	StdoutPort           uint32     `protobuf:"varint,4,opt,name=StdoutPort,json=stdoutPort,proto3" json:"StdoutPort,omitempty"`
+	StderrPort           uint32     `protobuf:"varint,5,opt,name=StderrPort,json=stderrPort,proto3" json:"StderrPort,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
 	XXX_unrecognized     []byte     `json:"-"`
 	XXX_sizecache        int32      `json:"-"`
@@ -93,24 +92,17 @@ func (m *ExtraData) GetStderrPort() uint32 {
 	return 0
 }
 
-func (m *ExtraData) GetDriveID() string {
-	if m != nil {
-		return m.DriveID
-	}
-	return ""
-}
-
 // Message to specify network config for a Firecracker VM
 type FirecrackerNetworkInterface struct {
-	AllowMMDS      bool                    `protobuf:"varint,1,opt,name=AllowMMDS,proto3" json:"AllowMMDS,omitempty"`
-	InRateLimiter  *FirecrackerRateLimiter `protobuf:"bytes,2,opt,name=InRateLimiter,proto3" json:"InRateLimiter,omitempty"`
-	OutRateLimiter *FirecrackerRateLimiter `protobuf:"bytes,3,opt,name=OutRateLimiter,proto3" json:"OutRateLimiter,omitempty"`
+	AllowMMDS      bool                    `protobuf:"varint,1,opt,name=AllowMMDS,json=allowMMDS,proto3" json:"AllowMMDS,omitempty"`
+	InRateLimiter  *FirecrackerRateLimiter `protobuf:"bytes,2,opt,name=InRateLimiter,json=inRateLimiter,proto3" json:"InRateLimiter,omitempty"`
+	OutRateLimiter *FirecrackerRateLimiter `protobuf:"bytes,3,opt,name=OutRateLimiter,json=outRateLimiter,proto3" json:"OutRateLimiter,omitempty"`
 	// CNIConfiguration specifies CNI configuration that will be used to generate
 	// a network interface for a Firecracker VM.
-	CNIConfig *CNIConfiguration `protobuf:"bytes,4,opt,name=CNIConfig,proto3" json:"CNIConfig,omitempty"`
+	CNIConfig *CNIConfiguration `protobuf:"bytes,4,opt,name=CNIConfig,json=cNIConfig,proto3" json:"CNIConfig,omitempty"`
 	// StaticNetworkConfiguration specifies static configuration parameters for a
 	// Firecracker VM's network interface
-	StaticConfig         *StaticNetworkConfiguration `protobuf:"bytes,5,opt,name=StaticConfig,proto3" json:"StaticConfig,omitempty"`
+	StaticConfig         *StaticNetworkConfiguration `protobuf:"bytes,5,opt,name=StaticConfig,json=staticConfig,proto3" json:"StaticConfig,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}                    `json:"-"`
 	XXX_unrecognized     []byte                      `json:"-"`
 	XXX_sizecache        int32                       `json:"-"`
@@ -181,23 +173,23 @@ type CNIConfiguration struct {
 	// NetworkName is the name of a CNI network (as found in CNI
 	// configuration files) that will be used to generate the
 	// network interface.
-	NetworkName string `protobuf:"bytes,1,opt,name=NetworkName,proto3" json:"NetworkName,omitempty"`
+	NetworkName string `protobuf:"bytes,1,opt,name=NetworkName,json=networkName,proto3" json:"NetworkName,omitempty"`
 	// InterfaceName corresponds to the CNI_IFNAME parameter that will be
 	// provided to CNI plugins during invocation.
-	InterfaceName string `protobuf:"bytes,2,opt,name=InterfaceName,proto3" json:"InterfaceName,omitempty"`
+	InterfaceName string `protobuf:"bytes,2,opt,name=InterfaceName,json=interfaceName,proto3" json:"InterfaceName,omitempty"`
 	// BinPath is a list of directories that will be searched when
 	// looking for CNI plugin binaries. Defaults to just "/opt/cni/bin"
-	BinPath []string `protobuf:"bytes,3,rep,name=BinPath,proto3" json:"BinPath,omitempty"`
+	BinPath []string `protobuf:"bytes,3,rep,name=BinPath,json=binPath,proto3" json:"BinPath,omitempty"`
 	// ConfDir is the directory in which CNI configuration will be sought.
 	// If not specified, will default to "/etc/cni/conf.d".
-	ConfDir string `protobuf:"bytes,4,opt,name=ConfDir,proto3" json:"ConfDir,omitempty"`
+	ConfDir string `protobuf:"bytes,4,opt,name=ConfDir,json=confDir,proto3" json:"ConfDir,omitempty"`
 	// CacheDir is the directory in which CNI results will be temporarily
 	// cached by the runtime. If not specified, it will default to
 	// "/var/lib/cni"
-	CacheDir string `protobuf:"bytes,5,opt,name=CacheDir,proto3" json:"CacheDir,omitempty"`
+	CacheDir string `protobuf:"bytes,5,opt,name=CacheDir,json=cacheDir,proto3" json:"CacheDir,omitempty"`
 	// Args corresponds to the CNI_ARGS parameter that will be provided to
 	// CNI plugins on invocation.
-	Args                 []*CNIConfiguration_CNIArg `protobuf:"bytes,6,rep,name=Args,proto3" json:"Args,omitempty"`
+	Args                 []*CNIConfiguration_CNIArg `protobuf:"bytes,6,rep,name=Args,json=args,proto3" json:"Args,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}                   `json:"-"`
 	XXX_unrecognized     []byte                     `json:"-"`
 	XXX_sizecache        int32                      `json:"-"`
@@ -270,8 +262,8 @@ func (m *CNIConfiguration) GetArgs() []*CNIConfiguration_CNIArg {
 }
 
 type CNIConfiguration_CNIArg struct {
-	Key                  string   `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"`
-	Value                string   `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
+	Key                  string   `protobuf:"bytes,1,opt,name=Key,json=key,proto3" json:"Key,omitempty"`
+	Value                string   `protobuf:"bytes,2,opt,name=Value,json=value,proto3" json:"Value,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -318,12 +310,12 @@ func (m *CNIConfiguration_CNIArg) GetValue() string {
 // Message to specify static configuration parameters for a
 // Firecracker VM's network interface
 type StaticNetworkConfiguration struct {
-	MacAddress  string `protobuf:"bytes,1,opt,name=MacAddress,proto3" json:"MacAddress,omitempty"`
-	HostDevName string `protobuf:"bytes,2,opt,name=HostDevName,proto3" json:"HostDevName,omitempty"`
+	MacAddress  string `protobuf:"bytes,1,opt,name=MacAddress,json=macAddress,proto3" json:"MacAddress,omitempty"`
+	HostDevName string `protobuf:"bytes,2,opt,name=HostDevName,json=hostDevName,proto3" json:"HostDevName,omitempty"`
 	// IPConfig optionally provides static IP configuration that will be configured
 	// on the VM's internal networking interface. If not specified, no IP
 	// configuration will be applied to the VM's internal nic automatically.
-	IPConfig             *IPConfiguration `protobuf:"bytes,3,opt,name=IPConfig,proto3" json:"IPConfig,omitempty"`
+	IPConfig             *IPConfiguration `protobuf:"bytes,3,opt,name=IPConfig,json=iPConfig,proto3" json:"IPConfig,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
 	XXX_unrecognized     []byte           `json:"-"`
 	XXX_sizecache        int32            `json:"-"`
@@ -380,15 +372,15 @@ type IPConfiguration struct {
 	// PrimaryAddr specifies, in CIDR notation, the primary address
 	// and subnet that a network interface will be assigned inside
 	// the VM.
-	PrimaryAddr string `protobuf:"bytes,1,opt,name=PrimaryAddr,proto3" json:"PrimaryAddr,omitempty"`
+	PrimaryAddr string `protobuf:"bytes,1,opt,name=PrimaryAddr,json=primaryAddr,proto3" json:"PrimaryAddr,omitempty"`
 	// GatewayAddr specifies the default gateway that a network interface
 	// should use inside the VM.
-	GatewayAddr string `protobuf:"bytes,3,opt,name=GatewayAddr,proto3" json:"GatewayAddr,omitempty"`
+	GatewayAddr string `protobuf:"bytes,3,opt,name=GatewayAddr,json=gatewayAddr,proto3" json:"GatewayAddr,omitempty"`
 	// Nameservers is a list of nameservers that the VM will be configured
 	// to use internally. Currently only up to 2 nameservers can be specified
 	// (any more in the list will be ignored) and configuration is provided
 	// to the VM via /proc/net/pnp.
-	Nameservers          []string `protobuf:"bytes,4,rep,name=Nameservers,proto3" json:"Nameservers,omitempty"`
+	Nameservers          []string `protobuf:"bytes,4,rep,name=Nameservers,json=nameservers,proto3" json:"Nameservers,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -441,13 +433,13 @@ func (m *IPConfiguration) GetNameservers() []string {
 
 // Message to set the machine config for a Firecracker VM
 type FirecrackerMachineConfiguration struct {
-	CPUTemplate string `protobuf:"bytes,1,opt,name=CPUTemplate,proto3" json:"CPUTemplate,omitempty"`
-	HtEnabled   bool   `protobuf:"varint,2,opt,name=HtEnabled,proto3" json:"HtEnabled,omitempty"`
+	CPUTemplate string `protobuf:"bytes,1,opt,name=CPUTemplate,json=cPUTemplate,proto3" json:"CPUTemplate,omitempty"`
+	HtEnabled   bool   `protobuf:"varint,2,opt,name=HtEnabled,json=htEnabled,proto3" json:"HtEnabled,omitempty"`
 	// Specifies the memory size of VM
 	// This lets us create a Firecracker VM of up to 4096 TiB, which
 	// for a microVM should be large enough
-	MemSizeMib           uint32   `protobuf:"varint,3,opt,name=MemSizeMib,proto3" json:"MemSizeMib,omitempty"`
-	VcpuCount            uint32   `protobuf:"varint,4,opt,name=VcpuCount,proto3" json:"VcpuCount,omitempty"`
+	MemSizeMib           uint32   `protobuf:"varint,3,opt,name=MemSizeMib,json=memSizeMib,proto3" json:"MemSizeMib,omitempty"`
+	VcpuCount            uint32   `protobuf:"varint,4,opt,name=VcpuCount,json=vcpuCount,proto3" json:"VcpuCount,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -506,80 +498,153 @@ func (m *FirecrackerMachineConfiguration) GetVcpuCount() uint32 {
 }
 
 // Message to specify the block device config for a Firecracker VM
-type FirecrackerDrive struct {
-	IsWritable           bool                    `protobuf:"varint,1,opt,name=IsWritable,proto3" json:"IsWritable,omitempty"`
-	IsRootDevice         bool                    `protobuf:"varint,2,opt,name=IsRootDevice,proto3" json:"IsRootDevice,omitempty"`
-	Partuuid             string                  `protobuf:"bytes,3,opt,name=Partuuid,proto3" json:"Partuuid,omitempty"`
-	PathOnHost           string                  `protobuf:"bytes,4,opt,name=PathOnHost,proto3" json:"PathOnHost,omitempty"`
-	RateLimiter          *FirecrackerRateLimiter `protobuf:"bytes,5,opt,name=RateLimiter,proto3" json:"RateLimiter,omitempty"`
+type FirecrackerRootDrive struct {
+	// (Required) HostPath is the path on the host to the filesystem image or device
+	// that will supply the rootfs of the VM.
+	HostPath string `protobuf:"bytes,1,opt,name=HostPath,json=hostPath,proto3" json:"HostPath,omitempty"`
+	// (Optional) If the HostPath points to a drive or image with multiple
+	// partitions, Partuuid specifies which partition will be used to boot
+	// the VM
+	Partuuid string `protobuf:"bytes,2,opt,name=Partuuid,json=partuuid,proto3" json:"Partuuid,omitempty"`
+	// (Optional) If set to true, IsWritable results in the VM Guest's rootfs
+	// being mounted as read-write. Defaults to false, in which case the
+	// rootfs is mounted as read-only.
+	IsWritable bool `protobuf:"varint,3,opt,name=IsWritable,json=isWritable,proto3" json:"IsWritable,omitempty"`
+	// (Optional) RateLimiter configuration that will be applied to the
+	// backing-drive for the VM's rootfs
+	RateLimiter          *FirecrackerRateLimiter `protobuf:"bytes,4,opt,name=RateLimiter,json=rateLimiter,proto3" json:"RateLimiter,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
 	XXX_unrecognized     []byte                  `json:"-"`
 	XXX_sizecache        int32                   `json:"-"`
 }
 
-func (m *FirecrackerDrive) Reset()         { *m = FirecrackerDrive{} }
-func (m *FirecrackerDrive) String() string { return proto.CompactTextString(m) }
-func (*FirecrackerDrive) ProtoMessage()    {}
-func (*FirecrackerDrive) Descriptor() ([]byte, []int) {
+func (m *FirecrackerRootDrive) Reset()         { *m = FirecrackerRootDrive{} }
+func (m *FirecrackerRootDrive) String() string { return proto.CompactTextString(m) }
+func (*FirecrackerRootDrive) ProtoMessage()    {}
+func (*FirecrackerRootDrive) Descriptor() ([]byte, []int) {
 	return fileDescriptor_d938547f84707355, []int{6}
 }
-func (m *FirecrackerDrive) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_FirecrackerDrive.Unmarshal(m, b)
+func (m *FirecrackerRootDrive) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_FirecrackerRootDrive.Unmarshal(m, b)
+}
+func (m *FirecrackerRootDrive) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_FirecrackerRootDrive.Marshal(b, m, deterministic)
 }
-func (m *FirecrackerDrive) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_FirecrackerDrive.Marshal(b, m, deterministic)
+func (m *FirecrackerRootDrive) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_FirecrackerRootDrive.Merge(m, src)
 }
-func (m *FirecrackerDrive) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_FirecrackerDrive.Merge(m, src)
+func (m *FirecrackerRootDrive) XXX_Size() int {
+	return xxx_messageInfo_FirecrackerRootDrive.Size(m)
 }
-func (m *FirecrackerDrive) XXX_Size() int {
-	return xxx_messageInfo_FirecrackerDrive.Size(m)
+func (m *FirecrackerRootDrive) XXX_DiscardUnknown() {
+	xxx_messageInfo_FirecrackerRootDrive.DiscardUnknown(m)
 }
-func (m *FirecrackerDrive) XXX_DiscardUnknown() {
-	xxx_messageInfo_FirecrackerDrive.DiscardUnknown(m)
+
+var xxx_messageInfo_FirecrackerRootDrive proto.InternalMessageInfo
+
+func (m *FirecrackerRootDrive) GetHostPath() string {
+	if m != nil {
+		return m.HostPath
+	}
+	return ""
 }
 
-var xxx_messageInfo_FirecrackerDrive proto.InternalMessageInfo
+func (m *FirecrackerRootDrive) GetPartuuid() string {
+	if m != nil {
+		return m.Partuuid
+	}
+	return ""
+}
 
-func (m *FirecrackerDrive) GetIsWritable() bool {
+func (m *FirecrackerRootDrive) GetIsWritable() bool {
 	if m != nil {
 		return m.IsWritable
 	}
 	return false
 }
 
-func (m *FirecrackerDrive) GetIsRootDevice() bool {
+func (m *FirecrackerRootDrive) GetRateLimiter() *FirecrackerRateLimiter {
 	if m != nil {
-		return m.IsRootDevice
+		return m.RateLimiter
 	}
-	return false
+	return nil
 }
 
-func (m *FirecrackerDrive) GetPartuuid() string {
+type FirecrackerDriveMount struct {
+	// (Required) HostPath is the path on the host to the filesystem image or device
+	// that will be mounted inside the VM.
+	HostPath string `protobuf:"bytes,1,opt,name=HostPath,json=hostPath,proto3" json:"HostPath,omitempty"`
+	// (Required) VMPath is the path inside the VM guest at which the filesystem
+	// image or device will be mounted.
+	VMPath string `protobuf:"bytes,2,opt,name=VMPath,json=vMPath,proto3" json:"VMPath,omitempty"`
+	// (Required) FilesystemType is the filesystem type (i.e. ext4, xfs, etc.), as
+	// used when mounting the filesystem image inside the VM. The VM guest kernel
+	// is expected to have support for this filesystem.
+	FilesystemType string `protobuf:"bytes,3,opt,name=FilesystemType,json=filesystemType,proto3" json:"FilesystemType,omitempty"`
+	// (Optional) Options are fstab-style options that the mount will be performed
+	// within the VM (i.e. ["rw", "noatime"]). Defaults to none if not specified.
+	Options              []string `protobuf:"bytes,4,rep,name=Options,json=options,proto3" json:"Options,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *FirecrackerDriveMount) Reset()         { *m = FirecrackerDriveMount{} }
+func (m *FirecrackerDriveMount) String() string { return proto.CompactTextString(m) }
+func (*FirecrackerDriveMount) ProtoMessage()    {}
+func (*FirecrackerDriveMount) Descriptor() ([]byte, []int) {
+	return fileDescriptor_d938547f84707355, []int{7}
+}
+func (m *FirecrackerDriveMount) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_FirecrackerDriveMount.Unmarshal(m, b)
+}
+func (m *FirecrackerDriveMount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_FirecrackerDriveMount.Marshal(b, m, deterministic)
+}
+func (m *FirecrackerDriveMount) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_FirecrackerDriveMount.Merge(m, src)
+}
+func (m *FirecrackerDriveMount) XXX_Size() int {
+	return xxx_messageInfo_FirecrackerDriveMount.Size(m)
+}
+func (m *FirecrackerDriveMount) XXX_DiscardUnknown() {
+	xxx_messageInfo_FirecrackerDriveMount.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_FirecrackerDriveMount proto.InternalMessageInfo
+
+func (m *FirecrackerDriveMount) GetHostPath() string {
 	if m != nil {
-		return m.Partuuid
+		return m.HostPath
 	}
 	return ""
 }
 
-func (m *FirecrackerDrive) GetPathOnHost() string {
+func (m *FirecrackerDriveMount) GetVMPath() string {
 	if m != nil {
-		return m.PathOnHost
+		return m.VMPath
 	}
 	return ""
 }
 
-func (m *FirecrackerDrive) GetRateLimiter() *FirecrackerRateLimiter {
+func (m *FirecrackerDriveMount) GetFilesystemType() string {
 	if m != nil {
-		return m.RateLimiter
+		return m.FilesystemType
+	}
+	return ""
+}
+
+func (m *FirecrackerDriveMount) GetOptions() []string {
+	if m != nil {
+		return m.Options
 	}
 	return nil
 }
 
 // Message to specify an IO rate limiter with bytes/s and ops/s limits
 type FirecrackerRateLimiter struct {
-	Bandwidth            *FirecrackerTokenBucket `protobuf:"bytes,1,opt,name=Bandwidth,proto3" json:"Bandwidth,omitempty"`
-	Ops                  *FirecrackerTokenBucket `protobuf:"bytes,2,opt,name=Ops,proto3" json:"Ops,omitempty"`
+	Bandwidth            *FirecrackerTokenBucket `protobuf:"bytes,1,opt,name=Bandwidth,json=bandwidth,proto3" json:"Bandwidth,omitempty"`
+	Ops                  *FirecrackerTokenBucket `protobuf:"bytes,2,opt,name=Ops,json=ops,proto3" json:"Ops,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
 	XXX_unrecognized     []byte                  `json:"-"`
 	XXX_sizecache        int32                   `json:"-"`
@@ -589,7 +654,7 @@ func (m *FirecrackerRateLimiter) Reset()         { *m = FirecrackerRateLimiter{}
 func (m *FirecrackerRateLimiter) String() string { return proto.CompactTextString(m) }
 func (*FirecrackerRateLimiter) ProtoMessage()    {}
 func (*FirecrackerRateLimiter) Descriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{7}
+	return fileDescriptor_d938547f84707355, []int{8}
 }
 func (m *FirecrackerRateLimiter) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_FirecrackerRateLimiter.Unmarshal(m, b)
@@ -625,9 +690,9 @@ func (m *FirecrackerRateLimiter) GetOps() *FirecrackerTokenBucket {
 
 // Message to specify a token buicket used to rate limit disk and network IO for a Firecracker VM
 type FirecrackerTokenBucket struct {
-	OneTimeBurst         int64    `protobuf:"varint,1,opt,name=OneTimeBurst,proto3" json:"OneTimeBurst,omitempty"`
-	RefillTime           int64    `protobuf:"varint,2,opt,name=RefillTime,proto3" json:"RefillTime,omitempty"`
-	Capacity             int64    `protobuf:"varint,3,opt,name=Capacity,proto3" json:"Capacity,omitempty"`
+	OneTimeBurst         int64    `protobuf:"varint,1,opt,name=OneTimeBurst,json=oneTimeBurst,proto3" json:"OneTimeBurst,omitempty"`
+	RefillTime           int64    `protobuf:"varint,2,opt,name=RefillTime,json=refillTime,proto3" json:"RefillTime,omitempty"`
+	Capacity             int64    `protobuf:"varint,3,opt,name=Capacity,json=capacity,proto3" json:"Capacity,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -637,7 +702,7 @@ func (m *FirecrackerTokenBucket) Reset()         { *m = FirecrackerTokenBucket{}
 func (m *FirecrackerTokenBucket) String() string { return proto.CompactTextString(m) }
 func (*FirecrackerTokenBucket) ProtoMessage()    {}
 func (*FirecrackerTokenBucket) Descriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{8}
+	return fileDescriptor_d938547f84707355, []int{9}
 }
 func (m *FirecrackerTokenBucket) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_FirecrackerTokenBucket.Unmarshal(m, b)
@@ -686,7 +751,8 @@ func init() {
 	proto.RegisterType((*StaticNetworkConfiguration)(nil), "StaticNetworkConfiguration")
 	proto.RegisterType((*IPConfiguration)(nil), "IPConfiguration")
 	proto.RegisterType((*FirecrackerMachineConfiguration)(nil), "FirecrackerMachineConfiguration")
-	proto.RegisterType((*FirecrackerDrive)(nil), "FirecrackerDrive")
+	proto.RegisterType((*FirecrackerRootDrive)(nil), "FirecrackerRootDrive")
+	proto.RegisterType((*FirecrackerDriveMount)(nil), "FirecrackerDriveMount")
 	proto.RegisterType((*FirecrackerRateLimiter)(nil), "FirecrackerRateLimiter")
 	proto.RegisterType((*FirecrackerTokenBucket)(nil), "FirecrackerTokenBucket")
 }
@@ -694,55 +760,60 @@ func init() {
 func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) }
 
 var fileDescriptor_d938547f84707355 = []byte{
-	// 789 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xdd, 0x8e, 0xe3, 0x34,
-	0x14, 0x56, 0x26, 0xd3, 0xd9, 0xd6, 0x9d, 0x85, 0x62, 0xad, 0x20, 0x74, 0x11, 0x54, 0x11, 0x17,
-	0x45, 0x5a, 0x65, 0xd0, 0x20, 0x90, 0xb8, 0x40, 0xab, 0xfe, 0x2c, 0x6c, 0x81, 0x4e, 0x2b, 0x77,
-	0x58, 0x24, 0xee, 0x3c, 0xc9, 0x69, 0x6b, 0x35, 0xb5, 0x23, 0xc7, 0x99, 0x6e, 0xf7, 0x11, 0x10,
-	0xef, 0xc0, 0x13, 0xf0, 0x28, 0x5c, 0xf3, 0x3a, 0xc8, 0x8e, 0x93, 0xba, 0xdd, 0xdd, 0xb9, 0x9a,
-	0x39, 0xdf, 0xf7, 0x9d, 0x73, 0xec, 0xef, 0x1c, 0xa7, 0xa8, 0xad, 0xf6, 0x19, 0xe4, 0x51, 0x26,
-	0x85, 0x12, 0xdd, 0x4f, 0x57, 0x42, 0xac, 0x52, 0xb8, 0x32, 0xd1, 0x5d, 0xb1, 0xbc, 0xa2, 0x7c,
-	0x5f, 0x52, 0xe1, 0x7f, 0x1e, 0x6a, 0xbd, 0x78, 0xad, 0x24, 0x1d, 0x53, 0x45, 0x71, 0x17, 0x35,
-	0x7f, 0xce, 0x05, 0x5f, 0x64, 0x10, 0x07, 0x5e, 0xcf, 0xeb, 0x5f, 0x92, 0x3a, 0xc6, 0xdf, 0xa1,
-	0x36, 0x29, 0x78, 0x3c, 0xcb, 0x14, 0x13, 0x3c, 0x0f, 0xce, 0x7a, 0x5e, 0xbf, 0x7d, 0xfd, 0x24,
-	0x2a, 0x4b, 0x47, 0x55, 0xe9, 0x68, 0xc0, 0xf7, 0xc4, 0x15, 0xe2, 0xcf, 0x50, 0x6b, 0xa1, 0x12,
-	0xc6, 0xe7, 0x42, 0xaa, 0xc0, 0xef, 0x79, 0xfd, 0xc7, 0xe4, 0x00, 0xe0, 0xcf, 0x11, 0x5a, 0xa8,
-	0x44, 0x14, 0xca, 0xd0, 0xe7, 0x86, 0x76, 0x10, 0xcb, 0x83, 0x94, 0x86, 0x6f, 0xd4, 0xbc, 0x45,
-	0x70, 0x80, 0x1e, 0x8d, 0x25, 0xbb, 0x87, 0xc9, 0x38, 0xb8, 0xe8, 0x79, 0xfd, 0x16, 0xa9, 0xc2,
-	0xf0, 0x9f, 0x33, 0xf4, 0xf4, 0x47, 0x26, 0x21, 0x96, 0x34, 0xde, 0x80, 0xbc, 0x01, 0xb5, 0x13,
-	0x72, 0x33, 0xe1, 0x0a, 0xe4, 0x92, 0xc6, 0xa0, 0xcf, 0x35, 0x48, 0x53, 0xb1, 0x9b, 0x4e, 0xc7,
-	0x0b, 0x73, 0xd9, 0x26, 0x39, 0x00, 0xf8, 0x07, 0xf4, 0x78, 0xc2, 0x09, 0x55, 0xf0, 0x2b, 0xdb,
-	0x32, 0x05, 0xd2, 0xde, 0xf7, 0x93, 0xc8, 0x29, 0xe9, 0xd0, 0xe4, 0x58, 0x8d, 0x9f, 0xa3, 0x0f,
-	0x66, 0x85, 0x72, 0xf3, 0xfd, 0x87, 0xf3, 0x4f, 0xe4, 0xf8, 0x0a, 0xb5, 0x46, 0x37, 0x93, 0x91,
-	0xe0, 0x4b, 0xb6, 0x32, 0xb6, 0xb4, 0xaf, 0x3f, 0x8a, 0x6a, 0xa4, 0x90, 0x54, 0x9b, 0x4b, 0x0e,
-	0x1a, 0xfc, 0x1c, 0x5d, 0x2e, 0x14, 0x55, 0x2c, 0xb6, 0x39, 0x0d, 0x93, 0xf3, 0x34, 0x2a, 0x41,
-	0x7b, 0xfb, 0xe3, 0xec, 0xa3, 0x84, 0xf0, 0xcf, 0x33, 0xd4, 0x39, 0x6d, 0x80, 0x7b, 0xa8, 0x6d,
-	0x53, 0x6f, 0xe8, 0x16, 0x8c, 0x4d, 0x2d, 0xe2, 0x42, 0xf8, 0x4b, 0x6d, 0x94, 0xf5, 0xd4, 0x68,
-	0xce, 0x8c, 0xe6, 0x18, 0xd4, 0x63, 0x1a, 0x32, 0x3e, 0xa7, 0x6a, 0x1d, 0xf8, 0x3d, 0x5f, 0x8f,
-	0xc9, 0x86, 0x9a, 0xd1, 0x2d, 0xc7, 0x4c, 0x9a, 0x6b, 0xb6, 0x48, 0x15, 0xea, 0x65, 0x1c, 0xd1,
-	0x78, 0x0d, 0x9a, 0x6a, 0x18, 0xaa, 0x8e, 0xf1, 0x33, 0x74, 0x3e, 0x90, 0xab, 0x3c, 0xb8, 0xe8,
-	0xf9, 0xfd, 0xf6, 0x75, 0xf0, 0x96, 0x33, 0x1a, 0x18, 0xc8, 0x15, 0x31, 0xaa, 0xee, 0xd7, 0xe8,
-	0xa2, 0x8c, 0x71, 0x07, 0xf9, 0xbf, 0xc0, 0xde, 0xde, 0x43, 0xff, 0x8b, 0x9f, 0xa0, 0xc6, 0x2b,
-	0x9a, 0x16, 0xd5, 0xb9, 0xcb, 0x20, 0xfc, 0xcb, 0x43, 0xdd, 0xf7, 0x3b, 0xa7, 0xb7, 0x72, 0x4a,
-	0xe3, 0x41, 0x92, 0x48, 0xc8, 0x73, 0x5b, 0xcd, 0x41, 0xb4, 0x6d, 0x2f, 0x45, 0xae, 0xc6, 0x70,
-	0xef, 0x58, 0xe2, 0x42, 0xf8, 0x19, 0x6a, 0x4e, 0xe6, 0x76, 0x54, 0xe5, 0x6a, 0x74, 0xa2, 0x0a,
-	0xa8, 0xe6, 0x53, 0x2b, 0xc2, 0x1d, 0xfa, 0xf0, 0x84, 0xd4, 0x2d, 0xe6, 0x92, 0x6d, 0xa9, 0xdc,
-	0xeb, 0xa6, 0xd5, 0x64, 0x1c, 0x48, 0x2b, 0x7e, 0xa2, 0x0a, 0x76, 0xb4, 0x54, 0xf8, 0xa5, 0xc2,
-	0x81, 0xcc, 0x74, 0xe9, 0x16, 0x72, 0x90, 0xf7, 0x20, 0xf3, 0xe0, 0xdc, 0x4c, 0xc6, 0x85, 0xc2,
-	0xbf, 0x3d, 0xf4, 0x85, 0xb3, 0xb1, 0x53, 0x1a, 0xaf, 0x19, 0x87, 0xb7, 0x4e, 0x32, 0x9a, 0xff,
-	0x76, 0x0b, 0xdb, 0x2c, 0xa5, 0xaa, 0xde, 0x11, 0x07, 0xd2, 0x4f, 0xed, 0xa5, 0x7a, 0xc1, 0xe9,
-	0x5d, 0x0a, 0x89, 0x31, 0xa3, 0x49, 0x0e, 0x80, 0x31, 0x13, 0xb6, 0x0b, 0xf6, 0x06, 0xa6, 0xec,
-	0xce, 0x7e, 0x21, 0x1c, 0x44, 0x67, 0xbf, 0x8a, 0xb3, 0x62, 0x24, 0x0a, 0x5e, 0x7d, 0x21, 0x0e,
-	0x40, 0xf8, 0xaf, 0x87, 0x3a, 0xce, 0x09, 0xcd, 0xeb, 0xd7, 0x25, 0x27, 0xf9, 0xef, 0x92, 0x29,
-	0xdd, 0xc1, 0x3e, 0x6e, 0x07, 0xc1, 0x21, 0xba, 0x9c, 0xe4, 0x44, 0x08, 0x3d, 0x0e, 0x16, 0x83,
-	0x3d, 0xd3, 0x11, 0xa6, 0xd7, 0x6f, 0x4e, 0xa5, 0x2a, 0x0a, 0x96, 0x58, 0xef, 0xea, 0x58, 0xd7,
-	0xd7, 0xcb, 0x3b, 0xe3, 0x7a, 0xa4, 0x76, 0x6f, 0x1d, 0x04, 0x7f, 0x8f, 0xda, 0xee, 0xdb, 0x6f,
-	0x3c, 0xfc, 0xf6, 0x5d, 0x6d, 0xf8, 0x06, 0x7d, 0xfc, 0x6e, 0x19, 0xfe, 0x16, 0xb5, 0x86, 0x94,
-	0x27, 0x3b, 0x96, 0xa8, 0xb5, 0xb9, 0xd3, 0x49, 0xc9, 0x5b, 0xb1, 0x01, 0x3e, 0x2c, 0xe2, 0x0d,
-	0x28, 0x72, 0x50, 0xe2, 0xaf, 0x90, 0x3f, 0xcb, 0xf2, 0x77, 0x7d, 0xbf, 0xdc, 0x04, 0xad, 0x09,
-	0x5f, 0x1f, 0xf5, 0x76, 0x68, 0x6d, 0xd8, 0x8c, 0xc3, 0x2d, 0xdb, 0xc2, 0xb0, 0x90, 0xb9, 0x32,
-	0xed, 0x7d, 0x72, 0x84, 0x69, 0x53, 0x08, 0x2c, 0x59, 0x9a, 0x6a, 0xc8, 0xf4, 0xf3, 0x89, 0x83,
-	0x94, 0xef, 0x39, 0xa3, 0x31, 0x53, 0x7b, 0x63, 0xa8, 0x4f, 0xea, 0x78, 0xf8, 0xe8, 0x8f, 0x46,
-	0xf9, 0x0b, 0x72, 0x61, 0xfe, 0x7c, 0xf3, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0xfe, 0x59,
-	0xb8, 0xc0, 0x06, 0x00, 0x00,
+	// 868 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x95, 0xdd, 0x6e, 0x23, 0x35,
+	0x14, 0xc7, 0x95, 0x4e, 0x93, 0xcc, 0x9c, 0x49, 0x4b, 0xb1, 0xca, 0x12, 0xba, 0x08, 0xa2, 0x11,
+	0x42, 0x41, 0x5a, 0xa5, 0xa8, 0x08, 0x24, 0x2e, 0xd0, 0x2a, 0x1f, 0xbb, 0x6c, 0x80, 0xb4, 0x91,
+	0x53, 0x8a, 0xc4, 0x9d, 0x33, 0x71, 0x12, 0x93, 0x8c, 0x3d, 0xb2, 0x3d, 0xc9, 0x66, 0x1f, 0x01,
+	0xc1, 0x33, 0x70, 0xcd, 0x05, 0x8f, 0xc0, 0xbb, 0x21, 0x7b, 0x3c, 0xc9, 0xb4, 0xbb, 0xf4, 0x2a,
+	0x3a, 0xbf, 0xff, 0x39, 0x73, 0xbe, 0x6c, 0x07, 0x42, 0xbd, 0x4b, 0xa9, 0xea, 0xa4, 0x52, 0x68,
+	0x71, 0xf1, 0xd1, 0x42, 0x88, 0xc5, 0x9a, 0x5e, 0x5a, 0x6b, 0x9a, 0xcd, 0x2f, 0x09, 0xdf, 0xe5,
+	0x52, 0xf4, 0x6f, 0x05, 0x82, 0x17, 0xaf, 0xb5, 0x24, 0x03, 0xa2, 0x09, 0xba, 0x00, 0xff, 0x07,
+	0x25, 0xf8, 0x24, 0xa5, 0x71, 0xb3, 0xd2, 0xaa, 0xb4, 0x1b, 0xd8, 0xff, 0xcd, 0xd9, 0xe8, 0x1b,
+	0x08, 0x71, 0xc6, 0xe3, 0x9b, 0x54, 0x33, 0xc1, 0x55, 0xf3, 0xa8, 0x55, 0x69, 0x87, 0x57, 0xe7,
+	0x9d, 0xfc, 0xd3, 0x9d, 0xe2, 0xd3, 0x9d, 0x2e, 0xdf, 0xe1, 0x50, 0x1e, 0x1c, 0xd1, 0xc7, 0x10,
+	0x4c, 0xf4, 0x8c, 0xf1, 0xb1, 0x90, 0xba, 0xe9, 0xb5, 0x2a, 0xed, 0x13, 0x1c, 0xa8, 0x02, 0xa0,
+	0x4f, 0x00, 0x26, 0x7a, 0x26, 0x32, 0x6d, 0xe5, 0x63, 0x2b, 0x83, 0xda, 0x13, 0xa7, 0x53, 0x29,
+	0xad, 0x5e, 0xdd, 0xeb, 0x8e, 0x44, 0xff, 0x1c, 0xc1, 0xd3, 0x97, 0x4c, 0xd2, 0x58, 0x92, 0x78,
+	0x45, 0xe5, 0x35, 0xd5, 0x5b, 0x21, 0x57, 0x43, 0xae, 0xa9, 0x9c, 0x93, 0x98, 0x9a, 0xec, 0xdd,
+	0xf5, 0x5a, 0x6c, 0x47, 0xa3, 0xc1, 0xc4, 0xb6, 0xe4, 0xe3, 0x80, 0x14, 0x00, 0x7d, 0x07, 0x27,
+	0x43, 0x8e, 0x89, 0xa6, 0x3f, 0xb1, 0x84, 0x69, 0x2a, 0x5d, 0x57, 0x1f, 0x76, 0x4a, 0x9f, 0x2c,
+	0xc9, 0xf8, 0x84, 0x95, 0xbd, 0xd1, 0x73, 0x38, 0xbd, 0xc9, 0x74, 0x39, 0xde, 0x7b, 0x3c, 0xfe,
+	0x54, 0xdc, 0x73, 0x47, 0x97, 0x10, 0xf4, 0xaf, 0x87, 0x7d, 0xc1, 0xe7, 0x6c, 0x61, 0x9b, 0x0f,
+	0xaf, 0xde, 0xef, 0xec, 0x49, 0x26, 0x89, 0x19, 0x21, 0x0e, 0xe2, 0x82, 0xa0, 0xe7, 0xd0, 0x98,
+	0x68, 0xa2, 0x59, 0xec, 0x62, 0xaa, 0x36, 0xe6, 0x69, 0x27, 0x87, 0xae, 0xfb, 0xfb, 0xd1, 0x0d,
+	0x55, 0x0a, 0x88, 0x7e, 0x3f, 0x82, 0xb3, 0x87, 0x09, 0x50, 0x0b, 0x42, 0x17, 0x7a, 0x4d, 0x12,
+	0x6a, 0xc7, 0x14, 0xe0, 0x90, 0x1f, 0x10, 0xfa, 0xcc, 0x0c, 0xca, 0xcd, 0xd4, 0xfa, 0x1c, 0x59,
+	0x9f, 0x13, 0x56, 0x86, 0xa8, 0x09, 0xf5, 0x1e, 0xe3, 0x63, 0xa2, 0x97, 0x4d, 0xaf, 0xe5, 0xb5,
+	0x03, 0x5c, 0x9f, 0xe6, 0xa6, 0x51, 0x4c, 0xca, 0x01, 0x93, 0xb6, 0xcd, 0x00, 0xd7, 0xe3, 0xdc,
+	0x34, 0x47, 0xae, 0x4f, 0xe2, 0x25, 0x35, 0x52, 0xd5, 0x4a, 0x7e, 0xec, 0x6c, 0xf4, 0x0c, 0x8e,
+	0xbb, 0x72, 0xa1, 0x9a, 0xb5, 0x96, 0xd7, 0x0e, 0xaf, 0x9a, 0x6f, 0x4d, 0xc6, 0x80, 0xae, 0x5c,
+	0xe0, 0x63, 0x22, 0x17, 0xea, 0xe2, 0x4b, 0xa8, 0xe5, 0x36, 0x3a, 0x03, 0xef, 0x47, 0xba, 0x73,
+	0x7d, 0x78, 0x2b, 0xba, 0x43, 0xe7, 0x50, 0xbd, 0x23, 0xeb, 0xac, 0xa8, 0xbb, 0xba, 0x31, 0x46,
+	0xf4, 0x47, 0x05, 0x2e, 0xfe, 0x7f, 0x72, 0xe6, 0xec, 0x8d, 0x48, 0xdc, 0x9d, 0xcd, 0x24, 0x55,
+	0xca, 0x7d, 0x0d, 0x92, 0x3d, 0x31, 0x63, 0x7b, 0x25, 0x94, 0x1e, 0xd0, 0x4d, 0x69, 0x24, 0xe1,
+	0xf2, 0x80, 0xd0, 0x33, 0xf0, 0x87, 0x63, 0xb7, 0xaa, 0xfc, 0x68, 0x9c, 0x75, 0x0a, 0x50, 0xec,
+	0xc7, 0x67, 0x0e, 0x44, 0x5b, 0x78, 0xef, 0x81, 0x68, 0x52, 0x8c, 0x25, 0x4b, 0x88, 0xdc, 0x99,
+	0xa4, 0xc5, 0x66, 0xd2, 0x03, 0x32, 0x1e, 0xdf, 0x13, 0x4d, 0xb7, 0x24, 0xf7, 0xf0, 0x72, 0x8f,
+	0xc5, 0x01, 0xd9, 0xed, 0x92, 0x84, 0x2a, 0x2a, 0x37, 0x54, 0xaa, 0xe6, 0xb1, 0xdd, 0x4c, 0xc8,
+	0x0f, 0x28, 0xfa, 0xab, 0x02, 0x9f, 0x96, 0x4e, 0xec, 0x88, 0xc4, 0x4b, 0xc6, 0xe9, 0x5b, 0x95,
+	0xf4, 0xc7, 0x3f, 0xdf, 0xd2, 0x24, 0x5d, 0x13, 0xbd, 0x3f, 0x23, 0xf1, 0x01, 0x99, 0xab, 0xf6,
+	0x4a, 0xbf, 0xe0, 0x64, 0xba, 0xa6, 0x33, 0x3b, 0x0c, 0x1f, 0x07, 0xcb, 0x02, 0xd8, 0x61, 0xd2,
+	0x64, 0xc2, 0xde, 0xd0, 0x11, 0x9b, 0xba, 0x77, 0x00, 0x92, 0x3d, 0x31, 0xd1, 0x77, 0x71, 0x9a,
+	0xf5, 0x45, 0xc6, 0x8b, 0x77, 0x20, 0xd8, 0x14, 0x20, 0xfa, 0xbb, 0x02, 0xe7, 0xe5, 0x3b, 0x25,
+	0x84, 0x1e, 0x48, 0xb6, 0xa1, 0xe6, 0xf8, 0x98, 0x1d, 0xd8, 0x33, 0x97, 0xd7, 0xe4, 0x2f, 0x9d,
+	0x6d, 0xb4, 0x31, 0x91, 0x3a, 0xcb, 0xd8, 0xcc, 0x2d, 0xc7, 0x4f, 0x9d, 0x6d, 0xca, 0x19, 0xaa,
+	0x5f, 0x24, 0xd3, 0xa6, 0x3a, 0x5b, 0x8e, 0x8f, 0x81, 0xed, 0x09, 0xfa, 0x16, 0xc2, 0xf2, 0xbd,
+	0x3e, 0x7e, 0xfc, 0x5e, 0x87, 0xf2, 0x60, 0x44, 0x7f, 0x56, 0xe0, 0x83, 0x92, 0x9f, 0xad, 0x73,
+	0x64, 0xba, 0x78, 0xb4, 0xd8, 0x27, 0x50, 0xbb, 0x1b, 0x59, 0x25, 0x2f, 0xb5, 0xb6, 0xb1, 0x16,
+	0xfa, 0x1c, 0x4e, 0x5f, 0xb2, 0x35, 0x55, 0x3b, 0xa5, 0x69, 0x72, 0xbb, 0x4b, 0xa9, 0x5b, 0xf1,
+	0xe9, 0xfc, 0x1e, 0x35, 0x37, 0xac, 0x78, 0x9a, 0xf3, 0x0d, 0xd7, 0x45, 0x6e, 0x46, 0x6f, 0xe0,
+	0xc9, 0xbb, 0xcb, 0x46, 0x5f, 0x43, 0xd0, 0x23, 0x7c, 0xb6, 0x65, 0x33, 0x57, 0xd0, 0x83, 0x16,
+	0x6f, 0xc5, 0x8a, 0xf2, 0x5e, 0x16, 0xaf, 0xa8, 0xc6, 0xc1, 0xb4, 0xf0, 0x44, 0x5f, 0x80, 0x77,
+	0x93, 0xaa, 0x77, 0xbd, 0x95, 0xe5, 0x00, 0x4f, 0xa4, 0x2a, 0x7a, 0x7d, 0x2f, 0x77, 0x49, 0x46,
+	0x11, 0x34, 0x6e, 0x38, 0xbd, 0x65, 0x09, 0xed, 0x65, 0x52, 0x69, 0x9b, 0xde, 0xc3, 0x0d, 0x51,
+	0x62, 0x66, 0x49, 0x98, 0xce, 0xd9, 0x7a, 0x6d, 0x90, 0xcd, 0xe7, 0x61, 0x90, 0x7b, 0x92, 0xbf,
+	0x1d, 0x29, 0x89, 0x99, 0xde, 0xd9, 0xa9, 0x78, 0xe6, 0xed, 0xc8, 0xed, 0x5e, 0xfd, 0xd7, 0x6a,
+	0xfe, 0x9f, 0x54, 0xb3, 0x3f, 0x5f, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x35, 0x4e, 0x86, 0xff,
+	0x12, 0x07, 0x00, 0x00,
 }
diff --git a/proto/types.proto b/proto/types.proto
index ca60054..dc5b1d3 100644
--- a/proto/types.proto
+++ b/proto/types.proto
@@ -11,7 +11,6 @@ message ExtraData {
 	uint32 StdinPort = 3;
 	uint32 StdoutPort = 4;
 	uint32 StderrPort = 5;
-	string DriveID = 6;
 }
 
 // Message to specify network config for a Firecracker VM
@@ -107,12 +106,45 @@ message FirecrackerMachineConfiguration {
 }
 
 // Message to specify the block device config for a Firecracker VM
-message FirecrackerDrive {
-	bool IsWritable = 1; // Specifies if the drive is writable from a guest
-	bool IsRootDevice = 2; // Specifies if the drive is the root device
-	string Partuuid = 3; // Specifies the unique id of the boot partition on this device
-	string PathOnHost = 4; // Specifies the host level path for the guest drive
-	FirecrackerRateLimiter RateLimiter = 5; // Specifies a rate limiter for block IO
+message FirecrackerRootDrive {
+  // (Required) HostPath is the path on the host to the filesystem image or device
+  // that will supply the rootfs of the VM.
+  string HostPath = 1;
+
+  // (Optional) If the HostPath points to a drive or image with multiple
+  // partitions, Partuuid specifies which partition will be used to boot
+  // the VM
+  string Partuuid = 2;
+
+  // (Optional) If set to true, IsWritable results in the VM Guest's rootfs
+  // being mounted as read-write. Defaults to false, in which case the
+  // rootfs is mounted as read-only.
+  bool IsWritable = 3;
+
+  // (Optional) RateLimiter configuration that will be applied to the
+  // backing-drive for the VM's rootfs
+  FirecrackerRateLimiter RateLimiter = 4;
+}
+
+message FirecrackerDriveMount {
+  // (Required) HostPath is the path on the host to the filesystem image or device
+  // that will be mounted inside the VM.
+  string HostPath = 1;
+
+  // (Required) VMPath is the path inside the VM guest at which the filesystem
+  // image or device will be mounted.
+  string VMPath = 2;
+
+  // (Required) FilesystemType is the filesystem type (i.e. ext4, xfs, etc.), as
+  // used when mounting the filesystem image inside the VM. The VM guest kernel
+  // is expected to have support for this filesystem.
+  string FilesystemType = 3;
+
+  // (Optional) Options are fstab-style options that the mount will be performed
+  // within the VM (i.e. ["rw", "noatime"]). Defaults to none if not specified. 
+  repeated string Options = 4;
+
+  // TODO support for ratelimiters and read-only drives (issue #296)
 }
 
 // Message to specify an IO rate limiter with bytes/s and ops/s limits
diff --git a/runtime/drive_handler.go b/runtime/drive_handler.go
index b82bb0d..f5f958e 100644
--- a/runtime/drive_handler.go
+++ b/runtime/drive_handler.go
@@ -178,13 +178,13 @@ func (h *stubDriveHandler) InDriveSet(path string) bool {
 }
 
 // PatchStubDrive will replace the next available stub drive with the provided drive
-func (h *stubDriveHandler) PatchStubDrive(ctx context.Context, client firecracker.MachineIface, pathOnHost string) (*string, error) {
+func (h *stubDriveHandler) PatchStubDrive(ctx context.Context, client firecracker.MachineIface, pathOnHost string) (string, error) {
 	h.mutex.Lock()
 	defer h.mutex.Unlock()
 
 	// Check to see if stubDriveIndex has increased more than the drive amount.
 	if h.stubDriveIndex >= int64(len(h.drives)) {
-		return nil, ErrDrivesExhausted
+		return "", ErrDrivesExhausted
 	}
 
 	d := h.drives[h.stubDriveIndex]
@@ -193,16 +193,16 @@ func (h *stubDriveHandler) PatchStubDrive(ctx context.Context, client firecracke
 	if d.DriveID == nil {
 		// this should never happen, but we want to ensure that we never nil
 		// dereference
-		return nil, ErrDriveIDNil
+		return "", ErrDriveIDNil
 	}
 
 	h.drives[h.stubDriveIndex] = d
 
 	err := client.UpdateGuestDrive(ctx, firecracker.StringValue(d.DriveID), pathOnHost)
 	if err != nil {
-		return nil, err
+		return "", err
 	}
 
 	h.stubDriveIndex++
-	return d.DriveID, nil
+	return firecracker.StringValue(d.DriveID), nil
 }
diff --git a/runtime/helpers.go b/runtime/helpers.go
index d09cdb3..99be1b6 100644
--- a/runtime/helpers.go
+++ b/runtime/helpers.go
@@ -112,19 +112,6 @@ func networkConfigFromProto(nwIface *proto.FirecrackerNetworkInterface, vmID str
 	return result, nil
 }
 
-func addDriveFromProto(builder firecracker.DrivesBuilder, drive *proto.FirecrackerDrive) firecracker.DrivesBuilder {
-	opt := func(d *models.Drive) {
-		d.IsRootDevice = firecracker.Bool(drive.IsRootDevice)
-		d.Partuuid = drive.Partuuid
-
-		if limiter := drive.RateLimiter; limiter != nil {
-			d.RateLimiter = rateLimiterFromProto(limiter)
-		}
-	}
-
-	return builder.AddDrive(drive.PathOnHost, !drive.IsWritable, opt)
-}
-
 // rateLimiterFromProto creates a firecracker RateLimiter object from the
 // protobuf message.
 func rateLimiterFromProto(rl *proto.FirecrackerRateLimiter) *models.RateLimiter {
diff --git a/runtime/jailer.go b/runtime/jailer.go
index bfa0932..33bea1e 100644
--- a/runtime/jailer.go
+++ b/runtime/jailer.go
@@ -47,9 +47,10 @@ type jailer interface {
 	// firecracker.Opt to be passed into firecracker.NewMachine which will allow
 	// for the VM to be jailed.
 	BuildJailedMachine(cfg *Config, machineCfg *firecracker.Config, vmID string) ([]firecracker.Opt, error)
-	// ExposeDeviceToJail will expose the given device provided by the snapshotter
-	// to the jailed filesystem
-	ExposeDeviceToJail(path string) error
+	// ExposeFileToJail will expose the given file to the jailed filesystem, including
+	// regular files and block devices. An error is returned if provided a path to a file
+	// with type that is not supported.
+	ExposeFileToJail(path string) error
 	// JailPath is used to return the directory we are supposed to be working in.
 	JailPath() vm.Dir
 	// StubDrivesOptions will return a set of options used to create a new stub
diff --git a/runtime/noop_jailer.go b/runtime/noop_jailer.go
index 420bd89..ae15872 100644
--- a/runtime/noop_jailer.go
+++ b/runtime/noop_jailer.go
@@ -63,8 +63,8 @@ func (j noopJailer) JailPath() vm.Dir {
 	return j.shimDir
 }
 
-func (j noopJailer) ExposeDeviceToJail(path string) error {
-	j.logger.Debug("noop operation for ExposeDeviceToJail")
+func (j noopJailer) ExposeFileToJail(path string) error {
+	j.logger.Debug("noop operation for ExposeFileToJail")
 	return nil
 }
 
diff --git a/runtime/runc_jailer.go b/runtime/runc_jailer.go
index 096d9a7..79fe25c 100644
--- a/runtime/runc_jailer.go
+++ b/runtime/runc_jailer.go
@@ -253,33 +253,49 @@ func (j runcJailer) StubDrivesOptions() []stubDrivesOpt {
 	}
 }
 
-// ExposeDeviceToJail will inspect the given file, srcDevicePath, and based on the
+// ExposeFileToJail will inspect the given file, srcPath, and based on the
 // file type, proper handling will occur to ensure that the file is visible in
 // the jail. For block devices we will use mknod to create the device and then
-// set the correct permissions to ensure visibility in the jail.
-func (j runcJailer) ExposeDeviceToJail(srcDevicePath string) error {
+// set the correct permissions to ensure visibility in the jail. Regular files
+// will be copied into the jail.
+func (j runcJailer) ExposeFileToJail(srcPath string) error {
 	uid := j.uid
 	gid := j.gid
 
 	stat := syscall.Stat_t{}
-	if err := syscall.Stat(srcDevicePath, &stat); err != nil {
+	if err := syscall.Stat(srcPath, &stat); err != nil {
 		return err
 	}
 
 	// Checks file type using S_IFMT which is the bit mask for the file type.
-	// Here we only care about block devices, ie S_IFBLK.  If it is a block type
-	// we will manually call mknod and create that device.
-	if (stat.Mode & syscall.S_IFMT) == syscall.S_IFBLK {
-		path := filepath.Join(j.RootPath(), filepath.Dir(srcDevicePath))
-		if err := mkdirAllWithPermissions(path, 0700, uid, gid); err != nil {
+	switch stat.Mode & syscall.S_IFMT {
+	case syscall.S_IFBLK:
+		parentDir := filepath.Join(j.RootPath(), filepath.Dir(srcPath))
+		if err := mkdirAllWithPermissions(parentDir, 0700, uid, gid); err != nil {
 			return err
 		}
 
-		dst := filepath.Join(path, filepath.Base(srcDevicePath))
+		dst := filepath.Join(parentDir, filepath.Base(srcPath))
 		if err := exposeBlockDeviceToJail(dst, int(stat.Rdev), int(uid), int(gid)); err != nil {
 			return err
 		}
-	} else {
+
+	case syscall.S_IFREG:
+		parentDir := filepath.Join(j.RootPath(), filepath.Dir(srcPath))
+		if err := mkdirAllWithPermissions(parentDir, 0700, uid, gid); err != nil {
+			return err
+		}
+
+		dst := filepath.Join(parentDir, filepath.Base(srcPath))
+		if err := copyFile(srcPath, dst, os.FileMode(stat.Mode)); err != nil {
+			return err
+		}
+
+		if err := os.Chown(dst, int(uid), int(gid)); err != nil {
+			return err
+		}
+
+	default:
 		return fmt.Errorf("unsupported mode: %v", stat.Mode)
 	}
 
diff --git a/runtime/service.go b/runtime/service.go
index 47beb75..d7073c3 100644
--- a/runtime/service.go
+++ b/runtime/service.go
@@ -57,6 +57,7 @@ import (
 	fcShim "github.com/firecracker-microvm/firecracker-containerd/internal/shim"
 	"github.com/firecracker-microvm/firecracker-containerd/internal/vm"
 	"github.com/firecracker-microvm/firecracker-containerd/proto"
+	drivemount "github.com/firecracker-microvm/firecracker-containerd/proto/service/drivemount/ttrpc"
 	fccontrolTtrpc "github.com/firecracker-microvm/firecracker-containerd/proto/service/fccontrol/ttrpc"
 )
 
@@ -120,6 +121,7 @@ type service struct {
 	vmStartOnce              sync.Once
 	agentClient              taskAPI.TaskService
 	eventBridgeClient        eventbridge.Getter
+	driveMountClient         drivemount.DriveMounterService
 	stubDriveHandler         *stubDriveHandler
 	exitAfterAllTasksDeleted bool // exit the VM and shim when all tasks are deleted
 
@@ -357,7 +359,7 @@ func logPanicAndDie(logger *logrus.Entry) {
 	}
 }
 
-func (s *service) generateExtraData(jsonBytes []byte, driveID *string, options *ptypes.Any) (*proto.ExtraData, error) {
+func (s *service) generateExtraData(jsonBytes []byte, options *ptypes.Any) (*proto.ExtraData, error) {
 	var opts *ptypes.Any
 	if options != nil {
 		// Copy values of existing options over
@@ -375,7 +377,6 @@ func (s *service) generateExtraData(jsonBytes []byte, driveID *string, options *
 		StdinPort:   s.nextVSockPort(),
 		StdoutPort:  s.nextVSockPort(),
 		StderrPort:  s.nextVSockPort(),
-		DriveID:     firecracker.StringValue(driveID),
 	}, nil
 }
 
@@ -509,10 +510,40 @@ func (s *service) createVM(requestCtx context.Context, request *proto.CreateVMRe
 	rpcClient := ttrpc.NewClient(conn, ttrpc.WithOnClose(func() { _ = conn.Close() }))
 	s.agentClient = taskAPI.NewTaskClient(rpcClient)
 	s.eventBridgeClient = eventbridge.NewGetterClient(rpcClient)
+	s.driveMountClient = drivemount.NewDriveMounterClient(rpcClient)
 	s.exitAfterAllTasksDeleted = request.ExitAfterAllTasksDeleted
 
+	err = s.mountDrives(requestCtx, request.DriveMounts)
+	if err != nil {
+		return err
+	}
+
 	s.logger.Info("successfully started the VM")
+	return nil
+}
+
+func (s *service) mountDrives(requestCtx context.Context, driveMounts []*proto.FirecrackerDriveMount) error {
+	for _, driveMount := range driveMounts {
+		err := s.jailer.ExposeFileToJail(driveMount.HostPath)
+		if err != nil {
+			return errors.Wrapf(err, "failed to expose drive mount host path to jail %s", driveMount.HostPath)
+		}
 
+		driveID, err := s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, driveMount.HostPath)
+		if err != nil {
+			return errors.Wrapf(err, "failed to patch drive mount stub")
+		}
+
+		_, err = s.driveMountClient.MountDrive(requestCtx, &drivemount.MountDriveRequest{
+			DriveID:         driveID,
+			DestinationPath: driveMount.VMPath,
+			FilesytemType:   driveMount.FilesystemType,
+			Options:         driveMount.Options,
+		})
+		if err != nil {
+			return errors.Wrapf(err, "failed to mount drive inside vm")
+		}
+	}
 	return nil
 }
 
@@ -599,6 +630,14 @@ func (s *service) SetVMMetadata(requestCtx context.Context, request *proto.SetVM
 }
 
 func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker.Config, error) {
+	for _, driveMount := range req.DriveMounts {
+		// Verify the request specified an absolute path for the source/dest of drives.
+		// Otherwise, users can implicitly rely on the CWD of this shim or agent.
+		if !strings.HasPrefix(driveMount.HostPath, "/") || !strings.HasPrefix(driveMount.VMPath, "/") {
+			return nil, errors.Errorf("driveMount %s contains relative path", driveMount.String())
+		}
+	}
+
 	relSockPath, err := s.shimDir.FirecrackerSockRelPath()
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get relative path to firecracker api socket")
@@ -651,7 +690,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
 	// Create stub drives first and let stub driver handler manage the drives
 	stubDriveHandler, err := newStubDriveHandler(
 		string(s.jailer.JailPath()),
-		s.logger, containerCount,
+		s.logger, containerCount+len(req.DriveMounts),
 		s.jailer.StubDrivesOptions()...,
 	)
 	if err != nil {
@@ -659,7 +698,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
 	}
 	s.stubDriveHandler = stubDriveHandler
 
-	cfg.Drives = append(stubDriveHandler.GetDrives(), s.buildNonStubDrives(req)...)
+	cfg.Drives = append(stubDriveHandler.GetDrives(), s.buildRootDrive(req)...)
 
 	// If no value for NetworkInterfaces was specified (not even an empty but non-nil list) and
 	// the runtime config specifies a default list, use those defaults
@@ -682,11 +721,11 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
 	return &cfg, nil
 }
 
-func (s *service) buildNonStubDrives(req *proto.CreateVMRequest) []models.Drive {
+func (s *service) buildRootDrive(req *proto.CreateVMRequest) []models.Drive {
 	var builder firecracker.DrivesBuilder
 
 	if input := req.RootDrive; input != nil {
-		builder = builder.WithRootDrive(input.PathOnHost,
+		builder = builder.WithRootDrive(input.HostPath,
 			firecracker.WithReadOnly(!input.IsWritable),
 			firecracker.WithPartuuid(input.Partuuid),
 			withRateLimiterFromProto(input.RateLimiter))
@@ -694,10 +733,6 @@ func (s *service) buildNonStubDrives(req *proto.CreateVMRequest) []models.Drive
 		builder = builder.WithRootDrive(s.config.RootDrive, firecracker.WithReadOnly(true))
 	}
 
-	for _, drive := range req.AdditionalDrives {
-		builder = addDriveFromProto(builder, drive)
-	}
-
 	return builder.Build()
 }
 
@@ -720,8 +755,10 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask
 		"checkpoint": request.Checkpoint,
 	}).Debug("creating task")
 
-	bundleDir := bundle.Dir(request.Bundle)
-	err = s.shimDir.CreateBundleLink(request.ID, bundleDir)
+	hostBundleDir := bundle.Dir(request.Bundle)
+	vmBundleDir := bundle.VMBundleDir(request.ID)
+
+	err = s.shimDir.CreateBundleLink(request.ID, hostBundleDir)
 	if err != nil {
 		err = errors.Wrap(err, "failed to create VM dir bundle link")
 		logger.WithError(err).Error()
@@ -736,28 +773,35 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask
 	}
 
 	for _, mnt := range request.Rootfs {
-		if err := s.jailer.ExposeDeviceToJail(mnt.Source); err != nil {
+		if err := s.jailer.ExposeFileToJail(mnt.Source); err != nil {
 			return nil, errors.Wrapf(err, "failed to expose mount to jail %v", mnt.Source)
 		}
-	}
 
-	var driveID *string
-	for _, mnt := range request.Rootfs {
-		driveID, err = s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, mnt.Source)
+		driveID, err := s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, mnt.Source)
 		if err != nil {
 			if err == ErrDrivesExhausted {
 				return nil, errors.Wrapf(errdefs.ErrUnavailable, "no remaining stub drives to be used")
 			}
 			return nil, errors.Wrapf(err, "failed to patch stub drive")
 		}
+
+		_, err = s.driveMountClient.MountDrive(requestCtx, &drivemount.MountDriveRequest{
+			DriveID:         driveID,
+			DestinationPath: vmBundleDir.RootfsPath(),
+			FilesytemType:   "ext4",
+			Options:         nil,
+		})
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to mount drive inside vm")
+		}
 	}
 
-	ociConfigBytes, err := bundleDir.OCIConfig().Bytes()
+	ociConfigBytes, err := hostBundleDir.OCIConfig().Bytes()
 	if err != nil {
 		return nil, err
 	}
 
-	extraData, err := s.generateExtraData(ociConfigBytes, driveID, request.Options)
+	extraData, err := s.generateExtraData(ociConfigBytes, request.Options)
 	if err != nil {
 		err = errors.Wrap(err, "failed to generate extra data")
 		logger.WithError(err).Error()
@@ -808,6 +852,15 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask
 		ioConnectorSet = vm.NewIOConnectorProxy(stdinConnectorPair, stdoutConnectorPair, stderrConnectorPair)
 	}
 
+	// override the request with the bundle dir that should be used inside the VM
+	request.Bundle = vmBundleDir.RootPath()
+
+	// The rootfs is mounted via a MountDrive call, so unset Rootfs in the request.
+	// We unfortunately can't rely on just having the runc shim inside the VM do
+	// the mount for us because we sometimes need to do mount retries due to our
+	// requirement of patching stub drives
+	request.Rootfs = nil
+
 	resp, err := s.taskManager.CreateTask(requestCtx, request, s.agentClient, ioConnectorSet)
 	if err != nil {
 		err = errors.Wrap(err, "failed to create task")
@@ -871,8 +924,8 @@ func (s *service) Exec(requestCtx context.Context, req *taskAPI.ExecProcessReque
 	logger := s.logger.WithField("task_id", req.ID).WithField("exec_id", req.ExecID)
 	logger.Debug("exec")
 
-	// no OCI config bytes or DriveID to provide for Exec, just leave those fields empty
-	extraData, err := s.generateExtraData(nil, nil, req.Spec)
+	// no OCI config bytes to provide for Exec, just leave those fields empty
+	extraData, err := s.generateExtraData(nil, req.Spec)
 	if err != nil {
 		err = errors.Wrap(err, "failed to generate extra data")
 		logger.WithError(err).Error()


Test patch:
diff --git a/agent/drive_handler_test.go b/agent/drive_handler_test.go
index e76c942..267ee54 100644
--- a/agent/drive_handler_test.go
+++ b/agent/drive_handler_test.go
@@ -14,9 +14,14 @@
 package main
 
 import (
+	"os"
+	"path/filepath"
+	"strconv"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestDiscoverDrives(t *testing.T) {
@@ -39,3 +44,124 @@ func TestDiscoverDrives(t *testing.T) {
 		})
 	}
 }
+
+func TestEvalAnySymlinks(t *testing.T) {
+	existingSymlink := "/proc/self/cwd" // will always exist and be a symlink to our cwd
+	nonExistentPath := filepath.Join(strconv.Itoa(int(time.Now().UnixNano())), "foo")
+	testPath := filepath.Join(existingSymlink, nonExistentPath)
+
+	resolvedPath, err := evalAnySymlinks(testPath)
+	require.NoError(t, err, "failed to evaluate symlinks in %q", testPath)
+
+	cwd, err := os.Getwd()
+	require.NoError(t, err, "failed to get current working dir")
+	assert.Equal(t, filepath.Join(cwd, nonExistentPath), resolvedPath)
+}
+
+func TestIsOrUnderDir(t *testing.T) {
+	type testcase struct {
+		baseDir      string
+		path         string
+		expectedTrue bool
+	}
+
+	for _, tc := range []testcase{
+		{
+			baseDir:      "/foo",
+			path:         "/foo/bar",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo/bar",
+			path:         "/foo/bar/baz",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/foo",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo/bar",
+			path:         "/foo/bar",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/foobar",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/bar",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo/bar",
+			path:         "/bar",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo/bar",
+			path:         "/foo",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo/bar",
+			path:         "/bar/bar",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "foo",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "bar",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/foo/../foo",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo/bar",
+			path:         "/foo/../foo/bar",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/foo/../bar",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/foo/..bar",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/foo/..bar/baz",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/",
+			path:         "/",
+			expectedTrue: true,
+		},
+		{
+			baseDir:      "/foo",
+			path:         "/",
+			expectedTrue: false,
+		},
+		{
+			baseDir:      "/",
+			path:         "/foo",
+			expectedTrue: true,
+		},
+	} {
+		assert.Equalf(t, tc.expectedTrue, isOrUnderDir(tc.path, tc.baseDir), "unexpected output for isOrUnderDir case %+v", tc)
+	}
+}
diff --git a/runtime/cni_integ_test.go b/runtime/cni_integ_test.go
index 67f053f..e7e62cb 100644
--- a/runtime/cni_integ_test.go
+++ b/runtime/cni_integ_test.go
@@ -92,10 +92,6 @@ func TestCNISupport_Isolated(t *testing.T) {
 				MachineCfg: &proto.FirecrackerMachineConfiguration{
 					MemSizeMib: 512,
 				},
-				RootDrive: &proto.FirecrackerDrive{
-					PathOnHost:   defaultVMRootfsPath,
-					IsRootDevice: true,
-				},
 				NetworkInterfaces: []*proto.FirecrackerNetworkInterface{{
 					CNIConfig: &proto.CNIConfiguration{
 						NetworkName:   cniNetworkName,
@@ -265,10 +261,6 @@ func TestCNIPlugin_Performance(t *testing.T) {
 				MachineCfg: &proto.FirecrackerMachineConfiguration{
 					MemSizeMib: uint32(vmMemSizeMB),
 				},
-				RootDrive: &proto.FirecrackerDrive{
-					PathOnHost:   defaultVMRootfsPath,
-					IsRootDevice: true,
-				},
 				NetworkInterfaces: []*proto.FirecrackerNetworkInterface{{
 					CNIConfig: &proto.CNIConfiguration{
 						NetworkName:   cniNetworkName,
diff --git a/runtime/drive_handler_test.go b/runtime/drive_handler_test.go
index 835b932..a8d2f85 100644
--- a/runtime/drive_handler_test.go
+++ b/runtime/drive_handler_test.go
@@ -97,7 +97,7 @@ func TestPatchStubDrive(t *testing.T) {
 	for i, path := range expectedReplacements {
 		driveID, err := handler.PatchStubDrive(ctx, client, path)
 		assert.NoError(t, err, "failed to patch stub drive")
-		assert.Equal(t, expectedDriveIDs[i], firecracker.StringValue(driveID), "drive ids are not equal")
+		assert.Equal(t, expectedDriveIDs[i], driveID, "drive ids are not equal")
 	}
 }
 
diff --git a/runtime/helpers_test.go b/runtime/helpers_test.go
index 11d3153..32dc890 100644
--- a/runtime/helpers_test.go
+++ b/runtime/helpers_test.go
@@ -207,15 +207,3 @@ func TestTokenBucketFromProto(t *testing.T) {
 	assert.EqualValues(t, refillTime, *bucket.RefillTime)
 	assert.EqualValues(t, capacity, *bucket.Size)
 }
-
-func TestAddDriveFromProto(t *testing.T) {
-	list := addDriveFromProto(firecracker.DrivesBuilder{}, &proto.FirecrackerDrive{
-		IsWritable: false,
-		PathOnHost: "/a",
-		Partuuid:   "xy",
-	}).Build()
-
-	assert.Equal(t, "/a", *list[0].PathOnHost)
-	assert.Equal(t, "xy", list[0].Partuuid)
-	assert.True(t, *list[0].IsReadOnly)
-}
diff --git a/runtime/service_integ_test.go b/runtime/service_integ_test.go
index a5e0e17..bde965f 100644
--- a/runtime/service_integ_test.go
+++ b/runtime/service_integ_test.go
@@ -44,6 +44,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	_ "github.com/firecracker-microvm/firecracker-containerd/firecracker-control"
+	fcClient "github.com/firecracker-microvm/firecracker-containerd/firecracker-control/client"
 	"github.com/firecracker-microvm/firecracker-containerd/internal"
 	"github.com/firecracker-microvm/firecracker-containerd/internal/vm"
 	"github.com/firecracker-microvm/firecracker-containerd/proto"
@@ -282,9 +283,8 @@ func TestMultipleVMs_Isolated(t *testing.T) {
 				MachineCfg: &proto.FirecrackerMachineConfiguration{
 					MemSizeMib: 512,
 				},
-				RootDrive: &proto.FirecrackerDrive{
-					PathOnHost:   rootfsPath,
-					IsRootDevice: true,
+				RootDrive: &proto.FirecrackerRootDrive{
+					HostPath: rootfsPath,
 				},
 				NetworkInterfaces: []*proto.FirecrackerNetworkInterface{
 					{
@@ -525,11 +525,7 @@ func TestLongUnixSocketPath_Isolated(t *testing.T) {
 
 	fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client())
 	_, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{
-		VMID: vmID,
-		RootDrive: &proto.FirecrackerDrive{
-			PathOnHost:   defaultVMRootfsPath,
-			IsRootDevice: true,
-		},
+		VMID:              vmID,
 		NetworkInterfaces: []*proto.FirecrackerNetworkInterface{},
 	})
 	require.NoError(t, err, "failed to create VM")
@@ -566,8 +562,6 @@ func TestStubBlockDevices_Isolated(t *testing.T) {
 	image, err := alpineImage(ctx, client, defaultSnapshotterName())
 	require.NoError(t, err, "failed to get alpine image")
 
-	rootfsPath := defaultVMRootfsPath
-
 	tapName := fmt.Sprintf("tap%d", vmID)
 	err = createTapDevice(ctx, tapName)
 	require.NoError(t, err, "failed to create tap device for vm %d", vmID)
@@ -581,10 +575,6 @@ func TestStubBlockDevices_Isolated(t *testing.T) {
 	fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client())
 	_, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{
 		VMID: strconv.Itoa(vmID),
-		RootDrive: &proto.FirecrackerDrive{
-			PathOnHost:   rootfsPath,
-			IsRootDevice: true,
-		},
 		NetworkInterfaces: []*proto.FirecrackerNetworkInterface{
 			{
 				AllowMMDS: true,
@@ -716,11 +706,7 @@ func testCreateContainerWithSameName(t *testing.T, vmID string) {
 	if len(vmID) != 0 {
 		fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client())
 		_, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{
-			VMID: vmID,
-			RootDrive: &proto.FirecrackerDrive{
-				PathOnHost:   defaultRootfsPath,
-				IsRootDevice: true,
-			},
+			VMID:           vmID,
 			ContainerCount: 2,
 		})
 		require.NoError(t, err)
@@ -845,3 +831,175 @@ func TestCreateTooManyContainers_Isolated(t *testing.T) {
 	assert.Equal("no remaining stub drives to be used: unavailable: unknown", err.Error())
 	require.Error(t, err)
 }
+
+func TestDriveMount_Isolated(t *testing.T) {
+	prepareIntegTest(t, func(cfg *Config) {
+		cfg.JailerConfig.RuncBinaryPath = "/usr/local/bin/runc"
+	})
+
+	testTimeout := 120 * time.Second
+	ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), defaultNamespace), testTimeout)
+	defer cancel()
+
+	ctrdClient, err := containerd.New(containerdSockPath, containerd.WithDefaultRuntime(firecrackerRuntime))
+	require.NoError(t, err, "unable to create client to containerd service at %s, is containerd running?", containerdSockPath)
+
+	fcClient, err := fcClient.New(containerdSockPath + ".ttrpc")
+	require.NoError(t, err, "failed to create fccontrol client")
+
+	image, err := alpineImage(ctx, ctrdClient, defaultSnapshotterName())
+	require.NoError(t, err, "failed to get alpine image")
+
+	vmID := "test-drive-mount"
+
+	vmMounts := []struct {
+		VMPath         string
+		FilesystemType string
+		VMMountOptions []string
+		ContainerPath  string
+		FSImgFile      internal.FSImgFile
+	}{
+		{
+			// /systemmount meant to make sure logic doesn't ban this just because it begins with /sys
+			VMPath:         "/systemmount",
+			FilesystemType: "ext4",
+			VMMountOptions: []string{"rw", "noatime"},
+			ContainerPath:  "/foo",
+			FSImgFile: internal.FSImgFile{
+				Subpath:  "dir/foo",
+				Contents: "foo\n",
+			},
+		},
+		{
+			VMPath:         "/mnt",
+			FilesystemType: "ext3",
+			VMMountOptions: []string{"ro", "relatime"},
+			ContainerPath:  "/bar",
+			FSImgFile: internal.FSImgFile{
+				Subpath:  "dir/bar",
+				Contents: "bar\n",
+			},
+		},
+	}
+
+	vmDriveMounts := []*proto.FirecrackerDriveMount{}
+	ctrBindMounts := []specs.Mount{}
+	ctrCatCommands := []string{}
+	for _, vmMount := range vmMounts {
+		vmDriveMounts = append(vmDriveMounts, &proto.FirecrackerDriveMount{
+			HostPath:       internal.CreateFSImg(ctx, t, vmMount.FilesystemType, vmMount.FSImgFile),
+			VMPath:         vmMount.VMPath,
+			FilesystemType: vmMount.FilesystemType,
+			Options:        vmMount.VMMountOptions,
+		})
+
+		ctrBindMounts = append(ctrBindMounts, specs.Mount{
+			Source:      vmMount.VMPath,
+			Destination: vmMount.ContainerPath,
+			Options:     []string{"bind"},
+		})
+
+		ctrCatCommands = append(ctrCatCommands, fmt.Sprintf("/bin/cat %s",
+			filepath.Join(vmMount.ContainerPath, vmMount.FSImgFile.Subpath),
+		))
+	}
+
+	_, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{
+		VMID:         vmID,
+		DriveMounts:  vmDriveMounts,
+		JailerConfig: &proto.JailerConfig{},
+	})
+	require.NoError(t, err, "failed to create vm")
+
+	containerName := fmt.Sprintf("%s-container", vmID)
+	snapshotName := fmt.Sprintf("%s-snapshot", vmID)
+
+	newContainer, err := ctrdClient.NewContainer(ctx,
+		containerName,
+		containerd.WithSnapshotter(defaultSnapshotterName()),
+		containerd.WithNewSnapshot(snapshotName, image),
+		containerd.WithNewSpec(
+			oci.WithProcessArgs("/bin/sh", "-c", strings.Join(append(ctrCatCommands,
+				"/bin/cat /proc/mounts",
+			), " && ")),
+			oci.WithMounts(ctrBindMounts),
+			firecrackeroci.WithVMID(vmID),
+		),
+	)
+	require.NoError(t, err, "failed to create container %s", containerName)
+
+	outputLines := strings.Split(startAndWaitTask(ctx, t, newContainer), "\n")
+	if len(outputLines) < len(vmMounts) {
+		require.Fail(t, "unexpected ctr output, expected at least %d lines: %+v", len(vmMounts), outputLines)
+	}
+
+	mountInfos, err := internal.ParseProcMountLines(outputLines[len(vmMounts):]...)
+	require.NoError(t, err, "failed to parse /proc/mount")
+	// this is n^2, but it's doubtful the number of mounts will reach a point where that matters...
+	for _, vmMount := range vmMounts {
+		// Make sure that this vmMount's test file was cat'd by a container previously and output the expected
+		// file contents. This ensure the filesystem was successfully mounted in the VM and the container.
+		assert.Containsf(t, outputLines[:len(vmMounts)], strings.TrimSpace(vmMount.FSImgFile.Contents),
+			"did not find expected test file output for vm mount at %q", vmMount.ContainerPath)
+
+		// iterate over /proc/mounts entries, find this vmMount's entry in there and verify it was mounted
+		// with the correct options.
+		var foundExpectedMount bool
+		for _, actualMountInfo := range mountInfos {
+			if actualMountInfo.DestPath == vmMount.ContainerPath {
+				foundExpectedMount = true
+				assert.Equalf(t, vmMount.FilesystemType, actualMountInfo.Type,
+					"vm mount at %q did have expected filesystem type", vmMount.ContainerPath)
+				for _, vmMountOption := range vmMount.VMMountOptions {
+					assert.Containsf(t, actualMountInfo.Options, vmMountOption,
+						"vm mount at %q did not have expected option", vmMount.ContainerPath)
+				}
+				break
+			}
+		}
+		assert.Truef(t, foundExpectedMount, "did not find expected mount at container path %q", vmMount.ContainerPath)
+	}
+}
+
+func TestDriveMountFails_Isolated(t *testing.T) {
+	prepareIntegTest(t)
+
+	testTimeout := 120 * time.Second
+	ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), defaultNamespace), testTimeout)
+	defer cancel()
+
+	fcClient, err := fcClient.New(containerdSockPath + ".ttrpc")
+	require.NoError(t, err, "failed to create fccontrol client")
+
+	testImgHostPath := internal.CreateFSImg(ctx, t, "ext4", internal.FSImgFile{
+		Subpath:  "idc",
+		Contents: "doesn't matter",
+	})
+
+	for _, driveMount := range []*proto.FirecrackerDriveMount{
+		{
+			HostPath:       testImgHostPath,
+			VMPath:         "/proc/foo", // invalid due to being under /proc
+			FilesystemType: "ext4",
+		},
+		{
+			HostPath:       testImgHostPath,
+			VMPath:         "/dev/foo", // invalid due to being under /dev
+			FilesystemType: "ext4",
+		},
+		{
+			HostPath:       testImgHostPath,
+			VMPath:         "/sys/foo", // invalid due to being under /sys
+			FilesystemType: "ext4",
+		},
+	} {
+		_, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{
+			VMID:        "test-drive-mount-fails",
+			DriveMounts: []*proto.FirecrackerDriveMount{driveMount},
+		})
+
+		// TODO it would be good to check for more specific error types, see #294 for possible improvements:
+		// https://github.com/firecracker-microvm/firecracker-containerd/issues/294
+		assert.Error(t, err, "unexpectedly succeeded in creating a VM with a drive mount under banned path")
+	}
+}
diff --git a/runtime/service_test.go b/runtime/service_test.go
index ebebbe3..30fa29a 100644
--- a/runtime/service_test.go
+++ b/runtime/service_test.go
@@ -79,8 +79,8 @@ func TestBuildVMConfiguration(t *testing.T) {
 			request: &proto.CreateVMRequest{
 				KernelArgs:      "REQUEST KERNEL ARGS",
 				KernelImagePath: "REQUEST KERNEL IMAGE",
-				RootDrive: &proto.FirecrackerDrive{
-					PathOnHost: "REQUEST ROOT DRIVE",
+				RootDrive: &proto.FirecrackerRootDrive{
+					HostPath:   "REQUEST ROOT DRIVE",
 					IsWritable: true,
 				},
 				MachineCfg: &proto.FirecrackerMachineConfiguration{
@@ -114,8 +114,8 @@ func TestBuildVMConfiguration(t *testing.T) {
 			request: &proto.CreateVMRequest{
 				KernelArgs:      "REQUEST KERNEL ARGS",
 				KernelImagePath: "REQUEST KERNEL IMAGE",
-				RootDrive: &proto.FirecrackerDrive{
-					PathOnHost: "REQUEST ROOT DRIVE",
+				RootDrive: &proto.FirecrackerRootDrive{
+					HostPath:   "REQUEST ROOT DRIVE",
 					IsWritable: true,
 				},
 				MachineCfg: &proto.FirecrackerMachineConfiguration{
@@ -154,8 +154,8 @@ func TestBuildVMConfiguration(t *testing.T) {
 			request: &proto.CreateVMRequest{
 				KernelArgs:      "REQUEST KERNEL ARGS",
 				KernelImagePath: "REQUEST KERNEL IMAGE",
-				RootDrive: &proto.FirecrackerDrive{
-					PathOnHost: "REQUEST ROOT DRIVE",
+				RootDrive: &proto.FirecrackerRootDrive{
+					HostPath: "REQUEST ROOT DRIVE",
 				},
 				MachineCfg: &proto.FirecrackerMachineConfiguration{},
 			},