diff --git a/cmd/deploy.go b/cmd/deploy.go
new file mode 100644
index 0000000000000000000000000000000000000000..8126ca4e4bdc32abaec084a2d5d3c095ba75d9bc
--- /dev/null
+++ b/cmd/deploy.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+	"errors"
+
+	dep "github.com/deis/helm-dm/deploy"
+	"github.com/deis/helm-dm/format"
+)
+
+func deploy(cfg *dep.Deployment, dry bool) error {
+	if dry {
+		format.Error("Not implemented: --dry-run")
+	}
+	if cfg.Filename == "" {
+		return errors.New("A filename must be specified. For a tar archive, this is the name of the root template in the archive.")
+	}
+
+	if err := cfg.Commit(); err != nil {
+		format.Error("Failed to commit deployment: %s", err)
+		return err
+	}
+
+	return nil
+}
diff --git a/cmd/helm.go b/cmd/helm.go
index d69ebd667af871590d55536a9e8bf0128a3689ea..d0edde4bbd959166456ba90e7b9d3a8abc472457 100644
--- a/cmd/helm.go
+++ b/cmd/helm.go
@@ -4,6 +4,7 @@ import (
 	"os"
 
 	"github.com/codegangsta/cli"
+	dep "github.com/deis/helm-dm/deploy"
 	"github.com/deis/helm-dm/format"
 )
 
