diff --git a/Dockerfile b/Dockerfile
index ff9c6b4..91a18db 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,16 @@
-FROM golang:1.18 AS starlight-proxy-build
+#########################################################################
+# starlight-proxy
+#########################################################################
+FROM golang:1.20 AS starlight-proxy-build
 
 WORKDIR /go/src/app
 COPY . .
 
 ENV GO111MODULE=on
+ENV PRODUCTION=true
 
 RUN make change-version-number set-production starlight-proxy-for-alpine
-
-#CMD ["/go/src/app/out/starlight-proxy"]
+#########################################################################
 FROM alpine:3.12 AS starlight-proxy
 
 COPY --from=starlight-proxy-build /go/src/app/out/ /opt/
@@ -15,18 +18,22 @@ WORKDIR /opt
 EXPOSE 8090
 CMD ["/opt/starlight-proxy"]
 
-FROM golang:1.18 AS starlight-cli-build
+#########################################################################
+# ctr-starlight
+#########################################################################
+FROM golang:1.20 AS starlight-cli-build
 
 WORKDIR /go/src/app
 COPY . .
 
 ENV GO111MODULE=on
+ENV PRODUCTION=true
 
 RUN make change-version-number set-production ctr-starlight-for-alpine
-
+#########################################################################
 FROM alpine:3.12 AS starlight-cli
 
 COPY --from=starlight-cli-build /go/src/app/out/ /opt/
 WORKDIR /opt
 EXPOSE 8090
-CMD ["/opt/ctr-starlight"]
\ No newline at end of file
+CMD ["/opt/ctr-starlight"]
diff --git a/Makefile b/Makefile
index 32f0d3c..9146578 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,6 @@ VERSIONNUMBER=$(shell echo $(VERSION) | sed 's/v//g')
 COMPILEDATE=$(shell date +%Y%m%d)
 
 
-.PHONY: build clean starlight-proxy starlight-daemon ctr-starlight test
 .SILENT: install-systemd-service
 
 ######################################################################
@@ -57,6 +56,12 @@ starlight-proxy-for-alpine:
 helm-package:
 	helm package ./demo/chart --version $(VERSIONNUMBER) -d /tmp
 
+.PHONE: helm-template
+helm-template:
+	@if [ ! -d ./sandbox ]; then mkdir ./sandbox; fi
+	@if [ ! -f ./sandbox/values.yaml ]; then cp ./demo/values.yaml ./sandbox/values.yaml; fi
+	@helm template ./demo/chart --version $(VERSIONNUMBER) -f ./sandbox/values.yaml
+
 .PHONY: push-helm-package
 push-helm-package:
 	helm push /tmp/starlight-$(VERSIONNUMBER).tgz oci://ghcr.io/mc256/starlight/
diff --git a/client/client.go b/client/client.go
index 9bcdab8..56e6d18 100644
--- a/client/client.go
+++ b/client/client.go
@@ -469,10 +469,10 @@ func (c *Client) UploadTraces(proxyCfg string, tc *fs.TraceCollection) error {
 }
 
 type PullFinishedMessage struct {
-	img       *images.Image
-	imageSize int64
-	base      string
-	err       error
+	img  *images.Image
+	meta *common.DeltaImageMetadata
+	base string
+	err  error
 }
 
 // for testing only
