diff --git a/.gitignore b/.gitignore
index 700353b37dd8e751d23fb3aaf994d5db4f2ec619..dd0a0fbe4a5b88cd3a78b553b5b2fdc6dd3804de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .go-project.mk
 devtool
 .devtool.config
+.vscode
diff --git a/cmd/build.go b/cmd/build.go
index d8dd4acbb843ce3976340c795e55c747e58210fc..073812c2747fdc9b7faaadc4e84280c745539d30 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -33,7 +33,11 @@ var buildCmd = &cobra.Command{
 	Use:   "build",
 	Short: "build the application",
 	Run: func(cmd *cobra.Command, args []string) {
-		checkLocalSetup()
+		prereqsInstalled := checkLocalSetup()
+		if !prereqsInstalled {
+			color.Red("Please install the required applications and try again.\n")
+			return
+		}
 
 		err := environment.Run(false, "minikube", "status")
 		if err != nil {
@@ -108,6 +112,13 @@ func init() {
 		panic("invalid arg")
 	}
 
+	buildCmd.Flags().Bool("dry-run", false, "Use the helm template command to output k8s objects")
+	viper.SetDefault("build.helm.dry.run", false)
+	err = viper.BindPFlag("build.helm.dry.run", buildCmd.Flags().Lookup("dry-run"))
+	if err != nil {
+		panic("invalid arg")
+	}
+
 }
 
 func build(config config.Config) error {
@@ -129,6 +140,8 @@ func build(config config.Config) error {
 		fallthrough
 	case "go":
 		environment.RunRequired(true, "make", "build")
+	case "angular":
+		environment.RunRequired(true, "yarn", "build:prod")
 	default:
 		color.New(color.FgRed).Printf("devtool doesn't support the '%s' language yet.\n", lang)
 		return errors.New("Language not supported")
@@ -155,32 +168,35 @@ func buildDockerAndDeploy(config config.Config) error {
 	color.Blue("Building Docker Containers")
 
 	for _, deployable := range config.Deployables {
-		dockerfile := deployable.Dockerfile
-		image := deployable.Name
-		tag, err := uuid.NewRandom()
-		if err != nil {
-			color.Red("Failed to generate UUID for docker tag")
-			return err
-		}
-
-		dockerTag := image + ":" + tag.String()
-
-		dockerRootDir := "."
-		if idx := strings.LastIndex(dockerfile, "/"); idx != -1 {
-			dockerRootDir = dockerfile[:idx]
-			fmt.Println(dockerRootDir)
-		}
-
-		color.New(color.FgGreen).Printf("Building: %s\n", dockerTag)
-		err = environment.Run(true, "docker", "build", "-t", dockerTag, "-f", dockerfile, dockerRootDir)
-		if err != nil {
-			return err
+		var image string
+		var tag string
+		var err error
+		if deployable.Dockerfile != "" {
+			image, tag, err = buildDockerImage(deployable)
+			if err != nil {
+				color.New(color.FgRed).Printf("Failed to build docker image: %s", err.Error())
+				return err
+			}
+		} else if deployable.Image != "" {
+			image = deployable.Image
+			tag = deployable.ImageTag
+			if tag == "" {
+				tag = "latest"
+			}
+		} else {
+			color.New(color.FgYellow).Printf("Deployable: %s does not have a valid Docker configuration", deployable.Name)
+			continue
 		}
 
 		color.Blue("Installing Chart")
 
 		releaseName := deployable.Name + "-local"
 		args := []string{"upgrade", releaseName, "--install", deployable.Chart, "--version", deployable.ChartVersion}
+
+		if viper.GetBool("build.helm.dry.run") {
+			args = append(args, "--dry-run", "--debug")
+		}
+
 		if len(config.LocalEnvVars) > 0 {
 			for _, key := range config.LocalEnvVars {
 				args = append(args, "--set", "environmentVariables."+key+"="+os.Getenv(key))
@@ -197,7 +213,7 @@ func buildDockerAndDeploy(config config.Config) error {
 		}
 
 		args = append(args, "--set", "image.repository="+image)
-		args = append(args, "--set", "image.tag="+tag.String())
+		args = append(args, "--set", "image.tag="+tag)
 		args = append(args, "-f", deployable.LocalConfig)
 
 		err = environment.Run(true, "helm", args...)
@@ -215,3 +231,28 @@ func buildDockerAndDeploy(config config.Config) error {
 	}
 	return nil
 }
+
+func buildDockerImage(deployable config.Deployable) (string, string, error) {
+	dockerfile := deployable.Dockerfile
+	image := deployable.Name
+	tag, err := uuid.NewRandom()
+	if err != nil {
+		color.Red("Failed to generate UUID for docker tag")
+		return "", "", err
+	}
+
+	dockerTag := image + ":" + tag.String()
+
+	dockerRootDir := "."
+	if idx := strings.LastIndex(dockerfile, "/"); idx != -1 {
+		dockerRootDir = dockerfile[:idx]
+		fmt.Println(dockerRootDir)
+	}
+
+	color.New(color.FgGreen).Printf("Building: %s\n", dockerTag)
+	err = environment.Run(true, "docker", "build", "-t", dockerTag, "-f", dockerfile, dockerRootDir)
+	if err != nil {
+		return "", "", err
+	}
+	return image, tag.String(), nil
+}
diff --git a/cmd/ci.go b/cmd/ci.go
index 4db1cd763cb9f02c749aff0c3a4eff2f6c2a4708..800ae41adb61588f0c974b26f5d71249aad35bef 100644
--- a/cmd/ci.go
+++ b/cmd/ci.go
@@ -18,6 +18,7 @@ import (
 	"os"
 
 	"git.psu.edu/k8s/devtool/config"
+	"git.psu.edu/k8s/devtool/environment"
 
 	"github.com/fatih/color"
 	"github.com/spf13/cobra"
@@ -30,9 +31,9 @@ var environmentSuffix string
 var ciCmd = &cobra.Command{
 	Use:   "ci",
 	Short: "Build tools for the CI system",
-	// Run: func(cmd *cobra.Command, args []string) {
-	// 	fmt.Println("config called")
-	// },
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.HelpFunc()(cmd, args)
+	},
 }
 
 func init() {
@@ -69,3 +70,8 @@ func getCiConfiguration() config.Config {
 	}
 	return conf
 }
+
+func helmUpdate() {
+	environment.RunRequired(false, "helm", "repo", "update")
+
+}
diff --git a/cmd/ci_build.go b/cmd/ci_build.go
index ccdf9c9d8f3ffa7d4eb832190e483618068770ab..420c49299d34afc314a0fdde9d878fc8018fda41 100644
--- a/cmd/ci_build.go
+++ b/cmd/ci_build.go
@@ -83,6 +83,11 @@ func buildCiDocker(config config.Config) error {
 		dockerfile := deployable.Dockerfile
 		image := deployable.Name
 
+		if dockerfile == "" {
+			color.New(color.FgYellow).Printf("Deployable %s has no Dockerfile, nothing to build.", deployable.Name)
+			continue
+		}
+
 		dockerImage := dockerRegistry + "/" + dockerRegistryNamespace + "/" + image
 		dockerTag := dockerImage + ":" + imageTag
 
diff --git a/cmd/ci_deploy.go b/cmd/ci_deploy.go
index 329ece014de402719c45792264e8e3206ce8d002..d59104759da682a2781923fad2e206311d329fed 100644
--- a/cmd/ci_deploy.go
+++ b/cmd/ci_deploy.go
@@ -59,6 +59,11 @@ func fluxDeployCi(config config.Config) error {
 	for _, deployable := range config.Deployables {
 		image := deployable.Name
 
+		if deployable.Dockerfile == "" {
+			color.New(color.FgYellow).Printf("Deployable %s has no Dockerfile, nothing to deploy.", deployable.Name)
+			continue
+		}
+
 		var releaseName string
 		if environmentSuffix == "" {
 			releaseName = deployable.Name
diff --git a/cmd/completion.go b/cmd/completion.go
new file mode 100644
index 0000000000000000000000000000000000000000..a3b4cbb7dde2a25407158d9f4017fce82c2251b4
--- /dev/null
+++ b/cmd/completion.go
@@ -0,0 +1,60 @@
+// Copyright © 2019 NAME HERE <EMAIL ADDRESS>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License 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 cmd
+
+import (
+	"os"
+
+	"github.com/fatih/color"
+	"github.com/spf13/cobra"
+)
+
+// completionCmd represents the completion command
+var completionCmd = &cobra.Command{
+	Use:   "completion",
+	Short: "Generates bash completion scripts",
+	Long: `To load completion run
+
+	source <(devtool completion)
+	
+	To configure your bash shell to load completions for each session add to your bashrc
+	
+	# ~/.bashrc or ~/.profile
+	source <(devtool completion)
+	`,
+	PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		// Do Nothing
+	},
+	Run: func(cmd *cobra.Command, args []string) {
+		err := rootCmd.GenBashCompletion(os.Stdout)
+		if err != nil {
+			color.Red("Failed to generate bash completion script")
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(completionCmd)
+
+	// Here you will define your flags and configuration settings.
+
+	// Cobra supports Persistent Flags which will work for this command
+	// and all subcommands, e.g.:
+	// completionCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+	// Cobra supports local flags which will only run when this command
+	// is called directly, e.g.:
+	// completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/cmd/config.go b/cmd/config.go
index 313b7daa2e2863e743441e078d3214f5df5b9451..dbf2ea5af37c27364bc39f50756a2bcfa8dad712 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -22,9 +22,9 @@ import (
 var configCmd = &cobra.Command{
 	Use:   "config",
 	Short: "Manage the projects build configuration",
-	// Run: func(cmd *cobra.Command, args []string) {
-	// 	fmt.Println("config called")
-	// },
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.HelpFunc()(cmd, args)
+	},
 }
 
 func init() {
diff --git a/cmd/config_add.go b/cmd/config_add.go
index 19e3a6ff9933cb657bf24f403898f9070c5716a3..75a65ad17b21cef7e12e8e887771d2af93bfd28c 100644
--- a/cmd/config_add.go
+++ b/cmd/config_add.go
@@ -15,7 +15,6 @@
 package cmd
 
 import (
-	"fmt"
 	"strings"
 
 	"git.psu.edu/k8s/devtool/config"
@@ -29,18 +28,19 @@ var addCmd = &cobra.Command{
 	Use:   "add",
 	Short: "adds a deployable unit to the project configuration",
 	Run: func(cmd *cobra.Command, args []string) {
-
 		conf, err := config.New(configFile)
 		if err != nil {
 			color.Red("Configuration doesn't exist, please run 'devtool config init' instead.")
 			return
 		}
 
+		helmUpdate()
+
 		deployables := conf.Deployables
 		dockerfiles := findDockerfiles()
-		for _, file := range dockerfiles {
-			fmt.Printf("Found Dockerfile: %s\n", file)
 
+		foundAny := false
+		for _, file := range dockerfiles {
 			found := false
 			for _, deployable := range deployables {
 				if deployable.Dockerfile == file {
@@ -52,6 +52,8 @@ var addCmd = &cobra.Command{
 			if !found {
 				color.New(color.FgYellow).Printf("Found Dockerfile: %s\n", file)
 
+				foundAny = true
+
 				prompt := promptui.Prompt{
 					Label:   "Include in config",
 					Default: "Yes",
@@ -85,6 +87,9 @@ var addCmd = &cobra.Command{
 				}
 			}
 		}
+		if !foundAny {
+			color.Red("Failed to find any new Dockerfiles")
+		}
 	},
 }
 
diff --git a/cmd/config_init.go b/cmd/config_init.go
index 79e140c4e7d20e03af2796e98537fb6cd6c0aa69..7f35edd9f183c7b6ae7a5c66851778762b01abce 100644
--- a/cmd/config_init.go
+++ b/cmd/config_init.go
@@ -15,7 +15,6 @@
 package cmd
 
 import (
-	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
@@ -52,15 +51,14 @@ var initCmd = &cobra.Command{
 		}
 		conf.Deployables = []config.Deployable{}
 
+		helmUpdate()
+
 		defaultName := conf.Name
 		if defaultName == "" {
 			wd, _ := os.Getwd()
-			fmt.Println(wd)
 			if idx := strings.LastIndex(wd, "/"); idx != -1 {
-				fmt.Println(idx)
 				idx++
 				defaultName = wd[idx:]
-				fmt.Println(defaultName)
 			}
 		}
 
diff --git a/cmd/root.go b/cmd/root.go
index 908c93650d55234736622a82f4cf70756df54c91..90b58d64ddf8ac8c72d01352b37ebeef0cb6de5f 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -38,7 +38,12 @@ var rootCmd = &cobra.Command{
 	Long:  `A tool used by software engineering to build applications.`,
 	// Uncomment the following line if your bare application
 	// has an action associated with it:
-	//	Run: func(cmd *cobra.Command, args []string) { },
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.HelpFunc()(cmd, args)
+	},
+	PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		PrintLogo()
+	},
 }
 
 // Execute adds all child commands to the root command and sets flags appropriately.
@@ -86,11 +91,11 @@ func initConfig() {
 	viper.SetEnvKeyReplacer(replacer)
 
 	// If a config file is found, read it in.
-	if err := viper.ReadInConfig(); err == nil {
-		fmt.Println("Using config file:", viper.ConfigFileUsed())
-	} else {
-		color.Red(err.Error())
-	}
+	// if err := viper.ReadInConfig(); err == nil {
+	// 	fmt.Println("Using config file:", viper.ConfigFileUsed())
+	// } else {
+	// 	color.Red(err.Error())
+	// }
 }
 
 func PrintLogo() {
diff --git a/cmd/run.go b/cmd/run.go
index b9f451945f66b5813930631af358a3a5621619f7..0dce6730fc6736f81a3909a4570fe320c17cb42f 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -26,6 +26,7 @@ import (
 )
 
 var command string
+var envFile string
 
 // runCmd represents the run command
 var runCmd = &cobra.Command{
@@ -39,13 +40,21 @@ var runCmd = &cobra.Command{
 			color.New(color.FgYellow).Printf("WARNING: no project configuration exists: %s\n", configFile)
 		}
 
+		checkEnvVars(conf)
+
 		localConfig := selectLocalConfig(conf.Deployables)
 
+		if _, err := os.Stat(localConfig); localConfig != "" && os.IsNotExist(err) {
+			color.New(color.FgYellow).Printf("WARN: Configuration file: '%s' doesn't exist\n", localConfig)
+			return
+		}
+
 		if localConfig != "" {
 			var helmValues config.HelmValues
 			helmValues, err = config.ReadHelmValues(localConfig)
 			if err != nil {
 				color.New(color.FgRed).Printf("Error reading local config file: %s\n", localConfig)
+				return
 			}
 			for k, v := range helmValues.EnvironmentVariables {
 				os.Setenv(k, v)
@@ -74,17 +83,25 @@ func init() {
 	if err != nil {
 		panic("invalid arg")
 	}
+
+	runCmd.Flags().StringVarP(&envFile, "env", "e", "", "Config YAML file with environmentVariables")
+
 }
 
 func selectLocalConfig(deployables []config.Deployable) string {
+	if envFile != "" {
+		return envFile
+	}
+
 	if len(deployables) == 0 {
+		color.Yellow("WARN: This project has no deployables, please specify one with the --env param.")
 		return ""
 	}
 
-	//if len(deployables) == 1 {
-	return deployables[0].LocalConfig
-	//}
-
-	//TODO add support for multiple deployables (prompt user and add arg)
+	if len(deployables) == 1 {
+		return deployables[0].LocalConfig
+	}
 
+	color.Yellow("WARN: This project has multiple deployables, please specify one with the --env param.")
+	return ""
 }
diff --git a/config/config.go b/config/config.go
index 9112c945184f5c4b53fd742e8adaa7253738f72f..255cf642121c60dba29cf524859c630e999d5eb6 100644
--- a/config/config.go
+++ b/config/config.go
@@ -18,6 +18,8 @@ type (
 	Deployable struct {
 		Name         string `json:"name"`
 		Dockerfile   string `json:"dockerfile"`
+		Image        string `json:"image"`
+		ImageTag     string `json:"imageTag"`
 		Chart        string `json:"chart"`
 		ChartVersion string `json:"chartVersion"`
 		LocalConfig  string `json:"localConfig"`
diff --git a/devtool.go b/devtool.go
index ba3588e5d4a821abfbe1dacf63e0e1a80eaf1625..9d25080a9694c07c880355586b34740128fcbc98 100644
--- a/devtool.go
+++ b/devtool.go
@@ -3,6 +3,5 @@ package main
 import "git.psu.edu/k8s/devtool/cmd"
 
 func main() {
-	cmd.PrintLogo()
 	cmd.Execute()
 }