@@ -22,7 +23,7 @@ func main() {
 func commands() []cli.Command {
 	return []cli.Command{
 		{
-			Name:        "install",
+			Name:        "init",
 			Usage:       "Initialize the client and install DM on Kubernetes.",
 			Description: ``,
 			Flags: []cli.Flag{
@@ -33,7 +34,7 @@ func commands() []cli.Command {
 			},
 			Action: func(c *cli.Context) {
 				if err := install(c.Bool("dry-run")); err != nil {
-					format.Error(err.Error())
+					format.Error("%s (Run 'helm doctor' for more information)", err)
 					os.Exit(1)
 				}
 			},
@@ -44,7 +45,7 @@ func commands() []cli.Command {
 			ArgsUsage: "",
 			Action: func(c *cli.Context) {
 				if err := target(c.Bool("dry-run")); err != nil {
-					format.Error(err.Error())
+					format.Error("%s (Is the cluster running?)", err)
 					os.Exit(1)
 				}
 			},
@@ -59,7 +60,66 @@ func commands() []cli.Command {
 			Name: "doctor",
 		},
 		{
-			Name: "deploy",
+			Name:    "deploy",
+			Aliases: []string{"install"},
+			Usage:   "Deploy a chart into the cluster.",
+			Action: func(c *cli.Context) {
+
+				args := c.Args()
+				if len(args) < 1 {
+					format.Error("First argument, filename, is required. Try 'helm deploy --help'")
+					os.Exit(1)
+				}
+
+				props, err := parseProperties(c.String("properties"))
+				if err != nil {
+					format.Error("Failed to parse properties: %s", err)
+					os.Exit(1)
+				}
+
+				d := &dep.Deployment{
+					Name:       c.String("Name"),
+					Properties: props,
+					Filename:   args[0],
+					Imports:    args[1:],
+					Repository: c.String("repository"),
+				}
+
+				if c.Bool("stdin") {
+					d.Input = os.Stdin
+				}
+
+				if err := deploy(d, c.Bool("dry-run")); err != nil {
+					format.Error("%s (Try running 'helm doctor')", err)
+					os.Exit(1)
+				}
+			},
+			Flags: []cli.Flag{
+				cli.BoolFlag{
+					Name:  "dry-run",
+					Usage: "Only display the underlying kubectl commands.",
+				},
+				cli.BoolFlag{
+					Name:  "stdin,i",
+					Usage: "Read a configuration from STDIN.",
+				},
+				cli.StringFlag{
+					Name:  "name",
+					Usage: "Name of deployment, used for deploy and update commands (defaults to template name)",
+				},
+				// TODO: I think there is a Generic flag type that we can implement parsing with.
+				cli.StringFlag{
+					Name:  "properties,p",
+					Usage: "A comma-separated list of key=value pairs: 'foo=bar,foo2=baz'.",
+				},
+				cli.StringFlag{
+					// FIXME: This is not right. It's sort of a half-baked forward
+					// port of dm.go.
+					Name:  "repository",
+					Usage: "The default repository",
+					Value: "kubernetes/application-dm-templates",
+				},
+			},
 		},
 		{
 			Name: "search",
diff --git a/cmd/properties.go b/cmd/properties.go
new file mode 100644
index 0000000000000000000000000000000000000000..9632c95f6dfda11f3958e329ccc24202a27037d2
--- /dev/null
+++ b/cmd/properties.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+	"errors"
+	"strconv"
+	"strings"
+)
+
+// TODO: The concept of property here is really simple. We could definitely get
+// better about the values we allow. Also, we need some validation on the names.
+
+var errInvalidProperty = errors.New("property is not in name=value format")
+
+// parseProperties is a utility for parsing a comma-separated key=value string.
+func parseProperties(kvstr string) (map[string]interface{}, error) {
+	properties := map[string]interface{}{}
+
+	if len(kvstr) == 0 {
+		return properties, nil
+	}
+
+	pairs := strings.Split(kvstr, ",")
+	for _, p := range pairs {
+		// Allow for "k=v, k=v"
+		p = strings.TrimSpace(p)
+		pair := strings.Split(p, "=")
+		if len(pair) == 1 {
+			return properties, errInvalidProperty
+		}
+
+		// If the value looks int-like, convert it.
+		if i, err := strconv.Atoi(pair[1]); err == nil {
+			properties[pair[0]] = pair[1]
+		} else {
+			properties[pair[0]] = i
+		}
+	}
+
+	return properties, nil
+}
diff --git a/deploy/deploy.go b/deploy/deploy.go
new file mode 100644
index 0000000000000000000000000000000000000000..038c7eafa55d7f19198b01f19979eff4fa43ca9c
--- /dev/null
+++ b/deploy/deploy.go
@@ -0,0 +1,148 @@
+package deploy
+
+import (
+	"archive/tar"
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/ghodss/yaml"
+	"github.com/kubernetes/deployment-manager/common"
+	"github.com/kubernetes/deployment-manager/expandybird/expander"
+	"github.com/kubernetes/deployment-manager/registry"
+)
+
+type Deployer interface {
+	Commit() error
+}
+
+// Deployment describes a deployment of a package.
+type Deployment struct {
+	// Name is the Deployment name. Autogenerated if empty.
+	Name string
+	// Filename is the filename for the base deployment.
+	Filename string
+	// Imports is a list of imported files.
+	Imports []string
+	// Properties to pass into the template.
+	Properties map[string]interface{}
+	// Input is a file containing templates. It may be os.Stdin.
+	Input *os.File
+	// Repository is the location of the templates.
+	Repository string
+}
+
+// Commit prepares the Deployment and then commits it to the remote processor.
+func (d *Deployment) Commit() error {
+	tpl, err := d.resolveTemplate()
+	if err != nil {
+		return err
+	}
+
+	// If a deployment Name is specified, set that explicitly.
+	if d.Name != "" {
+		tpl.Name = d.Name
+	}
+
+	return nil
+}
+
+// resolveTemplate resolves what kind of template is being loaded, and then returns the template.
+func (d *Deployment) resolveTemplate() (*common.Template, error) {
+	// If some input has been specified, read it.
+	if d.Input != nil {
+		// Assume this is a tar archive.
+		tpl, err := expander.NewTemplateFromArchive(d.Filename, d.Input, d.Imports)
+		if err == nil {
+			return tpl, err
+		} else if err != tar.ErrHeader {
+			return nil, err
+		}
+
+		// If we get here, the file is not a tar archive.
+		if _, err := os.Stdin.Seek(0, 0); err != nil {
+			return nil, err
+		}
+		return expander.NewTemplateFromReader(d.Filename, d.Input, d.Imports)
+	}
+
+	// Non-Stdin case
+	if len(d.Imports) > 0 {
+		if t, err := registryType(d.Filename); err != nil {
+			return expander.NewTemplateFromRootTemplate(d.Filename)
+		} else {
+			return buildTemplateFromType(t, d.Repository, d.Properties)
+		}
+	}
+	return expander.NewTemplateFromFileNames(d.Filename, d.Imports)
+}
+
+// registryType is a placeholder until registry.ParseType() is merged.
+func registryType(name string) (*registry.Type, error) {
+	tList := strings.Split(name, ":")
+	if len(tList) != 2 {
+		return nil, errors.New("No version")
+	}
+
+	tt := registry.Type{Version: tList[1]}
+
+	cList := strings.Split(tList[0], "/")
+
+	if len(cList) == 1 {
+		tt.Name = tList[0]
+	} else {
+		tt.Collection = cList[0]
+		tt.Name = cList[1]
+	}
+	return &tt, nil
+}
+
+// buildTemplateFromType is a straight lift-n-shift from dm.go.
+func buildTemplateFromType(t *registry.Type, reg string, props map[string]interface{}) (*common.Template, error) {
+	// Name the deployment after the type name.
+	name := fmt.Sprintf("%s:%s", t.Name, t.Version)
+	git, err := getGitRegistry(reg)
+	if err != nil {
+		return nil, err
+	}
+	gurls, err := git.GetURLs(*t)
+	if err != nil {
+		return nil, err
+	}
+
+	config := common.Configuration{Resources: []*common.Resource{&common.Resource{
+		Name:       name,
+		Type:       gurls[0],
+		Properties: props,
+	}}}
+
+	y, err := yaml.Marshal(config)
+	if err != nil {
+		return nil, fmt.Errorf("error: %s\ncannot create configuration for deployment: %v\n", err, config)
+	}
+
+	return &common.Template{
+		Name:    name,
+		Content: string(y),
+		// No imports, as this is a single type from repository.
+	}, nil
+}
+
+func getGitRegistry(reg string) (registry.Registry, error) {
+	s := strings.SplitN(reg, "/", 3)
+	if len(s) < 2 {
+		return nil, fmt.Errorf("invalid template registry: %s", reg)
+	}
+
+	path := ""
+	if len(s) > 2 {
+		path = s[3]
+	}
+
+	if s[0] == "helm" {
+		return registry.NewGithubPackageRegistry(s[0], s[1]), nil
+	} else {
+		return registry.NewGithubRegistry(s[0], s[1], path), nil
+	}
+}