Repository: firecracker-microvm/firecracker-containerd
Pull request #153 (2019-04-01 23:02:05)

Add an image-builder component
*Issue #75, #33*

*Description of changes:*

This PR adds automation for creating MicroVM root fillesystem images. The images are created as *squashfs* files and configured to support read-only attachment with a read/write overlay layer on top. RunC and the firecracker-containerd agent are embedded in the image, and the agent is started by default. Per our current convention, the snapshotter-created container rootfs image is assumed to be on `vdb` and is mounted at `/container`.

If you have docker installed and configured in such a way as to allow you to run containers without root access, you can build an image with `make image` from the top level directory. The resulting image will be available as `image-builder/rootfs.img` See [README.md](image-builder/README.md) for more details.

In order to use the resulting image with firecracker-containerd, you should pass the following kernel parameters at MicroVM launch: `systemd.journald.forward_to_console systemd.unit=firecracker.target init=/sbin/overlay-init`

There are a couple of optimizations missing at this time. In particular, because the target responsible for building the agent is a `PHONY` target, the dependent `image` target can run when not strictly necessary.


nmeyerhans: > It might be worth moving the runc submodule into another directory (maybe dependencies/runc?) to try and keep the root of this repo somewhat more organized.

How does `submodules/runc` sound? Keeping the fact that this is a submodule explicit might help prevent confusion.
samuelkarp: > How does `submodules/runc` sound? Keeping the fact that this is a submodule explicit might help prevent confusion.

That works for me.

Patch:
diff --git a/.gitignore b/.gitignore
index c875fbd..1639997 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .idea/
 bin/
+*stamp
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..6570354
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "runc"]
+	path = _submodules/runc
+	url = https://github.com/opencontainers/runc
diff --git a/Makefile b/Makefile
index f2216b3..2a1f2cf 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,10 @@ DOCKER_IMAGE_TAG?=latest
 
 GOPATH:=$(shell go env GOPATH)
 BINPATH:=$(abspath ./bin)
+SUBMODULES=_submodules
+RUNC_DIR=$(SUBMODULES)/runc
+RUNC_BIN=$(RUNC_DIR)/runc
+UID:=$(shell id -u)
 
 all: $(SUBDIRS)
 
@@ -31,9 +35,16 @@ proto:
 clean:
 	for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done
 	- rm -rf $(BINPATH)/
+	$(MAKE) -C $(RUNC_DIR) clean
+	rm -f *stamp
+	$(MAKE) -C tools/image-builder clean-in-docker
+
+distclean: clean
+	docker rmi localhost/runc-builder:latest
+	$(MAKE) -C tools/image-builder distclean
 
 lint:
-	$(BINPATH)/ltag -t ./.headers -check -v
+	$(BINPATH)/ltag -t ./.headers -excludes "tools $(SUBMODULES)" -check -v
 	$(BINPATH)/git-validation -run DCO,dangling-whitespace,short-subject -range HEAD~20..HEAD
 	$(BINPATH)/golangci-lint run
 
@@ -43,6 +54,34 @@ deps:
 	GOBIN=$(BINPATH) GO111MODULE=off go get -u github.com/vbatts/git-validation
 	GOBIN=$(BINPATH) GO111MODULE=off go get -u github.com/kunalkushwaha/ltag
 
+runc-builder: runc-builder-stamp
+
+runc-builder-stamp: tools/docker/Dockerfile.runc-builder
+	cd tools/docker && docker build -t localhost/runc-builder:latest -f Dockerfile.runc-builder .
+	touch $@
+
+$(RUNC_DIR)/VERSION:
+	git submodule update --init --recursive $(RUNC_DIR)
+
+runc: $(RUNC_BIN)
+
+$(RUNC_BIN): $(RUNC_DIR)/VERSION runc-builder-stamp
+	docker run --rm -it --user $(UID) \
+		--volume $(PWD)/$(RUNC_DIR):/gopath/src/github.com/opencontainers/runc \
+		--volume $(PWD)/deps:/target \
+		-e HOME=/tmp \
+		-e GOPATH=/gopath \
+		--workdir /gopath/src/github.com/opencontainers/runc \
+		localhost/runc-builder:latest \
+		make runc
+
+image: $(RUNC_BIN) agent
+	mkdir -p tools/image-builder/files_ephemeral/usr/local/bin
+	cp $(RUNC_BIN) tools/image-builder/files_ephemeral/usr/local/bin
+	cp agent/agent tools/image-builder/files_ephemeral/usr/local/bin
+	touch tools/image-builder/files_ephemeral
+	$(MAKE) -C tools/image-builder all-in-docker
+
 install:
 	for d in $(SUBDIRS); do $(MAKE) -C $$d install; done
 
@@ -69,4 +108,4 @@ docker-image-e2etest-naive:
 
 docker-images: | docker-image-e2etest-naive docker-image-unittest-nonroot docker-image-unittest
 