@@ -489,7 +489,7 @@ func (c *Client) pullImageGrpc(ns, base, ref, proxy string, ret *chan PullFinish
 	// connect to containerd
 	ctr, err := containerd.New(c.cfg.Containerd, containerd.WithDefaultNamespace(ns))
 	if err != nil {
-		*ret <- PullFinishedMessage{nil, 0, "", errors.Wrapf(err, "failed to connect to containerd")}
+		*ret <- PullFinishedMessage{nil, nil, "", errors.Wrapf(err, "failed to connect to containerd")}
 		return
 	}
 	defer ctr.Close()
@@ -498,7 +498,7 @@ func (c *Client) pullImageGrpc(ns, base, ref, proxy string, ret *chan PullFinish
 	var baseImg containerd.Image
 	baseImg, err = c.FindBaseImage(ctr, base, ref)
 	if err != nil {
-		*ret <- PullFinishedMessage{nil, 0, "", errors.Wrapf(err, "failed to identify base image")}
+		*ret <- PullFinishedMessage{nil, nil, "", errors.Wrapf(err, "failed to identify base image")}
 		return
 	}
 
@@ -524,7 +524,7 @@ func (c *Client) PullImage(
 	reqFilter := getImageFilter(ref, false)
 	img, err := c.findImage(ctr, reqFilter)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, "", errors.Wrapf(err, "failed to check requested image %s", ref)}
+		*ready <- PullFinishedMessage{nil, nil, "", errors.Wrapf(err, "failed to check requested image %s", ref)}
 		return
 	}
 
@@ -532,11 +532,11 @@ func (c *Client) PullImage(
 		labels := img.Labels()
 		if _, has := labels[util.ContentLabelCompletion]; has {
 			if _, err = c.LoadImage(ctr, img.Target().Digest); err != nil {
-				*ready <- PullFinishedMessage{nil, 0, "", errors.Wrapf(err, "failed to load image %s", ref)}
+				*ready <- PullFinishedMessage{nil, nil, "", errors.Wrapf(err, "failed to load image %s", ref)}
 				return
 			}
 			meta := img.Metadata()
-			*ready <- PullFinishedMessage{&meta, 0, "", fmt.Errorf("requested image %s already exists", ref)}
+			*ready <- PullFinishedMessage{&meta, nil, "", fmt.Errorf("requested image %s already exists", ref)}
 			return
 
 		}
@@ -549,7 +549,7 @@ func (c *Client) PullImage(
 			log.G(c.ctx).
 				WithField("image", ref).
 				Info("failed to remove incomplete image")
-			*ready <- PullFinishedMessage{nil, 0, "", errors.Wrapf(err, "failed to remove unfinished image %s", ref)}
+			*ready <- PullFinishedMessage{nil, nil, "", errors.Wrapf(err, "failed to remove unfinished image %s", ref)}
 			return
 		}
 	}
@@ -569,7 +569,7 @@ func (c *Client) PullImage(
 	// pull image
 	body, res, err := p.DeltaImage(baseRef, ref, platform)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, "", errors.Wrapf(err, "failed to pull image %s", ref)}
+		*ready <- PullFinishedMessage{nil, nil, "", errors.Wrapf(err, "failed to pull image %s", ref)}
 		return
 	}
 	defer func() {
@@ -628,53 +628,53 @@ func (c *Client) PullImage(
 	// manifest
 	buf, err = c.readBody(body, res.ManifestSize)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to read manifest")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to read manifest")}
 		return
 	}
 	manifest, man, err = c.handleManifest(buf)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to handle manifest")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to handle manifest")}
 		return
 	}
 	err = c.storeManifest(cs, pcn, res.Digest, ref,
 		manifest.Config.Digest.String(), res.StarlightDigest,
 		man)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to store manifest")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to store manifest")}
 		return
 	}
 
 	// config
 	buf, err = c.readBody(body, res.ConfigSize)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to read config")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to read config")}
 		return
 	}
 	imageConfig, con, err = c.handleConfig(buf)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to handle config")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to handle config")}
 		return
 	}
 	err = c.storeConfig(cs, pcn, ref, manifest.Config.Digest, con)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to store config")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to store config")}
 		return
 	}
 
 	// starlight header
 	buf, err = c.readBody(body, res.StarlightHeaderSize)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to read starlight header")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to read starlight header")}
 		return
 	}
 	star, sta, err := c.handleStarlightHeader(buf)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to handle starlight header")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to handle starlight header")}
 		return
 	}
 	err = c.storeStarlightHeader(cs, pcn, ref, res.StarlightDigest, sta)
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to store starlight header")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to store starlight header")}
 		return
 	}
 
@@ -695,7 +695,7 @@ func (c *Client) PullImage(
 		UpdatedAt: time.Now(),
 	})
 	if err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to create image %s", ref)}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to create image %s", ref)}
 		return
 	}
 
@@ -721,13 +721,13 @@ func (c *Client) PullImage(
 			log.G(c.ctx).
 				WithError(err).
 				Error("failed to set optimizer on")
-			*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to enable optimizer")}
+			*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to enable optimizer")}
 			return
 		}
 	}
 
 	if err = star.PrepareDirectories(c); err != nil {
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to initialize directories")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to initialize directories")}
 		return
 	}
 
