diff --git a/metadata/app/schema.go b/metadata/app/schema.go
index c3c720e9b6584631c0cf199567133461a96e5851..1db429f89aa41fc462d73ef3238b662c75fcf830 100644
--- a/metadata/app/schema.go
+++ b/metadata/app/schema.go
@@ -29,8 +29,14 @@ const (
 	DefaultVersion    = "0.0.1"
 )
 
-var ErrRegistryNameInvalid = fmt.Errorf("Registry name is invalid")
-var ErrRegistryExists = fmt.Errorf("Registry with name already exists")
+var (
+	ErrRegistryNameInvalid = fmt.Errorf("Registry name is invalid")
+	ErrRegistryExists      = fmt.Errorf("Registry with name already exists")
+	// ErrEnvironmentNameInvalid is the error where an environment name is invalid.
+	ErrEnvironmentNameInvalid = fmt.Errorf("Environment name is invalid")
+	// ErrEnvironmentExists is the error when trying to create an environment that already exists.
+	ErrEnvironmentExists = fmt.Errorf("Environment with name already exists")
+)
 
 type Spec struct {
 	APIVersion   string           `json:"apiVersion,omitempty"`
@@ -44,10 +50,76 @@ type Spec struct {
 	Bugs         string           `json:"bugs,omitempty"`
 	Keywords     []string         `json:"keywords,omitempty"`
 	Registries   RegistryRefSpecs `json:"registries,omitempty"`
+	Environments EnvironmentSpecs `json:"environments,omitempty"`
 	Libraries    LibraryRefSpecs  `json:"libraries,omitempty"`
 	License      string           `json:"license,omitempty"`
 }
 
+type RepositorySpec struct {
+	Type string `json:"type"`
+	URI  string `json:"uri"`
+}
+
+type RegistryRefSpec struct {
+	Name       string          `json:"-"`
+	Protocol   string          `json:"protocol"`
+	URI        string          `json:"uri"`
+	GitVersion *GitVersionSpec `json:"gitVersion"`
+}
+
+type RegistryRefSpecs map[string]*RegistryRefSpec
+
+// EnvironmentSpecs contains one or more EnvironmentSpec.
+type EnvironmentSpecs map[string]*EnvironmentSpec
+
+// EnvironmentSpec contains the specification for ksonnet environments.
+//
+// KubernetesVersion: The Kubernetes version the target cluster is running on.
+// Destinations:      One or more cluster addresses that this environment
+//                    points to.
+// Targets:           The relative component paths that this environment wishes
+//                    to deploy onto it's destinations.
+type EnvironmentSpec struct {
+	Name              string                      `json:"-"`
+	KubernetesVersion string                      `json:"k8sVersion"`
+	Destinations      EnvironmentDestinationSpecs `json:"destinations"`
+	Targets           []string                    `json:"targets"`
+}
+
+// EnvironmentDestinationSpecs contains one or more EnvironmentDestinationSpec.
+type EnvironmentDestinationSpecs []*EnvironmentDestinationSpec
+
+// EnvironmentDestinationSpec contains the specification for the cluster
+// addresses that the environment points to.
+//
+// Server:    The Kubernetes server that the cluster is running on.
+// Namespace: The namespace of the Kubernetes server that targets should
+//            be deployed to. This is "default", by default.
+type EnvironmentDestinationSpec struct {
+	Server    string `json:"server"`
+	Namespace string `json:"namespace"`
+}
+
+type LibraryRefSpec struct {
+	Name       string          `json:"name"`
+	Registry   string          `json:"registry"`
+	GitVersion *GitVersionSpec `json:"gitVersion"`
+}
+
+type GitVersionSpec struct {
+	RefSpec   string `json:"refSpec"`
+	CommitSHA string `json:"commitSha"`
+}
+
+type LibraryRefSpecs map[string]*LibraryRefSpec
+
+type ContributorSpec struct {
+	Name  string `json:"name"`
+	Email string `json:"email"`
+}
+
+type ContributorSpecs []*ContributorSpec
+
 func Unmarshal(bytes []byte) (*Spec, error) {
 	schema := Spec{}
 	err := yaml.Unmarshal(bytes, &schema)
@@ -105,36 +177,33 @@ func (s *Spec) validate() error {
 	return nil
 }
 
-type RepositorySpec struct {
-	Type string `json:"type"`
-	URI  string `json:"uri"`
-}
-
-type RegistryRefSpec struct {
-	Name       string          `json:"-"`
-	Protocol   string          `json:"protocol"`
-	URI        string          `json:"uri"`
-	GitVersion *GitVersionSpec `json:"gitVersion"`
+// GetEnvironmentSpec returns the environment specification for the environment.
+func (s *Spec) GetEnvironmentSpec(name string) (*EnvironmentSpec, bool) {
+	environmentSpec, ok := s.Environments[name]
+	if ok {
+		environmentSpec.Name = name
+	}
+	return environmentSpec, ok
 }
 
-type RegistryRefSpecs map[string]*RegistryRefSpec
+// AddEnvironmentSpec adds an EnvironmentSpec to the list of EnvironmentSpecs.
+// This is equivalent to registering the environment for a ksonnet app.
+func (s *Spec) AddEnvironmentSpec(spec *EnvironmentSpec) error {
+	if spec.Name == "" {
+		return ErrEnvironmentNameInvalid
+	}
 
-type LibraryRefSpec struct {
-	Name       string          `json:"name"`
-	Registry   string          `json:"registry"`
-	GitVersion *GitVersionSpec `json:"gitVersion"`
-}
+	_, environmentSpecExists := s.Environments[spec.Name]
+	if environmentSpecExists {
+		return ErrEnvironmentExists
+	}
 
-type GitVersionSpec struct {
-	RefSpec   string `json:"refSpec"`
-	CommitSHA string `json:"commitSha"`
+	s.Environments[spec.Name] = spec
+	return nil
 }
 
-type LibraryRefSpecs map[string]*LibraryRefSpec
-
-type ContributorSpec struct {
-	Name  string `json:"name"`
-	Email string `json:"email"`
+// DeleteEnvironmentSpec removes the environment specification from the app spec.
+func (s *Spec) DeleteEnvironmentSpec(name string) error {
+	delete(s.Environments, name)
+	return nil
 }
-
-type ContributorSpecs []*ContributorSpec
diff --git a/metadata/app/schema_test.go b/metadata/app/schema_test.go
index c56112d48565e3d488fd0860ae8fe9eeeafbc6b1..b0036777a1a6d6bdd506bd96c8e6ff4ce5341a14 100644
--- a/metadata/app/schema_test.go
+++ b/metadata/app/schema_test.go
@@ -16,7 +16,6 @@
 package app
 
 import (
-	"fmt"
 	"testing"
 
 	"github.com/blang/semver"
@@ -34,6 +33,19 @@ func makeSimpleRefSpec(name, protocol, uri, version string) *RegistryRefSpec {
 	}
 }
 
+func makeSimpleEnvironmentSpec(name, namespace, server, k8sVersion string) *EnvironmentSpec {
+	return &EnvironmentSpec{
+		Name: name,
+		Destinations: EnvironmentDestinationSpecs{
+			&EnvironmentDestinationSpec{
+				Namespace: namespace,
+				Server:    server,
+			},
+		},
+		KubernetesVersion: k8sVersion,
+	}
+}
+
 func TestApiVersionValidate(t *testing.T) {
 	type spec struct {
 		spec string
@@ -87,7 +99,6 @@ func TestGetRegistryRefSuccess(t *testing.T) {
 	}
 
 	r1, ok := example1.GetRegistryRef("simple1")
-	fmt.Println(r1)
 	if r1 == nil || !ok {
 		t.Error("Expected registry to contain 'simple1'")
 	}
@@ -159,3 +170,140 @@ func TestAddRegistryRefFailure(t *testing.T) {
 		t.Error("Expected registry to fail to add registry with duplicate name and different uri")
 	}
 }
+
+func TestGetEnvironmentSpecSuccess(t *testing.T) {
+	const (
+		env        = "dev"
+		namespace  = "default"
+		server     = "http://example.com"
+		k8sVersion = "1.8.0"
+	)
+
+	example1 := Spec{
+		Environments: EnvironmentSpecs{
+			env: &EnvironmentSpec{
+				Destinations: EnvironmentDestinationSpecs{
+					&EnvironmentDestinationSpec{
+						Namespace: namespace,
+						Server:    server,
+					},
+				},
+				KubernetesVersion: k8sVersion,
+			},
+		},
+	}
+
+	r1, ok := example1.GetEnvironmentSpec(env)
+	if r1 == nil || !ok {
+		t.Errorf("Expected environments to contain '%s'", env)
+	}
+
+	if len(r1.Destinations) != 1 || r1.Destinations[0].Namespace != namespace ||
+		r1.Destinations[0].Server != server || r1.KubernetesVersion != k8sVersion {
+		t.Errorf("Environment did not add correct values:\n%s", r1)
+	}
+}
+
+func TestGetEnvironmentSpecFailure(t *testing.T) {
+	example1 := Spec{
+		Environments: EnvironmentSpecs{
+			"dev": &EnvironmentSpec{
+				Destinations: EnvironmentDestinationSpecs{
+					&EnvironmentDestinationSpec{
+						Namespace: "default",
+						Server:    "http://example.com",
+					},
+				},
+				KubernetesVersion: "1.8.0",
+			},
+		},
+	}
+
+	r1, ok := example1.GetEnvironmentSpec("prod")
+	if r1 != nil || ok {
+		t.Error("Expected environemnts to not contain 'prod'")
+	}
+}
+
+func TestAddEnvironmentSpecSuccess(t *testing.T) {
+	const (
+		env        = "dev"
+		namespace  = "default"
+		server     = "http://example.com"
+		k8sVersion = "1.8.0"
+	)
+
+	example1 := Spec{
+		Environments: EnvironmentSpecs{},
+	}
+
+	err := example1.AddEnvironmentSpec(makeSimpleEnvironmentSpec(env, namespace, server, k8sVersion))
+	if err != nil {
+		t.Errorf("Expected environment add to succeed:\n%s", err)
+	}
+
+	r1, ok1 := example1.GetEnvironmentSpec(env)
+	if !ok1 || len(r1.Destinations) != 1 || r1.Destinations[0].Namespace != namespace ||
+		r1.Destinations[0].Server != server || r1.KubernetesVersion != k8sVersion {
+		t.Errorf("Environment did not add correct values:\n%s", r1)
+	}
+}
+
+func TestAddEnvironmentSpecFailure(t *testing.T) {
+	const (
+		envName1   = "dev"
+		envName2   = ""
+		namespace  = "default"
+		server     = "http://example.com"
+		k8sVersion = "1.8.0"
+	)
+
+	example1 := Spec{
+		Environments: EnvironmentSpecs{
+			envName1: &EnvironmentSpec{
+				Destinations: EnvironmentDestinationSpecs{
+					&EnvironmentDestinationSpec{
+						Namespace: namespace,
+						Server:    server,
+					},
+				},
+				KubernetesVersion: k8sVersion,
+			},
+		},
+	}
+
+	err := example1.AddEnvironmentSpec(makeSimpleEnvironmentSpec(envName2, namespace, server, k8sVersion))
+	if err != ErrEnvironmentNameInvalid {
+		t.Error("Expected failure while adding environment with an invalid name")
+	}
+
+	err = example1.AddEnvironmentSpec(makeSimpleEnvironmentSpec(envName1, namespace, server, k8sVersion))
+	if err != ErrEnvironmentExists {
+		t.Error("Expected failure while adding environment with duplicate name")
+	}
+}
+
+func TestDeleteEnvironmentSpec(t *testing.T) {
+	example1 := Spec{
+		Environments: EnvironmentSpecs{
+			"dev": &EnvironmentSpec{
+				Destinations: EnvironmentDestinationSpecs{
+					&EnvironmentDestinationSpec{
+						Namespace: "default",
+						Server:    "http://example.com",
+					},
+				},
+				KubernetesVersion: "1.8.0",
+			},
+		},
+	}
+
+	err := example1.DeleteEnvironmentSpec("dev")
+	if err != nil {
+		t.Error("Expected to successfully delete an environment in spec")
+	}
+
+	if _, ok := example1.GetEnvironmentSpec("dev"); ok {
+		t.Error("Expected environment 'dev' to be deleted from spec, but still exists")
+	}
+}
diff --git a/metadata/component.go b/metadata/component.go
index 7cbe06ebf768608ca6afe3775195770b9c47b94a..709ee716b5d3331a0db025594a5e34a5e61b0644 100644
--- a/metadata/component.go
+++ b/metadata/component.go
@@ -171,6 +171,38 @@ func (m *manager) DeleteComponent(name string) error {
 	return nil
 }
 
+func (m *manager) GetComponentParams(component string) (param.Params, error) {
+	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
+	if err != nil {
+		return nil, err
+	}
+
+	return param.GetComponentParams(component, string(text))
+}
+
+func (m *manager) GetAllComponentParams() (map[string]param.Params, error) {
+	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
+	if err != nil {
+		return nil, err
+	}
+
+	return param.GetAllComponentParams(string(text))
+}
+
+func (m *manager) SetComponentParams(component string, params param.Params) error {
+	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
+	if err != nil {
+		return err
+	}
+
+	jsonnet, err := param.SetComponentParams(component, string(text), params)
+	if err != nil {
+		return err
+	}
+
+	return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(jsonnet), defaultFilePermissions)
+}
+
 func (m *manager) findComponentPath(name string) (string, error) {
 	componentPaths, err := m.ComponentPaths()
 	if err != nil {
@@ -198,3 +230,31 @@ func (m *manager) findComponentPath(name string) (string, error) {
 
 	return componentPath, nil
 }
+
+func (m *manager) writeComponentParams(componentName string, params param.Params) error {
+	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
+	if err != nil {
+		return err
+	}
+
+	appended, err := param.AppendComponent(componentName, string(text), params)
+	if err != nil {
+		return err
+	}
+
+	return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(appended), defaultFilePermissions)
+}
+
+func genComponentParamsContent() []byte {
+	return []byte(`{
+  global: {
+    // User-defined global parameters; accessible to all component and environments, Ex:
+    // replicas: 4,
+  },
+  components: {
+    // Component-level parameters, defined initially from 'ks prototype use ...'
+    // Each object below should correspond to a component in the components/ directory
+  },
+}
+`)
+}
diff --git a/metadata/component_test.go b/metadata/component_test.go
index 00a5ddb299fdaa46b4f68f31111714216f3e8dd7..d5021bcd819ca377f810fcdab8d4df6396ca05ca 100644
--- a/metadata/component_test.go
+++ b/metadata/component_test.go
@@ -138,3 +138,21 @@ func TestFindComponentPath(t *testing.T) {
 		t.Fatalf("m.findComponentPath failed; expected '%s', got '%s'", expected, path)
 	}
 }
+
+func TestGenComponentParamsContent(t *testing.T) {
+	expected := `{
+  global: {
+    // User-defined global parameters; accessible to all component and environments, Ex:
+    // replicas: 4,
+  },
+  components: {
+    // Component-level parameters, defined initially from 'ks prototype use ...'
+    // Each object below should correspond to a component in the components/ directory
+  },
+}
+`
+	content := string(genComponentParamsContent())
+	if content != expected {
+		t.Fatalf("Expected to generate:\n%s\n, got:\n%s", expected, content)
+	}
+}
diff --git a/metadata/manager.go b/metadata/manager.go
index 19128b3cadfbbeba9427045aa4967865dc315fb0..8423f16a4254a8e2f1c4cf3353a28ecdeb9bcc65 100644
--- a/metadata/manager.go
+++ b/metadata/manager.go
@@ -23,7 +23,6 @@ import (
 
 	"github.com/ksonnet/ksonnet/generator"
 	"github.com/ksonnet/ksonnet/metadata/app"
-	param "github.com/ksonnet/ksonnet/metadata/params"
 	"github.com/ksonnet/ksonnet/metadata/registry"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
@@ -223,38 +222,6 @@ func (m *manager) EnvPaths(env string) (metadataPath, mainPath, paramsPath, spec
 	return
 }
 
-func (m *manager) GetComponentParams(component string) (param.Params, error) {
-	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
-	if err != nil {
-		return nil, err
-	}
-
-	return param.GetComponentParams(component, string(text))
-}
-
-func (m *manager) GetAllComponentParams() (map[string]param.Params, error) {
-	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
-	if err != nil {
-		return nil, err
-	}
-
-	return param.GetAllComponentParams(string(text))
-}
-
-func (m *manager) SetComponentParams(component string, params param.Params) error {
-	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
-	if err != nil {
-		return err
-	}
-
-	jsonnet, err := param.SetComponentParams(component, string(text), params)
-	if err != nil {
-		return err
-	}
-
-	return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(jsonnet), defaultFilePermissions)
-}
-
 // AppSpec will return the specification for a ksonnet application
 // (typically stored in `app.yaml`)
 func (m *manager) AppSpec() (*app.Spec, error) {
@@ -356,34 +323,6 @@ func (m *manager) createAppDirTree(name string, appYAMLData, baseLibData []byte,
 	return nil
 }
 
-func (m *manager) writeComponentParams(componentName string, params param.Params) error {
-	text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
-	if err != nil {
-		return err
-	}
-
-	appended, err := param.AppendComponent(componentName, string(text), params)
-	if err != nil {
-		return err
-	}
-
-	return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(appended), defaultFilePermissions)
-}
-
-func genComponentParamsContent() []byte {
-	return []byte(`{
-  global: {
-    // User-defined global parameters; accessible to all component and environments, Ex:
-    // replicas: 4,
-  },
-  components: {
-    // Component-level parameters, defined initially from 'ks prototype use ...'
-    // Each object below should correspond to a component in the components/ directory
-  },
-}
-`)
-}
-
 func generateRegistryYAMLData(incubatorReg registry.Manager) ([]byte, error) {
 	regSpec, err := incubatorReg.FetchRegistrySpec()
 	if err != nil {
@@ -395,11 +334,12 @@ func generateRegistryYAMLData(incubatorReg registry.Manager) ([]byte, error) {
 
 func generateAppYAMLData(name string, refs ...*app.RegistryRefSpec) ([]byte, error) {
 	content := app.Spec{
-		APIVersion: app.DefaultAPIVersion,
-		Kind:       app.Kind,
-		Name:       name,
-		Version:    app.DefaultVersion,
-		Registries: app.RegistryRefSpecs{},
+		APIVersion:   app.DefaultAPIVersion,
+		Kind:         app.Kind,
+		Name:         name,
+		Version:      app.DefaultVersion,
+		Registries:   app.RegistryRefSpecs{},
+		Environments: app.EnvironmentSpecs{},
 	}
 
 	for _, ref := range refs {
diff --git a/metadata/manager_test.go b/metadata/manager_test.go
index 9c4cf463005d0c697b6c00f40be286ebd0b21fef..88175f9556b02b9af3328f66db88e92d5bb5f86a 100644
--- a/metadata/manager_test.go
+++ b/metadata/manager_test.go
@@ -277,21 +277,3 @@ func TestDoubleNewFailure(t *testing.T) {
 		t.Fatalf("Expected to fail to create app with message '%s', got '%s'", targetErr, err.Error())
 	}
 }
-
-func TestGenComponentParamsContent(t *testing.T) {
-	expected := `{
-  global: {
-    // User-defined global parameters; accessible to all component and environments, Ex:
-    // replicas: 4,
-  },
-  components: {
-    // Component-level parameters, defined initially from 'ks prototype use ...'
-    // Each object below should correspond to a component in the components/ directory
-  },
-}
-`
-	content := string(genComponentParamsContent())
-	if content != expected {
-		t.Fatalf("Expected to generate:\n%s\n, got:\n%s", expected, content)
-	}
-}