-.PHONY: all $(SUBDIRS) clean proto deps lint install docker-images docker-image-e2etest-naive docker-image-unittest-nonroot docker-image-unittest
+.PHONY: all $(SUBDIRS) clean proto deps lint install docker-images docker-image-e2etest-naive docker-image-unittest-nonroot docker-image-unittest runc-builder runc
diff --git a/_submodules/runc b/_submodules/runc
new file mode 160000
index 0000000..6635b4f
--- /dev/null
+++ b/_submodules/runc
@@ -0,0 +1,1 @@
+Subproject commit 6635b4f0c6af3810594d2770f662f34ddc15b40d
diff --git a/firecracker-control/local.go b/firecracker-control/local.go
index aeabbc4..d6a9c27 100644
--- a/firecracker-control/local.go
+++ b/firecracker-control/local.go
@@ -14,7 +14,6 @@
 package service
 
 import (
-	"context"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -26,6 +25,7 @@ import (
 	firecracker "github.com/firecracker-microvm/firecracker-go-sdk"
 	"github.com/golang/protobuf/ptypes/empty"
 	"github.com/pkg/errors"
+	"golang.org/x/net/context"
 
 	"github.com/firecracker-microvm/firecracker-containerd/proto"
 )
@@ -278,27 +278,43 @@ func (s *local) StopVM(ctx context.Context, req *proto.StopVMRequest) (*empty.Em
 	return &empty.Empty{}, nil
 }
 
-func (s *local) GetVMInfo(ctx context.Context, req *proto.GetVMInfoRequest) (*proto.GetVMInfoResponse, error) {
-	id := req.GetVMID()
-	instance, err := s.getVM(id)
+// GetVMAddress returns a socket file location of the VM instance
+func (s *local) GetVMAddress(_ context.Context, req *proto.GetVMAddressRequest) (*proto.GetVMAddressResponse, error) {
+	instance, err := s.getVM(req.GetVMID())
 	if err != nil {
 		return nil, err
 	}
 
-	var cid uint32
-	if len(instance.cfg.VsockDevices) > 0 {
-		cid = instance.cfg.VsockDevices[0].CID
+	return &proto.GetVMAddressResponse{
+		SocketPath: instance.cfg.SocketPath,
+	}, nil
+}
+
+// GetFifoPath returns FIFO file location of the VM instance
+func (s *local) GetFifoPath(ctx context.Context, req *proto.GetFifoPathRequest) (*proto.GetFifoPathResponse, error) {
+	var (
+		id       = req.GetVMID()
+		fifoType = req.GetFifoType()
+	)
+
+	instance, err := s.getVM(id)
+	if err != nil {
+		return nil, err
 	}
 
-	resp := &proto.GetVMInfoResponse{
-		VMID:            id,
-		ContextID:       cid,
-		SocketPath:      instance.cfg.SocketPath,
-		LogFifoPath:     instance.cfg.LogFifo,
-		MetricsFifoPath: instance.cfg.MetricsFifo,
+	var path string
+	switch fifoType {
+	case proto.FifoType_LOG:
+		path = instance.cfg.LogFifo
+	case proto.FifoType_METRICS:
+		path = instance.cfg.MetricsFifo
+	default:
+		return nil, fmt.Errorf("unsupported fifo type %q", fifoType.String())
 	}
 
-	return resp, nil
+	return &proto.GetFifoPathResponse{
+		Path: path,
+	}, nil
 }
 
 // SetVMMetadata sets Firecracker instance metadata
diff --git a/firecracker-control/service.go b/firecracker-control/service.go
index 3587ac8..08a610f 100644
--- a/firecracker-control/service.go
+++ b/firecracker-control/service.go
@@ -76,9 +76,14 @@ func (s *service) StopVM(ctx context.Context, req *proto.StopVMRequest) (*empty.
 	return s.local.StopVM(ctx, req)
 }
 
-func (s *service) GetVMInfo(ctx context.Context, req *proto.GetVMInfoRequest) (*proto.GetVMInfoResponse, error) {
-	log.G(ctx).Debugf("get VM info: %+v", req)
-	return s.local.GetVMInfo(ctx, req)
+func (s *service) GetVMAddress(ctx context.Context, req *proto.GetVMAddressRequest) (*proto.GetVMAddressResponse, error) {
+	log.G(ctx).Debugf("get VM address: %+v", req)
+	return s.local.GetVMAddress(ctx, req)
+}
+
+func (s *service) GetFifoPath(ctx context.Context, req *proto.GetFifoPathRequest) (*proto.GetFifoPathResponse, error) {
+	log.G(ctx).Debugf("get fifo path: %+v", req)
+	return s.local.GetFifoPath(ctx, req)
 }
 
 func (s *service) SetVMMetadata(ctx context.Context, req *proto.SetVMMetadataRequest) (*empty.Empty, error) {
diff --git a/proto/firecracker.pb.go b/proto/firecracker.pb.go
index ccc268d..befba23 100644
--- a/proto/firecracker.pb.go
+++ b/proto/firecracker.pb.go
@@ -24,6 +24,29 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
 
+type FifoType int32
+
+const (
+	FifoType_LOG     FifoType = 0
+	FifoType_METRICS FifoType = 1
+)
+
+var FifoType_name = map[int32]string{
+	0: "LOG",
+	1: "METRICS",
+}
+var FifoType_value = map[string]int32{
+	"LOG":     0,
+	"METRICS": 1,
+}
+
+func (x FifoType) String() string {
+	return proto.EnumName(FifoType_name, int32(x))
+}
+func (FifoType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{0}
+}
+
 // CreateVMRequest specifies creation parameters for a new FC instance
 type CreateVMRequest struct {
 	// VM identifier to assign
@@ -51,7 +74,7 @@ func (m *CreateVMRequest) Reset()         { *m = CreateVMRequest{} }
 func (m *CreateVMRequest) String() string { return proto.CompactTextString(m) }
 func (*CreateVMRequest) ProtoMessage()    {}
 func (*CreateVMRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_7b3f3f0a94e95e55, []int{0}
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{0}
 }
 func (m *CreateVMRequest) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_CreateVMRequest.Unmarshal(m, b)
@@ -138,7 +161,7 @@ func (m *StopVMRequest) Reset()         { *m = StopVMRequest{} }
 func (m *StopVMRequest) String() string { return proto.CompactTextString(m) }
 func (*StopVMRequest) ProtoMessage()    {}
 func (*StopVMRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_7b3f3f0a94e95e55, []int{1}
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{1}
 }
 func (m *StopVMRequest) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_StopVMRequest.Unmarshal(m, b)
@@ -165,110 +188,162 @@ func (m *StopVMRequest) GetVMID() string {
 	return ""
 }
 
-type GetVMInfoRequest struct {
+type GetVMAddressRequest struct {
 	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *GetVMInfoRequest) Reset()         { *m = GetVMInfoRequest{} }
-func (m *GetVMInfoRequest) String() string { return proto.CompactTextString(m) }
-func (*GetVMInfoRequest) ProtoMessage()    {}
-func (*GetVMInfoRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_7b3f3f0a94e95e55, []int{2}
+func (m *GetVMAddressRequest) Reset()         { *m = GetVMAddressRequest{} }
+func (m *GetVMAddressRequest) String() string { return proto.CompactTextString(m) }
+func (*GetVMAddressRequest) ProtoMessage()    {}
+func (*GetVMAddressRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{2}
 }
-func (m *GetVMInfoRequest) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_GetVMInfoRequest.Unmarshal(m, b)
+func (m *GetVMAddressRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetVMAddressRequest.Unmarshal(m, b)
 }
-func (m *GetVMInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_GetVMInfoRequest.Marshal(b, m, deterministic)
+func (m *GetVMAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetVMAddressRequest.Marshal(b, m, deterministic)
 }
-func (dst *GetVMInfoRequest) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_GetVMInfoRequest.Merge(dst, src)
+func (dst *GetVMAddressRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetVMAddressRequest.Merge(dst, src)
 }
-func (m *GetVMInfoRequest) XXX_Size() int {
-	return xxx_messageInfo_GetVMInfoRequest.Size(m)
+func (m *GetVMAddressRequest) XXX_Size() int {
+	return xxx_messageInfo_GetVMAddressRequest.Size(m)
 }
-func (m *GetVMInfoRequest) XXX_DiscardUnknown() {
-	xxx_messageInfo_GetVMInfoRequest.DiscardUnknown(m)
+func (m *GetVMAddressRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetVMAddressRequest.DiscardUnknown(m)
 }
 
-var xxx_messageInfo_GetVMInfoRequest proto.InternalMessageInfo
+var xxx_messageInfo_GetVMAddressRequest proto.InternalMessageInfo
 
-func (m *GetVMInfoRequest) GetVMID() string {
+func (m *GetVMAddressRequest) GetVMID() string {
 	if m != nil {
 		return m.VMID
 	}
 	return ""
 }
 
-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"`
+type GetVMAddressResponse struct {
+	SocketPath           string   `protobuf:"bytes,1,opt,name=SocketPath,proto3" json:"SocketPath,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *GetVMInfoResponse) Reset()         { *m = GetVMInfoResponse{} }
-func (m *GetVMInfoResponse) String() string { return proto.CompactTextString(m) }
-func (*GetVMInfoResponse) ProtoMessage()    {}
-func (*GetVMInfoResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_7b3f3f0a94e95e55, []int{3}
+func (m *GetVMAddressResponse) Reset()         { *m = GetVMAddressResponse{} }
+func (m *GetVMAddressResponse) String() string { return proto.CompactTextString(m) }
+func (*GetVMAddressResponse) ProtoMessage()    {}
+func (*GetVMAddressResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{3}
 }
-func (m *GetVMInfoResponse) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_GetVMInfoResponse.Unmarshal(m, b)
+func (m *GetVMAddressResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetVMAddressResponse.Unmarshal(m, b)
 }
-func (m *GetVMInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_GetVMInfoResponse.Marshal(b, m, deterministic)
+func (m *GetVMAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetVMAddressResponse.Marshal(b, m, deterministic)
 }
-func (dst *GetVMInfoResponse) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_GetVMInfoResponse.Merge(dst, src)
+func (dst *GetVMAddressResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetVMAddressResponse.Merge(dst, src)
 }
-func (m *GetVMInfoResponse) XXX_Size() int {
-	return xxx_messageInfo_GetVMInfoResponse.Size(m)
+func (m *GetVMAddressResponse) XXX_Size() int {
+	return xxx_messageInfo_GetVMAddressResponse.Size(m)
 }
-func (m *GetVMInfoResponse) XXX_DiscardUnknown() {
-	xxx_messageInfo_GetVMInfoResponse.DiscardUnknown(m)
+func (m *GetVMAddressResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetVMAddressResponse.DiscardUnknown(m)
 }
 
-var xxx_messageInfo_GetVMInfoResponse proto.InternalMessageInfo
+var xxx_messageInfo_GetVMAddressResponse proto.InternalMessageInfo
 
-func (m *GetVMInfoResponse) GetVMID() string {
+func (m *GetVMAddressResponse) GetSocketPath() string {
 	if m != nil {
-		return m.VMID
+		return m.SocketPath
 	}
 	return ""
 }
 
-func (m *GetVMInfoResponse) GetContextID() uint32 {
-	if m != nil {
-		return m.ContextID
-	}
-	return 0
+type GetFifoPathRequest struct {
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	FifoType             FifoType `protobuf:"varint,2,opt,name=FifoType,proto3,enum=FifoType" json:"FifoType,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *GetFifoPathRequest) Reset()         { *m = GetFifoPathRequest{} }
+func (m *GetFifoPathRequest) String() string { return proto.CompactTextString(m) }
+func (*GetFifoPathRequest) ProtoMessage()    {}
+func (*GetFifoPathRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{4}
+}
+func (m *GetFifoPathRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetFifoPathRequest.Unmarshal(m, b)
+}
+func (m *GetFifoPathRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetFifoPathRequest.Marshal(b, m, deterministic)
+}
+func (dst *GetFifoPathRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetFifoPathRequest.Merge(dst, src)
+}
+func (m *GetFifoPathRequest) XXX_Size() int {
+	return xxx_messageInfo_GetFifoPathRequest.Size(m)
+}
+func (m *GetFifoPathRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetFifoPathRequest.DiscardUnknown(m)
 }
 
-func (m *GetVMInfoResponse) GetSocketPath() string {
+var xxx_messageInfo_GetFifoPathRequest proto.InternalMessageInfo
+
+func (m *GetFifoPathRequest) GetVMID() string {
 	if m != nil {
-		return m.SocketPath
+		return m.VMID
 	}
 	return ""
 }
 
-func (m *GetVMInfoResponse) GetLogFifoPath() string {
+func (m *GetFifoPathRequest) GetFifoType() FifoType {
 	if m != nil {
-		return m.LogFifoPath
+		return m.FifoType
 	}
-	return ""
+	return FifoType_LOG
+}
+
+type GetFifoPathResponse struct {
+	Path                 string   `protobuf:"bytes,1,opt,name=Path,proto3" json:"Path,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *GetFifoPathResponse) Reset()         { *m = GetFifoPathResponse{} }
+func (m *GetFifoPathResponse) String() string { return proto.CompactTextString(m) }
+func (*GetFifoPathResponse) ProtoMessage()    {}
+func (*GetFifoPathResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{5}
+}
+func (m *GetFifoPathResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetFifoPathResponse.Unmarshal(m, b)
+}
+func (m *GetFifoPathResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetFifoPathResponse.Marshal(b, m, deterministic)
+}
+func (dst *GetFifoPathResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetFifoPathResponse.Merge(dst, src)
+}
+func (m *GetFifoPathResponse) XXX_Size() int {
+	return xxx_messageInfo_GetFifoPathResponse.Size(m)
+}
+func (m *GetFifoPathResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetFifoPathResponse.DiscardUnknown(m)
 }
 
-func (m *GetVMInfoResponse) GetMetricsFifoPath() string {
+var xxx_messageInfo_GetFifoPathResponse proto.InternalMessageInfo
+
+func (m *GetFifoPathResponse) GetPath() string {
 	if m != nil {
-		return m.MetricsFifoPath
+		return m.Path
 	}
 	return ""
 }
@@ -285,7 +360,7 @@ func (m *SetVMMetadataRequest) Reset()         { *m = SetVMMetadataRequest{} }
 func (m *SetVMMetadataRequest) String() string { return proto.CompactTextString(m) }
 func (*SetVMMetadataRequest) ProtoMessage()    {}
 func (*SetVMMetadataRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_7b3f3f0a94e95e55, []int{4}
+	return fileDescriptor_firecracker_abf7bcabf5d4fa05, []int{6}
 }
 func (m *SetVMMetadataRequest) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_SetVMMetadataRequest.Unmarshal(m, b)
@@ -322,9 +397,12 @@ func (m *SetVMMetadataRequest) GetMetadata() string {
 func init() {
 	proto.RegisterType((*CreateVMRequest)(nil), "CreateVMRequest")
 	proto.RegisterType((*StopVMRequest)(nil), "StopVMRequest")
-	proto.RegisterType((*GetVMInfoRequest)(nil), "GetVMInfoRequest")
-	proto.RegisterType((*GetVMInfoResponse)(nil), "GetVMInfoResponse")
+	proto.RegisterType((*GetVMAddressRequest)(nil), "GetVMAddressRequest")
+	proto.RegisterType((*GetVMAddressResponse)(nil), "GetVMAddressResponse")
+	proto.RegisterType((*GetFifoPathRequest)(nil), "GetFifoPathRequest")
+	proto.RegisterType((*GetFifoPathResponse)(nil), "GetFifoPathResponse")
 	proto.RegisterType((*SetVMMetadataRequest)(nil), "SetVMMetadataRequest")
+	proto.RegisterEnum("FifoType", FifoType_name, FifoType_value)
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -343,8 +421,10 @@ type FirecrackerClient interface {
 	CreateVM(ctx context.Context, in *CreateVMRequest, opts ...grpc.CallOption) (*empty.Empty, error)
 	// Stops existing Firecracker instance by VM ID
 	StopVM(ctx context.Context, in *StopVMRequest, opts ...grpc.CallOption) (*empty.Empty, error)
-	// Returns VM info by VM ID
-	GetVMInfo(ctx context.Context, in *GetVMInfoRequest, opts ...grpc.CallOption) (*GetVMInfoResponse, error)
+	// Gets VM's instance socket file location
+	GetVMAddress(ctx context.Context, in *GetVMAddressRequest, opts ...grpc.CallOption) (*GetVMAddressResponse, error)
+	// Gets VM's instance FIFO file location
+	GetFifoPath(ctx context.Context, in *GetFifoPathRequest, opts ...grpc.CallOption) (*GetFifoPathResponse, error)
 	// Sets VM's instance metadata
 	SetVMMetadata(ctx context.Context, in *SetVMMetadataRequest, opts ...grpc.CallOption) (*empty.Empty, error)
 }
@@ -375,9 +455,18 @@ func (c *firecrackerClient) StopVM(ctx context.Context, in *StopVMRequest, opts
 	return out, nil
 }
 
-func (c *firecrackerClient) GetVMInfo(ctx context.Context, in *GetVMInfoRequest, opts ...grpc.CallOption) (*GetVMInfoResponse, error) {
-	out := new(GetVMInfoResponse)
-	err := c.cc.Invoke(ctx, "/Firecracker/GetVMInfo", in, out, opts...)
+func (c *firecrackerClient) GetVMAddress(ctx context.Context, in *GetVMAddressRequest, opts ...grpc.CallOption) (*GetVMAddressResponse, error) {
+	out := new(GetVMAddressResponse)
+	err := c.cc.Invoke(ctx, "/Firecracker/GetVMAddress", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *firecrackerClient) GetFifoPath(ctx context.Context, in *GetFifoPathRequest, opts ...grpc.CallOption) (*GetFifoPathResponse, error) {
+	out := new(GetFifoPathResponse)
+	err := c.cc.Invoke(ctx, "/Firecracker/GetFifoPath", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -399,8 +488,10 @@ type FirecrackerServer interface {
 	CreateVM(context.Context, *CreateVMRequest) (*empty.Empty, error)
 	// Stops existing Firecracker instance by VM ID
 	StopVM(context.Context, *StopVMRequest) (*empty.Empty, error)
-	// Returns VM info by VM ID
-	GetVMInfo(context.Context, *GetVMInfoRequest) (*GetVMInfoResponse, error)
+	// Gets VM's instance socket file location
+	GetVMAddress(context.Context, *GetVMAddressRequest) (*GetVMAddressResponse, error)
+	// Gets VM's instance FIFO file location
+	GetFifoPath(context.Context, *GetFifoPathRequest) (*GetFifoPathResponse, error)
 	// Sets VM's instance metadata
 	SetVMMetadata(context.Context, *SetVMMetadataRequest) (*empty.Empty, error)
 }
@@ -445,20 +536,38 @@ func _Firecracker_StopVM_Handler(srv interface{}, ctx context.Context, dec func(
 	return interceptor(ctx, in, info, handler)
 }
 
-func _Firecracker_GetVMInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(GetVMInfoRequest)
+func _Firecracker_GetVMAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetVMAddressRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(FirecrackerServer).GetVMAddress(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/Firecracker/GetVMAddress",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(FirecrackerServer).GetVMAddress(ctx, req.(*GetVMAddressRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Firecracker_GetFifoPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetFifoPathRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(FirecrackerServer).GetVMInfo(ctx, in)
+		return srv.(FirecrackerServer).GetFifoPath(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/Firecracker/GetVMInfo",
+		FullMethod: "/Firecracker/GetFifoPath",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(FirecrackerServer).GetVMInfo(ctx, req.(*GetVMInfoRequest))
+		return srv.(FirecrackerServer).GetFifoPath(ctx, req.(*GetFifoPathRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
@@ -494,8 +603,12 @@ var _Firecracker_serviceDesc = grpc.ServiceDesc{
 			Handler:    _Firecracker_StopVM_Handler,
 		},
 		{
-			MethodName: "GetVMInfo",
-			Handler:    _Firecracker_GetVMInfo_Handler,
+			MethodName: "GetVMAddress",
+			Handler:    _Firecracker_GetVMAddress_Handler,
+		},
+		{
+			MethodName: "GetFifoPath",
+			Handler:    _Firecracker_GetFifoPath_Handler,
 		},
 		{
 			MethodName: "SetVMMetadata",
@@ -506,39 +619,41 @@ var _Firecracker_serviceDesc = grpc.ServiceDesc{
 	Metadata: "firecracker.proto",
 }
 
-func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_firecracker_7b3f3f0a94e95e55) }
-
-var fileDescriptor_firecracker_7b3f3f0a94e95e55 = []byte{
-	// 481 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0xd1, 0x8e, 0xd2, 0x40,
-	0x14, 0x4d, 0x65, 0x61, 0xe1, 0x12, 0x76, 0xe9, 0x44, 0x4d, 0x83, 0x1b, 0xd3, 0x60, 0xb2, 0xe1,
-	0x69, 0x30, 0xe8, 0xab, 0xc6, 0x15, 0xc4, 0x54, 0xad, 0x31, 0x25, 0xe1, 0xc1, 0xb7, 0xb1, 0xdc,
-	0x76, 0x1b, 0xd8, 0x99, 0x3a, 0xbd, 0xa8, 0xfb, 0x21, 0x7e, 0x88, 0x9f, 0xe5, 0x5f, 0x98, 0x4e,
-	0x05, 0xba, 0x85, 0xe5, 0x09, 0xee, 0xb9, 0xe7, 0xe4, 0x9e, 0xce, 0xb9, 0x17, 0xec, 0x28, 0xd1,
-	0x18, 0x6a, 0x11, 0x2e, 0x51, 0xf3, 0x54, 0x2b, 0x52, 0xbd, 0x27, 0xb1, 0x52, 0xf1, 0x0a, 0x87,
-	0xa6, 0xfa, 0xb6, 0x8e, 0x86, 0x78, 0x93, 0xd2, 0xed, 0xff, 0x66, 0x9b, 0x6e, 0x53, 0xcc, 0x8a,
-	0xa2, 0xff, 0xbb, 0x06, 0xe7, 0x63, 0x8d, 0x82, 0x70, 0xee, 0x07, 0xf8, 0x7d, 0x8d, 0x19, 0x31,
-	0x06, 0x27, 0x73, 0xdf, 0x9b, 0x38, 0x96, 0x6b, 0x0d, 0x5a, 0x81, 0xf9, 0xcf, 0xde, 0x00, 0xf8,
-	0x22, 0xbc, 0x4e, 0x24, 0x8e, 0xa3, 0xd8, 0x79, 0xe0, 0x5a, 0x83, 0xf6, 0xc8, 0xe5, 0xd3, 0xdd,
-	0xe4, 0x4d, 0x57, 0xc9, 0x28, 0x89, 0xd7, 0x5a, 0x50, 0xa2, 0x64, 0x50, 0xd2, 0xb0, 0x01, 0x9c,
-	0x7f, 0x44, 0x2d, 0x71, 0xe5, 0xdd, 0x88, 0x18, 0xbf, 0x08, 0xba, 0x76, 0x6a, 0x66, 0x40, 0x15,
-	0x66, 0x4f, 0x01, 0x0a, 0xe8, 0x4a, 0xc7, 0x99, 0x73, 0x62, 0x48, 0x25, 0x84, 0x0d, 0xa1, 0x15,
-	0x28, 0x45, 0x13, 0x9d, 0xfc, 0x40, 0xa7, 0x6e, 0xac, 0xd8, 0x65, 0x2b, 0xa6, 0x11, 0xec, 0x38,
-	0xec, 0x15, 0x74, 0xaf, 0x16, 0x8b, 0x24, 0xb7, 0x24, 0x56, 0x06, 0xca, 0x9c, 0x86, 0x5b, 0x3b,
-	0xac, 0xdb, 0xa3, 0xb2, 0x0f, 0x60, 0x7f, 0x46, 0xfa, 0xa9, 0xf4, 0xd2, 0x93, 0x84, 0x3a, 0x12,
-	0x21, 0x66, 0xce, 0xa9, 0xd1, 0x5f, 0x94, 0xf5, 0x55, 0x52, 0xb0, 0x2f, 0x63, 0x97, 0x70, 0x36,
-	0x56, 0x92, 0x44, 0x22, 0x51, 0x8f, 0xd5, 0x5a, 0x92, 0xd3, 0x74, 0xad, 0x41, 0x3d, 0xa8, 0xa0,
-	0xfd, 0x67, 0xd0, 0x99, 0x91, 0x4a, 0x8f, 0x86, 0xd2, 0xbf, 0x84, 0xee, 0x7b, 0xa4, 0xb9, 0xef,
-	0xc9, 0x48, 0x1d, 0xe3, 0xfd, 0xb1, 0xc0, 0x2e, 0x11, 0xb3, 0x54, 0xc9, 0x0c, 0x0f, 0xc6, 0x7c,
-	0x01, 0xad, 0xdc, 0x08, 0xfe, 0x22, 0x6f, 0x62, 0x52, 0xee, 0x04, 0x3b, 0x20, 0x0f, 0x66, 0xa6,
-	0xc2, 0x25, 0x52, 0x29, 0xbd, 0x12, 0xc2, 0x5c, 0x68, 0x7f, 0x52, 0xf1, 0x34, 0x89, 0x94, 0x21,
-	0x14, 0xc9, 0x95, 0xa1, 0x7c, 0x09, 0x7c, 0x24, 0x9d, 0x84, 0xd9, 0x96, 0x55, 0x2f, 0x96, 0xa0,
-	0x02, 0xf7, 0xa7, 0xf0, 0x70, 0x96, 0x5b, 0xf6, 0x91, 0xc4, 0x42, 0x90, 0x38, 0xb6, 0x9c, 0x3d,
-	0x68, 0x6e, 0x68, 0xc6, 0x74, 0x2b, 0xd8, 0xd6, 0xa3, 0xbf, 0x16, 0xb4, 0x4b, 0x19, 0xb1, 0x97,
-	0xd0, 0xdc, 0xec, 0x3b, 0xeb, 0xf2, 0xca, 0xea, 0xf7, 0x1e, 0xf3, 0xe2, 0x72, 0xf8, 0xe6, 0x72,
-	0xf8, 0xbb, 0xfc, 0x72, 0xd8, 0x73, 0x68, 0x14, 0x71, 0xb0, 0x33, 0x7e, 0x27, 0x97, 0x7b, 0x15,
-	0x23, 0x68, 0x6d, 0x9f, 0x9c, 0xd9, 0xbc, 0x9a, 0x53, 0x8f, 0xf1, 0xfd, 0x44, 0x5e, 0x43, 0xe7,
-	0xce, 0x37, 0xb3, 0x47, 0xfc, 0xd0, 0x1b, 0xdc, 0x37, 0xf3, 0xed, 0xe9, 0xd7, 0x7a, 0x81, 0x34,
-	0xcc, 0xcf, 0x8b, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xac, 0xa7, 0xfc, 0x93, 0x1b, 0x04, 0x00,
-	0x00,
+func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_firecracker_abf7bcabf5d4fa05) }
+
+var fileDescriptor_firecracker_abf7bcabf5d4fa05 = []byte{
+	// 521 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x5d, 0x8f, 0xd2, 0x50,
+	0x10, 0x95, 0xe5, 0x7b, 0x70, 0x59, 0x98, 0x65, 0x4d, 0x83, 0xc6, 0x34, 0x18, 0x0d, 0xfa, 0x70,
+	0x31, 0x68, 0x8c, 0x89, 0xd1, 0x88, 0xec, 0x42, 0x50, 0xeb, 0x9a, 0xb2, 0xe1, 0xc1, 0xb7, 0x2b,
+	0x4c, 0xbb, 0x0d, 0x6c, 0x6f, 0xbd, 0xbd, 0x68, 0xf8, 0x21, 0xfe, 0x0b, 0x7f, 0xa4, 0xe9, 0x2d,
+	0x2c, 0xe5, 0x63, 0x79, 0xa2, 0x73, 0xe6, 0x1c, 0xe6, 0x64, 0xce, 0x1d, 0xa8, 0x3a, 0x9e, 0xa4,
+	0xb1, 0xe4, 0xe3, 0x29, 0x49, 0x16, 0x48, 0xa1, 0x44, 0xfd, 0xa1, 0x2b, 0x84, 0x3b, 0xa3, 0x96,
+	0xae, 0x7e, 0xce, 0x9d, 0x16, 0xdd, 0x04, 0x6a, 0xb1, 0x6c, 0x96, 0xd4, 0x22, 0xa0, 0x30, 0x2e,
+	0x1a, 0x7f, 0xd3, 0x70, 0xd2, 0x95, 0xc4, 0x15, 0x8d, 0x2c, 0x9b, 0x7e, 0xcd, 0x29, 0x54, 0x88,
+	0x90, 0x19, 0x59, 0x83, 0x73, 0x23, 0x65, 0xa6, 0x9a, 0x45, 0x5b, 0x7f, 0xe3, 0x47, 0x00, 0x8b,
+	0x8f, 0xaf, 0x3d, 0x9f, 0xba, 0x8e, 0x6b, 0x1c, 0x99, 0xa9, 0x66, 0xa9, 0x6d, 0xb2, 0xde, 0x7a,
+	0xf2, 0xaa, 0x2b, 0x7c, 0xc7, 0x73, 0xe7, 0x92, 0x2b, 0x4f, 0xf8, 0x76, 0x42, 0x83, 0x4d, 0x38,
+	0xf9, 0x42, 0xd2, 0xa7, 0xd9, 0xe0, 0x86, 0xbb, 0xf4, 0x9d, 0xab, 0x6b, 0x23, 0xad, 0x07, 0x6c,
+	0xc3, 0xf8, 0x18, 0x20, 0x86, 0x3a, 0xd2, 0x0d, 0x8d, 0x8c, 0x26, 0x25, 0x10, 0x6c, 0x41, 0xd1,
+	0x16, 0x42, 0x9d, 0x4b, 0xef, 0x37, 0x19, 0x59, 0x6d, 0xa5, 0x9a, 0xb4, 0xa2, 0x1b, 0xf6, 0x9a,
+	0x83, 0xef, 0xa1, 0xd2, 0x99, 0x4c, 0xbc, 0xc8, 0x12, 0x9f, 0x69, 0x28, 0x34, 0x72, 0x66, 0x7a,
+	0xbf, 0x6e, 0x87, 0x8a, 0x9f, 0xa1, 0xfa, 0x8d, 0xd4, 0x1f, 0x21, 0xa7, 0x03, 0x5f, 0x91, 0x74,
+	0xf8, 0x98, 0x42, 0x23, 0xaf, 0xf5, 0x8f, 0x92, 0xfa, 0x6d, 0x92, 0xbd, 0x2b, 0xc3, 0x67, 0x50,
+	0xee, 0x0a, 0x5f, 0x71, 0xcf, 0x27, 0xd9, 0x15, 0x73, 0x5f, 0x19, 0x05, 0x33, 0xd5, 0xcc, 0xda,
+	0x5b, 0x68, 0xe3, 0x09, 0x1c, 0x0f, 0x95, 0x08, 0x0e, 0x86, 0xd2, 0x78, 0x0e, 0xa7, 0x7d, 0x52,
+	0x23, 0xab, 0x33, 0x99, 0x48, 0x0a, 0xc3, 0x43, 0xd4, 0x37, 0x50, 0xdb, 0xa4, 0x86, 0x81, 0xf0,
+	0x43, 0x8a, 0x76, 0x3d, 0x14, 0xe3, 0x29, 0x29, 0x1d, 0x48, 0xac, 0x48, 0x20, 0x8d, 0x4b, 0xc0,
+	0x3e, 0xa9, 0x9e, 0xe7, 0x88, 0xa8, 0x3c, 0xf4, 0x42, 0x9e, 0x42, 0x21, 0xa2, 0x5d, 0x2d, 0x02,
+	0xd2, 0xef, 0xa3, 0xdc, 0x2e, 0xb2, 0x15, 0x60, 0xdf, 0xb6, 0x96, 0x9e, 0xd7, 0x7f, 0xb8, 0xf4,
+	0x81, 0x90, 0x49, 0x38, 0xd0, 0xdf, 0x8d, 0x1e, 0xd4, 0x86, 0x91, 0x67, 0x8b, 0x14, 0x9f, 0x70,
+	0xc5, 0x0f, 0x4d, 0xaf, 0x43, 0x61, 0x45, 0xd3, 0xd3, 0x8b, 0xf6, 0x6d, 0xfd, 0xc2, 0x5c, 0x3b,
+	0xc3, 0x3c, 0xa4, 0xbf, 0x5e, 0xf6, 0x2b, 0xf7, 0xb0, 0x04, 0x79, 0xeb, 0xe2, 0xca, 0x1e, 0x74,
+	0x87, 0x95, 0x54, 0xfb, 0xdf, 0x11, 0x94, 0x12, 0x41, 0xe2, 0x6b, 0x28, 0xac, 0x8e, 0x02, 0x2b,
+	0x6c, 0xeb, 0x3e, 0xea, 0x0f, 0x58, 0x7c, 0x5e, 0x6c, 0x75, 0x5e, 0xec, 0x22, 0x3a, 0x2f, 0x7c,
+	0x09, 0xb9, 0x38, 0x33, 0x2c, 0xb3, 0x8d, 0xf0, 0xee, 0x54, 0xbc, 0x83, 0xfb, 0xc9, 0x54, 0xb0,
+	0xc6, 0xf6, 0xe4, 0x59, 0x3f, 0x63, 0x7b, 0xa3, 0x7b, 0x0b, 0xa5, 0xc4, 0x26, 0xf1, 0x94, 0xed,
+	0x06, 0x55, 0xaf, 0xb1, 0x7d, 0xcb, 0xfe, 0x00, 0xc7, 0x1b, 0x8b, 0xc5, 0x33, 0xb6, 0x6f, 0xd1,
+	0x77, 0xd9, 0xfe, 0x94, 0xff, 0x91, 0x8d, 0x91, 0x9c, 0xfe, 0x79, 0xf5, 0x3f, 0x00, 0x00, 0xff,
+	0xff, 0x62, 0xb0, 0xa1, 0x88, 0x83, 0x04, 0x00, 0x00,
 }
diff --git a/proto/firecracker.proto b/proto/firecracker.proto
index aae660e..3c05dc2 100644
--- a/proto/firecracker.proto
+++ b/proto/firecracker.proto
@@ -12,8 +12,11 @@ service Firecracker {
     // Stops existing Firecracker instance by VM ID
     rpc StopVM(StopVMRequest) returns (google.protobuf.Empty);
 
-    // Returns VM info by VM ID
-    rpc GetVMInfo(GetVMInfoRequest) returns (GetVMInfoResponse);
+    // Gets VM's instance socket file location
+    rpc GetVMAddress(GetVMAddressRequest) returns (GetVMAddressResponse);
+
+    // Gets VM's instance FIFO file location
+    rpc GetFifoPath(GetFifoPathRequest) returns (GetFifoPathResponse);
 
     // Sets VM's instance metadata
     rpc SetVMMetadata(SetVMMetadataRequest) returns (google.protobuf.Empty);
@@ -50,16 +53,26 @@ message StopVMRequest {
     string VMID = 1;
 }
 
-message GetVMInfoRequest {
+message GetVMAddressRequest {
     string VMID = 1;
 }
 
-message GetVMInfoResponse {
+message GetVMAddressResponse {
+    string SocketPath = 1;
+}
+
+enum FifoType {
+    LOG     = 0;
+    METRICS = 1;
+}
+
+message GetFifoPathRequest {
     string VMID = 1;
-    uint32 ContextID = 2;
-    string SocketPath = 3;
-    string LogFifoPath = 4;
-    string MetricsFifoPath = 5;
+    FifoType FifoType = 2;
+}
+
+message GetFifoPathResponse {
+    string Path = 1;
 }
 
 message SetVMMetadataRequest {
diff --git a/tools/docker/Dockerfile.runc-builder b/tools/docker/Dockerfile.runc-builder
new file mode 100644
index 0000000..9e0b3f7
--- /dev/null
+++ b/tools/docker/Dockerfile.runc-builder
@@ -0,0 +1,4 @@
+FROM golang:1.12-stretch
+
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -y install libseccomp-dev pkg-config
diff --git a/tools/image-builder/.gitignore b/tools/image-builder/.gitignore
new file mode 100644
index 0000000..57356b6
--- /dev/null
+++ b/tools/image-builder/.gitignore
@@ -0,0 +1,4 @@
+files_ephemeral
+*.img
+rootfs
+*stamp
diff --git a/tools/image-builder/Dockerfile.debian-image b/tools/image-builder/Dockerfile.debian-image
new file mode 100644
index 0000000..1b4687d
--- /dev/null
+++ b/tools/image-builder/Dockerfile.debian-image
@@ -0,0 +1,23 @@
+# 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.
+
+FROM debian:stretch-slim
+
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -y install \
+      --no-install-recommends \
+      make squashfs-tools debootstrap
+
+VOLUME ["/src"]
+WORKDIR "/src"
+ENTRYPOINT ["make"]
diff --git a/tools/image-builder/Makefile b/tools/image-builder/Makefile
new file mode 100644
index 0000000..51ee2d7
--- /dev/null
+++ b/tools/image-builder/Makefile
@@ -0,0 +1,97 @@
+# 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.
+
+UID        := $(shell id -u)
+WORKDIR    := rootfs
+WORKDIRLOC := $(shell readlink -f $(WORKDIR))
+IMAGE_DIRS := /dev /bin /etc /etc/init.d /tmp /var /run /proc /sys /container/rootfs /agent /rom /overlay
+DIRS       := $(foreach dir,$(IMAGE_DIRS),"$(WORKDIR)$(dir)")
+DEBMIRROR  ?= http://deb.debian.org/debian
+
+# When referenced in a _stamp target's recipe,
+# e.g. files_debootstrap_stamp, this function uses tar to copy the
+# contents of the base directory (e.g. files_debootstrap) to the image
+# root. In order for the file locations to be correctly represented in
+# the image root, the directory structure of the source directory
+# should match that of the root image, e.g. if files_debootstrap
+# contains usr/local/bin/foo, then 'foo' will be installed in
+# /usr/local/bin/ in the root filesystem.
+define install_dir =
+if [ -d $(subst _stamp,,$@) ]; then \
+  cd $(subst _stamp,,$@) && tar cf - . | (cd "$(WORKDIRLOC)" && tar xvf -);\
+fi
+# Reset the timestamp on $(WORKDIR), which may have been modified by the
+# creation of files in it:
+touch --reference=debootstrap_stamp --no-create "$(WORKDIR)"
+endef
+
+all: rootfs.img
+
+$(WORKDIR):
+	mkdir $(WORKDIR)
+
+image_files=$(shell find files_* -mindepth 1 -type f -print)
+files_%_stamp: debootstrap_stamp $(image_files)
+	$(install_dir)
+	touch $@
+
+debootstrap: debootstrap_stamp
+
+debootstrap_stamp: $(WORKDIR)
+ifneq ($(UID),0)
+	$(error $(@) needs to run as root, not $(UID))
+endif
+	debootstrap \
+		--variant=minbase \
+		--include=udev,systemd,systemd-sysv,procps,libseccomp2 \
+		stretch \
+		"$(WORKDIR)" $(DEBMIRROR)
+	rm -rf "$(WORKDIR)/var/cache/apt/archives" \
+	       "$(WORKDIR)/usr/share/doc" \
+	       "$(WORKDIR)/var/lib/apt/lists"
+	mkdir -p $(DIRS)
+	touch $@
+
+rootfs.img: files_common_stamp files_debootstrap_stamp files_ephemeral_stamp
+	mksquashfs "$(WORKDIR)" rootfs.img -noappend
+
+builder: builder_stamp
+
+builder_stamp:
+	docker build -t fc-image-builder -f Dockerfile.debian-image .
+	touch $@
+
+# For any given target, append "-in-docker" to it to run the build
+# recipe in a container, e.g. instead of:
+# $ make rootfs.img
+# you can use
+# $ make rootfs.img-in-docker
+%-in-docker: builder_stamp
+	docker run --rm \
+		--security-opt=apparmor=unconfined \
+		-it --volume $(CURDIR):/src \
+		--cap-add=sys_admin \
+		--cap-add=sys_chroot \
+		--env=DEBMIRROR \
+		fc-image-builder:latest $(subst -in-docker,,$@)
+
+clean:
+	-rm -f *stamp
+	-rm -rf $(WORKDIR)
+	-rm -f rootfs.img
+
+distclean: clean-in-docker
+	rm -rf files_ephemeral
+	docker rmi fc-image-builder:latest
+
+.PHONY: debootstrap clean distclean builder %-in-docker
diff --git a/tools/image-builder/README.md b/tools/image-builder/README.md
new file mode 100644
index 0000000..eb1a7dc
--- /dev/null
+++ b/tools/image-builder/README.md
@@ -0,0 +1,53 @@
+# Create Firecracker VM images for use with firecracker-containerd #
+
+## Debian root image ##
+
+### Overview ###
+
+The image builder component of firecracker-containerd will build a
+microVM image including the necessary components to support container
+management inside the microVM. In particular, the
+firecracker-containerd runtime agent and runc binary will be installed
+in the image.
+
+The image is generated as a read-only squashfs image. A read/write
+overlay layer is supported via the /sbin/overlay-init program, which
+should be used as init (e.g. by passing `init=/sbin/overlay-init` as a
+kernel boot parameter). By default, overlay-init allocates a tmpfs
+filesystem for use as the upper layer, but a block device can be
+provided via the `overlay_root` kernel parameter,
+e.g. `overlay_root=vdc`. This device should already contain a
+(possibly empty) ext4 filesystem. By using a block device, it is
+possible to preserve the filesystem state beyond the termination of
+the VM, and potentially re-use it for subsequent VM execution.
+
+The image currently expects `vdb` to be the block device containing
+the container root filesystem, and this device is mounted on
+`/container/rootfs`.
+
+If the `vsock_srv` program from the
+[clownix](https://github.com/clownix/cloonix_vsock) github repository
+is present in `ephemeral_files/bin/` when the image is built, it will
+be embedded in the image and run at boot, allowing easy shell access
+to the VM. This can be convenient for debugging, but it is not
+embedded by default for security and licensing reasons.
+
+### Generation ###
+
+There are two alternatives for providing the build environment. You
+can perform the image build in Docker, in which case the only
+build-time dependency is that you can launch Docker container directly
+(i.e. without `sudo`, etc). To build an image in this configuration, use:
+
+`$ make rootfs.img-in-docker`
+
+Alternatively, to build outside a container, you'll need:
+
+* To run the build process as root.
+* `[debootstrap](https://salsa.debian.org/installer-team/debootstrap)`
+  (Install via the package of the same name on Debian and Ubuntu)
+* `mksquashfs`, available in the
+   `[squashfs-tools](https://packages.debian.org/stretch/squashfs-tools)
+   package on Debian and Ubuntu.
+
+Then execute `make rootfs.img`
diff --git a/tools/image-builder/files_debootstrap/etc/hostname b/tools/image-builder/files_debootstrap/etc/hostname
new file mode 100644
index 0000000..181fd07
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/hostname
@@ -0,0 +1,1 @@
+microvm
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/apt-daily-upgrade.timer b/tools/image-builder/files_debootstrap/etc/systemd/system/apt-daily-upgrade.timer
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/apt-daily-upgrade.timer
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/apt-daily.timer b/tools/image-builder/files_debootstrap/etc/systemd/system/apt-daily.timer
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/apt-daily.timer
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount b/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount
new file mode 100644
index 0000000..fedf6ca
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount
@@ -0,0 +1,9 @@
+[Unit]
+Description=Single container root filesystem
+ConditionPathIsDirectory=/container/rootfs
+DefaultDependencies=no
+Before=local-fs.target
+
+[Mount]
+What=/dev/vdb
+Where=/container/rootfs
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service
new file mode 100644
index 0000000..2c8e239
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Firecracker VM agent
+StartLimitIntervalSec=2
+ConditionPathIsMountPoint=/container/rootfs
+After=local-fs.target
+Requires=local-fs.target
+
+[Service]
+Type=simple
+WorkingDirectory=/container
+ExecStart=/usr/local/bin/agent -id 1
+Restart=always
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target
new file mode 100644
index 0000000..f82ccc2
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target
@@ -0,0 +1,6 @@
+[Unit]
+Description=Firecracker containerd VM
+Requires=basic.target
+Conflicts=rescue.service rescue.target
+After=basic.target rescue.service rescue.target
+AllowIsolate=yes
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/container-rootfs.mount b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/container-rootfs.mount
new file mode 120000
index 0000000..9481a79
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/container-rootfs.mount
@@ -0,0 +1,1 @@
+/etc/systemd/system/container-rootfs.mount
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/firecracker-agent.service b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/firecracker-agent.service
new file mode 120000
index 0000000..f5467fc
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/firecracker-agent.service
@@ -0,0 +1,1 @@
+/etc/systemd/system/firecracker-agent.service
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/getty.target b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/getty.target
new file mode 120000
index 0000000..d6896c3
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/getty.target
@@ -0,0 +1,1 @@
+/lib/systemd/system/getty.target
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/vsock-cli.service b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/vsock-cli.service
new file mode 120000
index 0000000..2efa166
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target.wants/vsock-cli.service
@@ -0,0 +1,1 @@
+/etc/systemd/system/vsock-cli.service
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/getty-static.service b/tools/image-builder/files_debootstrap/etc/systemd/system/getty-static.service
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/getty-static.service
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/rom-dev.mount b/tools/image-builder/files_debootstrap/etc/systemd/system/rom-dev.mount
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/rom-dev.mount
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/rom-overlay.mount b/tools/image-builder/files_debootstrap/etc/systemd/system/rom-overlay.mount
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/rom-overlay.mount
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/rom.mount b/tools/image-builder/files_debootstrap/etc/systemd/system/rom.mount
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/rom.mount
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/systemd-timesyncd.service b/tools/image-builder/files_debootstrap/etc/systemd/system/systemd-timesyncd.service
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/systemd-timesyncd.service
@@ -0,0 +1,1 @@
+/dev/null
\ No newline at end of file
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/vsock-cli.service b/tools/image-builder/files_debootstrap/etc/systemd/system/vsock-cli.service
new file mode 100644
index 0000000..57c596b
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/vsock-cli.service
@@ -0,0 +1,28 @@
+# 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.
+#
+# If the vsock_srv program from
+# https://github.com/clownix/cloonix_vsock is present, let's run it as
+# a service. It's useful for debug and test, but we don't want it
+# installed by default.
+
+[Unit]
+Description=vsock login service
+ConditionFileIsExecutable=/bin/vsock_srv
+StartLimitIntervalSec=2
+
+[Service]
+Type=forking
+ExecStart=/bin/vsock_srv 9999
+GuessMainPID=yes
+Restart=always
diff --git a/tools/image-builder/files_debootstrap/sbin/overlay-init b/tools/image-builder/files_debootstrap/sbin/overlay-init
new file mode 100755
index 0000000..a27b845
--- /dev/null
+++ b/tools/image-builder/files_debootstrap/sbin/overlay-init
@@ -0,0 +1,64 @@
+#!/bin/sh
+# 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.
+
+set -x
+
+# Parameters:
+# 1. rw_root -- path where the read/write root is mounted
+# 2. work_dir -- path to the overlay workdir (must be on same filesystem as rw_root)
+# Overlay will be set up on /mnt, original root on /mnt/rom
+pivot() {
+    local rw_root work_dir
+    rw_root="$1"
+    work_dir="$2"
+    /bin/mount \
+	-o noatime,lowerdir=/,upperdir=${rw_root},workdir=${work_dir} \
+	-t overlay "overlayfs:${rw_root}" /mnt
+    pivot_root /mnt /mnt/rom
+}
+
+# Overlay is configured under /overlay
+# Global variable $overlay_root is expected to be set to either:
+# "ram", which configures a tmpfs as the rw overlay layer (this is
+# the default, if the variable is unset)
+# - or -
+# A block device name, relative to /dev, in which case it is assumed
+# to contain an ext4 filesystem suitable for use as a rw overlay
+# layer. e.g. "vdb"
+do_overlay() {
+    local overlay_dir="/overlay"
+    if [ "$overlay_root" = ram ] ||
+           [ -z "$overlay_root" ]; then
+        /bin/mount -t tmpfs -o noatime,mode=0755 tmpfs /overlay
+    else
+        /bin/mount -t ext4 "/dev/$overlay_root" /overlay
+    fi
+    mkdir -p /overlay/root /overlay/work
+    pivot /overlay/root /overlay/work
+}
+
+# If we're given an overlay, ensure that it really exists. Panic if not.
+if [ -n "$overlay_root" ] &&
+       [ "$overlay_root" != ram ] &&
+       [ ! -b "/dev/$overlay_root" ]; then
+    echo -n "FATAL: "
+    echo "Overlay root given as $overlay_root but /dev/$overlay_root does not exist"
+    exit 1
+fi
+
+do_overlay
+
+# invoke the actual system init program and procede with the boot
+# process.
+exec /sbin/init $@


Test patch:
diff --git a/firecracker-control/local_mock_test.go b/firecracker-control/local_mock_test.go
index 5778cdd..8d50a6b 100644
--- a/firecracker-control/local_mock_test.go
+++ b/firecracker-control/local_mock_test.go
@@ -5,10 +5,10 @@
 package service
 
 import (
-	context "context"
 	events "github.com/containerd/containerd/events"
 	firecracker_go_sdk "github.com/firecracker-microvm/firecracker-go-sdk"
 	gomock "github.com/golang/mock/gomock"
+	context "golang.org/x/net/context"
 	reflect "reflect"
 )
 
diff --git a/firecracker-control/local_test.go b/firecracker-control/local_test.go
index f826390..947e8d3 100644
--- a/firecracker-control/local_test.go
+++ b/firecracker-control/local_test.go
@@ -136,26 +136,29 @@ func TestLocal_StopInvalidVM(t *testing.T) {
 	assert.Equal(t, ErrVMNotFound, err)
 }
 
-func TestLocal_GetVMInfo(t *testing.T) {
-	cfg := &firecracker.Config{
-		VsockDevices: []firecracker.VsockDevice{{CID: 123}},
-		SocketPath:   "socket",
-		LogFifo:      "logs",
-		MetricsFifo:  "metrics",
+func TestLocal_GetVMAddress(t *testing.T) {
+	obj := &local{
+		vm: map[string]*instance{"1": {cfg: &firecracker.Config{SocketPath: "2"}}},
 	}
 
+	response, err := obj.GetVMAddress(testCtx, &proto.GetVMAddressRequest{VMID: "1"})
+	assert.NoError(t, err)
+	assert.NotNil(t, response)
+	assert.Equal(t, "2", response.SocketPath)
+}
+
+func TestLocal_GetFifoPath(t *testing.T) {
 	obj := &local{
-		vm: map[string]*instance{"1": {cfg: cfg}},
+		vm: map[string]*instance{"1": {cfg: &firecracker.Config{LogFifo: "logs", MetricsFifo: "metrics"}}},
 	}
 
-	response, err := obj.GetVMInfo(testCtx, &proto.GetVMInfoRequest{VMID: "1"})
+	response, err := obj.GetFifoPath(testCtx, &proto.GetFifoPathRequest{VMID: "1", FifoType: proto.FifoType_LOG})
 	assert.NoError(t, err)
-	assert.NotNil(t, response)
+	assert.Equal(t, "logs", response.Path)
 
-	assert.EqualValues(t, 123, response.ContextID)
-	assert.Equal(t, "socket", response.SocketPath)
-	assert.Equal(t, "logs", response.LogFifoPath)
-	assert.Equal(t, "metrics", response.MetricsFifoPath)
+	response, err = obj.GetFifoPath(testCtx, &proto.GetFifoPathRequest{VMID: "1", FifoType: proto.FifoType_METRICS})
+	assert.NoError(t, err)
+	assert.Equal(t, "metrics", response.Path)
 }
 
 func TestLocal_SetVMMetadata(t *testing.T) {