@@ -739,7 +739,7 @@ func (c *Client) PullImage(
 		log.G(c.ctx).
 			WithError(err).
 			Error("failed to create snapshots")
-		*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to create snapshots")}
+		*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to create snapshots")}
 		return
 	}
 
@@ -751,7 +751,7 @@ func (c *Client) PullImage(
 	// close(*ready)
 	//
 	// wola! we are done here.
-	*ready <- PullFinishedMessage{&ctrImg, res.ContentLength, baseRef, nil}
+	*ready <- PullFinishedMessage{&ctrImg, res, baseRef, nil}
 
 	// 6. Extract file content
 	// download content
@@ -761,7 +761,7 @@ func (c *Client) PullImage(
 
 	if err = star.Extract(&body); err != nil {
 		if ready != nil { // second signal
-			*ready <- PullFinishedMessage{nil, 0, baseRef, errors.Wrapf(err, "failed to extract starlight image")}
+			*ready <- PullFinishedMessage{nil, nil, baseRef, errors.Wrapf(err, "failed to extract starlight image")}
 		}
 		return
 	}
@@ -792,7 +792,7 @@ func (c *Client) PullImage(
 	}
 
 	if ready != nil { // second signal
-		*ready <- PullFinishedMessage{&ctrImg, res.ContentLength, baseRef, nil}
+		*ready <- PullFinishedMessage{&ctrImg, res, baseRef, nil}
 	}
 }
 
diff --git a/client/grpc.go b/client/grpc.go
index 52e1ddf..9b7474e 100644
--- a/client/grpc.go
+++ b/client/grpc.go
@@ -96,32 +96,40 @@ func (s *StarlightDaemonAPIServer) PullImage(ctx context.Context, ref *pb.ImageR
 
 	if ret.err != nil {
 		if ret.img != nil {
+			// requested image is already pulled
 			return &pb.ImagePullResponse{
 				Success:        true,
 				Message:        ret.err.Error(),
 				BaseImage:      ret.base,
-				TotalImageSize: ret.imageSize,
+				TotalImageSize: ret.meta.ContentLength,
 			}, nil
 		} else {
 			return &pb.ImagePullResponse{
 				Success:        false,
 				Message:        ret.err.Error(),
 				BaseImage:      ret.base,
-				TotalImageSize: ret.imageSize,
+				TotalImageSize: ret.meta.ContentLength,
 			}, nil
 		}
 	}
 
 	// wait for the entire delta image to be pulled to the local filesystem
 	// holding on the second signal from the pullImageGrpc goroutine
-	if ref.DisableEarlyStart { 
+	if ref.DisableEarlyStart {
 		ret := <-ready // second signal
-		if ret.err != nil {
+		if ret.img != nil {
+			return &pb.ImagePullResponse{
+				Success:        true,
+				Message:        ret.err.Error(),
+				BaseImage:      ret.base,
+				TotalImageSize: ret.meta.ContentLength,
+			}, nil
+		} else {
 			return &pb.ImagePullResponse{
 				Success:        false,
 				Message:        ret.err.Error(),
 				BaseImage:      ret.base,
-				TotalImageSize: ret.imageSize,
+				TotalImageSize: ret.meta.ContentLength,
 			}, nil
 		}
 	}
diff --git a/cmd/ctr-starlight/listproxy/list_proxy.go b/cmd/ctr-starlight/listproxy/list_proxy.go
index 3e2a128..6ab1bf0 100644
--- a/cmd/ctr-starlight/listproxy/list_proxy.go
+++ b/cmd/ctr-starlight/listproxy/list_proxy.go
@@ -59,8 +59,7 @@ func Action(context *cli.Context) (err error) {
 	}
 
 	// send to proxy server
-	err = listProxyProfile(pb.NewDaemonClient(conn))
-	if err != nil {
+	if err = listProxyProfile(pb.NewDaemonClient(conn)); err != nil {
 		return err
 	}
 
diff --git a/cmd/ctr-starlight/main.go b/cmd/ctr-starlight/main.go
index 2e0bf43..a50da1a 100644
--- a/cmd/ctr-starlight/main.go
+++ b/cmd/ctr-starlight/main.go
@@ -45,9 +45,10 @@ func init() {
 func main() {
 	app := NewApp()
 	if err := app.Run(os.Args); err != nil {
-		_, _ = fmt.Fprintf(os.Stderr, "ctr-starlight: \n%v\n", err)
+		_, _ = fmt.Fprintf(os.Stderr, "ctr-starlight: %v\n", err)
 		os.Exit(1)
 	}
+	os.Exit(0)
 }
 
 func NewApp() *cli.App {
@@ -93,7 +94,7 @@ https://github.com/mc256/starlight
 			Required:    false,
 		},
 	}
-	app.Commands = append([]*cli.Command{
+	app.Commands = []*cli.Command{
 		cmdVersion.Command(),   // 1. confirm the version of starlight-daemon
 		cmdAddProxy.Command(),  // 2. add starlight proxy to the daemon
 		cmdListProxy.Command(), // 3. list proxy profiles in daemon
@@ -103,7 +104,7 @@ https://github.com/mc256/starlight
 		cmdOptimizer.Command(), // 7. turn on/off filesystem traces
 		cmdReport.Command(),    // 8. upload filesystem traces to starlight proxy
 		cmdPull.Command(),      // 9. pull starlight image
-	})
+	}
 
 	return app
 }
diff --git a/cmd/ctr-starlight/notify/notify.go b/cmd/ctr-starlight/notify/notify.go
index 32b7a2f..a777928 100644
--- a/cmd/ctr-starlight/notify/notify.go
+++ b/cmd/ctr-starlight/notify/notify.go
@@ -9,6 +9,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+
 	"github.com/containerd/containerd/log"
 	"github.com/google/go-containerregistry/pkg/name"
 	pb "github.com/mc256/starlight/client/api"
@@ -18,11 +19,10 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 )
 
-func notify(client pb.DaemonClient, req *pb.NotifyRequest, quiet bool) {
+func notify(client pb.DaemonClient, req *pb.NotifyRequest, quiet bool) error {
 	resp, err := client.NotifyProxy(context.Background(), req)
 	if err != nil {
-		fmt.Printf("notify starlight proxy server failed: %v\n", err)
-		return
+		return fmt.Errorf("notify starlight proxy server failed: %v", err)
 	}
 	if resp.Success {
 		if !quiet {
@@ -31,6 +31,7 @@ func notify(client pb.DaemonClient, req *pb.NotifyRequest, quiet bool) {
 	} else {
 		fmt.Printf("notify starlight proxy server failed: %v\n", resp)
 	}
+	return nil
 }
 
 func SharedAction(ctx context.Context, c *cli.Context, reference name.Reference) (err error) {
@@ -45,13 +46,11 @@ func SharedAction(ctx context.Context, c *cli.Context, reference name.Reference)
 	defer conn.Close()
 
 	// notify
-	notify(pb.NewDaemonClient(conn), &pb.NotifyRequest{
+	return notify(pb.NewDaemonClient(conn), &pb.NotifyRequest{
 		ProxyConfig: c.String("profile"),
 		Insecure:    c.Bool("insecure") || c.Bool("insecure-destination"),
 		Reference:   reference.String(),
 	}, c.Bool("quiet"))
-
-	return nil
 }
 
 func Action(ctx context.Context, c *cli.Context) (err error) {
diff --git a/cmd/ctr-starlight/optimizer/optimizer.go b/cmd/ctr-starlight/optimizer/optimizer.go
index 7d5dcb9..1ebd1ed 100644
--- a/cmd/ctr-starlight/optimizer/optimizer.go
+++ b/cmd/ctr-starlight/optimizer/optimizer.go
@@ -8,20 +8,20 @@ package optimizer
 import (
 	"context"
 	"fmt"
+	"time"
+
 	pb "github.com/mc256/starlight/client/api"
 	"github.com/urfave/cli/v2"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
-	"time"
 )
 
-func optimizer(client pb.DaemonClient, req *pb.OptimizeRequest, quiet bool) {
+func optimizer(client pb.DaemonClient, req *pb.OptimizeRequest, quiet bool) error {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 	defer cancel()
 	resp, err := client.SetOptimizer(ctx, req)
 	if err != nil {
-		fmt.Printf("set optimizer status failed: %v\n", err)
-		return
+		return fmt.Errorf("set optimizer status failed: %v", err)
 	}
 	if resp.Success {
 		if !quiet {
@@ -36,6 +36,7 @@ func optimizer(client pb.DaemonClient, req *pb.OptimizeRequest, quiet bool) {
 	} else {
 		fmt.Printf("set optimizer status failed: %s\n", resp.Message)
 	}
+	return nil
 }
 
 func Action(ctx context.Context, c *cli.Context) (err error) {
@@ -66,12 +67,10 @@ func Action(ctx context.Context, c *cli.Context) (err error) {
 	defer conn.Close()
 
 	// set optimizer
-	optimizer(pb.NewDaemonClient(conn), &pb.OptimizeRequest{
+	return optimizer(pb.NewDaemonClient(conn), &pb.OptimizeRequest{
 		Enable: a,
 		Group:  c.String("group"),
 	}, c.Bool("quiet"))
-
-	return nil
 }
 
 func Command() *cli.Command {
diff --git a/cmd/ctr-starlight/ping/ping.go b/cmd/ctr-starlight/ping/ping.go
index 17c7ec8..4a7bd03 100644
--- a/cmd/ctr-starlight/ping/ping.go
+++ b/cmd/ctr-starlight/ping/ping.go
@@ -9,6 +9,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+
 	pb "github.com/mc256/starlight/client/api"
 	"github.com/urfave/cli/v2"
 	"google.golang.org/grpc"
diff --git a/cmd/ctr-starlight/pull/pull.go b/cmd/ctr-starlight/pull/pull.go
index 32e4b05..b5628d1 100644
--- a/cmd/ctr-starlight/pull/pull.go
+++ b/cmd/ctr-starlight/pull/pull.go
@@ -17,21 +17,29 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 )
 
-func pullImage(client pb.DaemonClient, ref *pb.ImageReference, quiet bool) {
+func pullImage(client pb.DaemonClient, ref *pb.ImageReference, quiet bool) error {
+	if ref.DisableEarlyStart {
+		fmt.Printf("early start is disabled\n")
+	}
 	start := time.Now()
 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30)
 	defer cancel()
 	resp, err := client.PullImage(ctx, ref)
 	if err != nil {
-		fmt.Printf("pull image failed: %v\n", err)
-		return
+		return fmt.Errorf("pull image failed: %v", err)
 	}
 	if resp.Success {
 		if quiet {
-			return
+			return nil
 		}
 		end := time.Now()
 
+		if resp.GetMessage() != "" {
+			// it is likely that the image is already pulled
+			fmt.Printf("%s\n", resp.GetMessage())
+			return nil
+		}
+
 		if resp.GetBaseImage() == "" {
 			fmt.Printf("requested to pull image %s successfully in %dms \n",
 				ref.Reference,
@@ -43,13 +51,11 @@ func pullImage(client pb.DaemonClient, ref *pb.ImageReference, quiet bool) {
 				end.Sub(start).Milliseconds(),
 			)
 		}
-		if ref.DisableEarlyStart {
-			fmt.Printf("entire image %s has been downloaded because early start is disabled\n", ref.Reference)
-		}
-		fmt.Printf("delta image size: %d bytes\n", resp.GetTotalImageSize())
+		fmt.Printf("delta image size: %d bytes\n", resp.TotalImageSize)
 	} else {
 		fmt.Printf("pull image failed: %s\n", resp.Message)
 	}
+	return nil
 }
 
 func Action(ctx context.Context, c *cli.Context) error {
@@ -74,14 +80,13 @@ func Action(ctx context.Context, c *cli.Context) error {
 	defer conn.Close()
 
 	// pull image
-	pullImage(pb.NewDaemonClient(conn), &pb.ImageReference{
+	return pullImage(pb.NewDaemonClient(conn), &pb.ImageReference{
 		Reference:         ref,
 		Base:              base,
 		ProxyConfig:       c.String("profile"),
 		Namespace:         c.String("namespace"),
 		DisableEarlyStart: c.Bool("disable-early-start"),
 	}, c.Bool("quiet"))
-	return nil
 }
 
 func Command() *cli.Command {
diff --git a/cmd/ctr-starlight/report/report.go b/cmd/ctr-starlight/report/report.go
index 50f2d40..1f00bfd 100644
--- a/cmd/ctr-starlight/report/report.go
+++ b/cmd/ctr-starlight/report/report.go
@@ -21,23 +21,23 @@ package report
 import (
 	"context"
 	"fmt"
+	"time"
+
 	pb "github.com/mc256/starlight/client/api"
 	"github.com/mc256/starlight/cmd/ctr-starlight/auth"
 	"github.com/urfave/cli/v2"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
-	"time"
 )
 
 // report uses the daemon client to report traces, it does not report directly to the proxy
 // Because it does not handle the credential and authentication of the proxy.
-func report(client pb.DaemonClient, req *pb.ReportTracesRequest, quiet bool) {
+func report(client pb.DaemonClient, req *pb.ReportTracesRequest, quiet bool) error {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 	defer cancel()
 	resp, err := client.ReportTraces(ctx, req)
 	if err != nil {
-		fmt.Printf("report traces failed: %v\n", err)
-		return
+		return fmt.Errorf("report traces failed: %v", err)
 	}
 	if resp.Success {
 		if !quiet {
@@ -46,6 +46,7 @@ func report(client pb.DaemonClient, req *pb.ReportTracesRequest, quiet bool) {
 	} else {
 		fmt.Printf("report traces failed: %s\n", resp.Message)
 	}
+	return nil
 }
 
 func Action(ctx context.Context, c *cli.Context) (err error) {
@@ -60,10 +61,9 @@ func Action(ctx context.Context, c *cli.Context) (err error) {
 	defer conn.Close()
 
 	// report
-	report(pb.NewDaemonClient(conn), &pb.ReportTracesRequest{
+	return report(pb.NewDaemonClient(conn), &pb.ReportTracesRequest{
 		ProxyConfig: c.String("profile"),
 	}, c.Bool("quiet"))
-	return nil
 }
 
 func Command() *cli.Command {
diff --git a/cmd/ctr-starlight/version/version.go b/cmd/ctr-starlight/version/version.go
index f346101..369cae7 100644
--- a/cmd/ctr-starlight/version/version.go
+++ b/cmd/ctr-starlight/version/version.go
@@ -8,12 +8,13 @@ package version
 import (
 	"context"
 	"fmt"
+	"time"
+
 	pb "github.com/mc256/starlight/client/api"
 	"github.com/mc256/starlight/util"
 	"github.com/urfave/cli/v2"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
-	"time"
 )
 
 func getVersion(client pb.DaemonClient) (string, error) {
diff --git a/demo/chart/templates/NOTES.txt b/demo/chart/templates/NOTES.txt
index 2c373d4..9b1164e 100644
--- a/demo/chart/templates/NOTES.txt
+++ b/demo/chart/templates/NOTES.txt
@@ -80,9 +80,11 @@ ON THE WORKER NODE:
   If using k3s, please create `/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl` and add the configuration.
   You will need to change `containerd` socket address to `/run/k3s/containerd/containerd.sock` in `/etc/starlight/proxy.json`.
 
-3. and restart the container
+3. and restart the container 
 
-  sudo systemctl restart containerd
+  sudo systemctl restart containerd starlight
+
+  if using k3s, please restart k3s-agent using `sudo systemctl restart k3s-agent starlight`
 
 
 4. Add Starlight Proxy to the edge node, test to see if it works
@@ -91,7 +93,7 @@ ON THE WORKER NODE:
   {{- end }}
   ctr-starlight test in-cluster
 
-5. Convert Container Images (on the edge node)
+5. Convert Container Images
 
   ctr-starlight convert \
     --insecure-destination --profile in-cluster --notify \
diff --git a/demo/chart/templates/daemonset-edge.yaml b/demo/chart/templates/daemonset-edge.yaml
index 6a28624..dd1d798 100644
--- a/demo/chart/templates/daemonset-edge.yaml
+++ b/demo/chart/templates/daemonset-edge.yaml
@@ -3,6 +3,7 @@ apiVersion: apps/v1
 kind: DaemonSet
 metadata:
   name: {{ include "starlight.fullname-edge" . }}
+  namespace: {{ .Values.namespace }}
   labels:
     {{- include "starlight.edgeLabels" . | nindent 4 }}
     kubernetes.io/cluster-service: "true"
@@ -19,7 +20,9 @@ spec:
       labels:
         {{- include "starlight.edgeSelectorLabels" . | nindent 8 }}
     spec:
+      {{- if .Values.serviceAccount.enabled}}
       serviceAccountName: {{ include "starlight.serviceAccountName" . }}
+      {{- end }}
       {{- with .Values.imagePullSecrets }}
       imagePullSecrets:
         {{- toYaml . | nindent 8 }}
diff --git a/demo/chart/templates/deployment-proxy.yaml b/demo/chart/templates/deployment-proxy.yaml
index 0779d3e..d2c5f20 100644
--- a/demo/chart/templates/deployment-proxy.yaml
+++ b/demo/chart/templates/deployment-proxy.yaml
@@ -2,6 +2,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: {{ include "starlight.fullname" . }}
+  namespace: {{ .Values.namespace }}
   labels:
     {{- include "starlight.proxyLabels" . | nindent 4 }}
     kubernetes.io/cluster-service: "true"
@@ -18,20 +19,26 @@ spec:
       labels:
         {{- include "starlight.proxySelectorLabels" . | nindent 8 }}
     spec:
+      {{- if .Values.serviceAccount.enabled}}
       serviceAccountName: {{ include "starlight.serviceAccountName" . }}
+      {{- end }}
       {{- with .Values.imagePullSecrets }}
       imagePullSecrets:
         {{- toYaml . | nindent 8 }}
       {{- end }}
       securityContext:
         {{- toYaml .Values.podSecurityContext | nindent 8 }} 
-      {{- if and (eq .Values.postgres.enabled true) (eq  .Values.postgres.persistence.enabled true) }}
       volumes:
+        {{- if and (eq .Values.postgres.enabled true) (eq  .Values.postgres.persistence.enabled true) }}
         - name: starlight-pv
           persistentVolumeClaim:
             {{- $newClaimName := include "starlight.fullname" .}}
             claimName: {{ .Values.postgres.persistence.existingClaim | default  $newClaimName }}
-      {{- end }}
+        {{- end }}
+        - name: dockerconfig
+          secret:
+            secretName: dockerconfig
+            optional: true
       containers:
       ##############################################################
       ##############################################################
@@ -69,6 +76,13 @@ spec:
             value: {{ .Values.starlightProxy.dbConnection | quote | default "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" }}
           - name: REGISTRY
             value: {{ .Values.starlightProxy.defaultRegistry | quote | default "http://container-registry.default.svc.cluster.local:5000" }}
+          - name: DOCKER_CONFIG
+            value: /opt/.docker/config.json
+          volumeMounts:
+            - mountPath: /opt/.docker/config.json
+              name: dockerconfig
+              subPath: config.json
+
         {{- if .Values.postgres.enabled }}
         ############################################################
         - name: starlight-metadata
diff --git a/demo/chart/templates/deployment-registry.yaml b/demo/chart/templates/deployment-registry.yaml
index 9234ebb..1d1913b 100644
--- a/demo/chart/templates/deployment-registry.yaml
+++ b/demo/chart/templates/deployment-registry.yaml
@@ -3,6 +3,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: {{ include "starlight.fullname-registry" . }}
+  namespace: {{ .Values.namespace }}
   labels:
     {{- include "starlight.registryLabels" . | nindent 4 }}
     kubernetes.io/cluster-service: "true"
@@ -19,7 +20,9 @@ spec:
       labels:
         {{- include "starlight.registrySelectorLabels" . | nindent 8 }}
     spec:
+      {{- if .Values.serviceAccount.enabled}}
       serviceAccountName: {{ include "starlight.serviceAccountName" . }}
+      {{- end }}
       {{- with .Values.imagePullSecrets }}
       imagePullSecrets:
         {{- toYaml . | nindent 8 }}
diff --git a/demo/chart/templates/ingress.yaml b/demo/chart/templates/ingress.yaml
index e462448..86648cc 100644
--- a/demo/chart/templates/ingress.yaml
+++ b/demo/chart/templates/ingress.yaml
@@ -16,6 +16,7 @@ apiVersion: extensions/v1beta1
 kind: Ingress
 metadata:
   name: {{ $fullName }}
+  namespace: {{ .Values.namespace }}
   labels:
     {{- include "starlight.proxyLabels" . | nindent 4 }}
   annotations:
diff --git a/demo/chart/templates/service-proxy.yaml b/demo/chart/templates/service-proxy.yaml
index 9b71c12..fcf0c9a 100644
--- a/demo/chart/templates/service-proxy.yaml
+++ b/demo/chart/templates/service-proxy.yaml
@@ -2,6 +2,7 @@ apiVersion: v1
 kind: Service
 metadata:
   name: {{ include "starlight.fullname" . }}
+  namespace: {{ .Values.namespace }}
   labels:
     {{- include "starlight.proxyLabels" . | nindent 4 }}
 spec:
diff --git a/demo/chart/templates/service-registry.yaml b/demo/chart/templates/service-registry.yaml
index b24f67e..692aef2 100644
--- a/demo/chart/templates/service-registry.yaml
+++ b/demo/chart/templates/service-registry.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: Service
 metadata:
   name: {{ include "starlight.fullname-registry" . }}
+  namespace: {{ .Values.namespace }}
   labels:
     {{- include "starlight.registryLabels" . | nindent 4 }}
 spec:
diff --git a/demo/chart/templates/serviceaccount.yaml b/demo/chart/templates/serviceaccount.yaml
new file mode 100644
index 0000000..45ca5a9
--- /dev/null
+++ b/demo/chart/templates/serviceaccount.yaml
@@ -0,0 +1,15 @@
+{{- if .Values.serviceAccount.enabled}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ .Values.serviceAccount.name }}
+  namespace: {{ .Values.namespace }}
+  {{- with .Values.serviceAccount.labels }}
+  labels:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end}}
\ No newline at end of file
diff --git a/demo/chart/values.yaml b/demo/chart/values.yaml
index 75bdf09..5265ef5 100644
--- a/demo/chart/values.yaml
+++ b/demo/chart/values.yaml
@@ -1,6 +1,7 @@
 # Starlight Proxy + Registry
 nameOverride: ""
 fullnameOverride: ""
+namespace: "default"
 
 ########################################################################
 # CLOUD
@@ -20,6 +21,10 @@ starlightProxy:
   defaultRegistry: "http://container-registry.default.svc.cluster.local:5000"
   resources: {}
 
+  # If you are using a private registry, you need to specify the secret name here
+  dockerConfigSecret: ""
+  
+
 # ---------------------------------------------------------------------
 # Metadata database - PostgreSQL
 # ---------------------------------------------------------------------
@@ -88,8 +93,13 @@ ingress:
     # kubernetes.io/tls-acme: "true"
     # set to the largest layer size for uploading container image
     nginx.ingress.kubernetes.io/proxy-body-size: 512m
-  # Set it to your domain name(s)
-  hosts: [ starlight.lan ]
+    # if you want to protect the starlight proxy with basic auth, please uncomment the following lines
+    # and create a secret named `registry-auth`:
+    # nginx.ingress.kubernetes.io/auth-type: basic
+    # nginx.ingress.kubernetes.io/auth-secret: registry-auth
+
+  hosts: 
+  - starlight.lan
   tls: []
 
 # select nodes in the cloud
@@ -128,6 +138,9 @@ edgeTolerations: []
 
 edgeNodeSelector: 
   kubernetes.io/os: linux 
+  # install starlight daemon on the edge node with label `starlight: true`
+  # set the label with `kubectl label node YOUREDGENODE node-role.kubernetes.io/starlight=ture`
+  node-role.kubernetes.io/starlight: "ture"
   # kubernetes.io/arch: arm64
   # kubernetes.io/hostname: edge
 
@@ -138,12 +151,14 @@ edgeAffinity: {}
 ########################################################################
 serviceAccount:
   # Specifies whether a service account should be created
-  create: true
+  enabled: true
   # Annotations to add to the service account
   annotations: {}
+  # Labels to add to the service account
+  labels: {}
   # The name of the service account to use.
   # If not set and create is true, a name is generated using the fullname template
-  name: ""
+  name: "starlight"
 
 podAnnotations: {}