diff --git a/actions/init.go b/actions/init.go
index fcb000ab37453853e2d95736018d4a1aa3eb7a2e..5ef2f716b810f76c8b5b8327568f3a27a50c31e5 100644
--- a/actions/init.go
+++ b/actions/init.go
@@ -37,8 +37,11 @@ func RunInit(m map[string]interface{}) error {
 	return i.Run()
 }
 
+type appLoadFn func(fs afero.Fs, root string, skipFindRoot bool) (app.App, error)
+
 type appInitFn func(fs afero.Fs, name, rootPath, k8sSpecFlag, serverURI, namespace string, registries []registry.Registry) error
-type initIncubatorFn func() (registry.Registry, error)
+
+type initIncubatorFn func(app.App) (registry.Registry, error)
 
 // Init creates a component namespace
 type Init struct {
@@ -51,6 +54,7 @@ type Init struct {
 	skipDefaultRegistries bool
 
 	appInitFn       appInitFn
+	appLoadFn       appLoadFn
 	initIncubatorFn initIncubatorFn
 }
 
@@ -68,6 +72,7 @@ func NewInit(m map[string]interface{}) (*Init, error) {
 		skipDefaultRegistries: ol.loadBool(OptionSkipDefaultRegistries),
 
 		appInitFn:       appinit.Init,
+		appLoadFn:       app.Load,
 		initIncubatorFn: initIncubator,
 	}
 
@@ -83,7 +88,12 @@ func (i *Init) Run() error {
 	var registries []registry.Registry
 
 	if !i.skipDefaultRegistries {
-		gh, err := i.initIncubatorFn()
+		a, err := i.appLoadFn(i.fs, i.rootPath, true)
+		if err != nil {
+			return err
+		}
+
+		gh, err := i.initIncubatorFn(a)
 		if err != nil {
 			return err
 		}
@@ -102,10 +112,12 @@ func (i *Init) Run() error {
 	)
 }
 
-func initIncubator() (registry.Registry, error) {
-	return registry.NewGitHub(&app.RegistryRefSpec{
-		Name:     "incubator",
-		Protocol: registry.ProtocolGitHub,
-		URI:      defaultIncubatorURI,
-	})
+func initIncubator(a app.App) (registry.Registry, error) {
+	return registry.NewGitHub(
+		a,
+		&app.RegistryRefSpec{
+			Name:     "incubator",
+			Protocol: registry.ProtocolGitHub,
+			URI:      defaultIncubatorURI,
+		})
 }
diff --git a/actions/init_test.go b/actions/init_test.go
index ac5375f86b3a75acc49e4aacc63930fca6752389..6a77915ec02fa3bdbd2e118cf3cdde20cfafd361 100644
--- a/actions/init_test.go
+++ b/actions/init_test.go
@@ -18,6 +18,7 @@ package actions
 import (
 	"testing"
 
+	"github.com/ksonnet/ksonnet/metadata/app"
 	amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
 	"github.com/ksonnet/ksonnet/pkg/registry"
 	rmocks "github.com/ksonnet/ksonnet/pkg/registry/mocks"
@@ -85,7 +86,11 @@ func TestInit(t *testing.T) {
 					return nil
 				}
 
-				a.initIncubatorFn = func() (registry.Registry, error) {
+				a.appLoadFn = func(fs afero.Fs, root string, skipFindRoot bool) (app.App, error) {
+					return appMock, nil
+				}
+
+				a.initIncubatorFn = func(a app.App) (registry.Registry, error) {
 					r := &rmocks.Registry{}
 					r.On("Protocol").Return("github")
 					r.On("URI").Return("github.com/ksonnet/parts/tree/master/incubator")
diff --git a/cmd/root.go b/cmd/root.go
index cd8b7a2c6ce84383c116ffaf2781f7ed404809a7..7b3f7a3cc4c024eaccdc70c67d0f7158068d8d3b 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -102,7 +102,7 @@ application configuration to remote clusters.
 			isInit = true
 		}
 
-		ka, err = app.Load(appFs, wd)
+		ka, err = app.Load(appFs, wd, false)
 		if err != nil && isInit {
 			return err
 		}
diff --git a/e2e/init_test.go b/e2e/init_test.go
index aa3c7afb396aebcacb0bc24de6ef3824fac5c92d..22d2b43f4db7c137ebf8f7373bef5547dfdfa8f8 100644
--- a/e2e/init_test.go
+++ b/e2e/init_test.go
@@ -1,3 +1,18 @@
+// Copyright 2018 The ksonnet authors
+//
+//
+//    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.
+
 // +build e2e
 
 package e2e
@@ -22,6 +37,10 @@ var _ = Describe("ks init", func() {
 			o := a.registryList()
 			assertOutput(filepath.Join("init", "registry-output.txt"), o.stdout)
 		})
+
+		It("creates a gitignore", func() {
+			assertContents(filepath.Join("init", "gitignore"), filepath.Join(a.dir, ".gitignore"))
+		})
 	})
 
 	Context("without default registries", func() {
diff --git a/e2e/pkg_test.go b/e2e/pkg_test.go
index 32d634b1f8702cf92184feeefac41a44ac9cc980..488d8c7b36279f877fd28aa8959a1b6b8cc86c3c 100644
--- a/e2e/pkg_test.go
+++ b/e2e/pkg_test.go
@@ -18,6 +18,7 @@
 package e2e
 
 import (
+	"os"
 	"path/filepath"
 
 	. "github.com/onsi/ginkgo"
@@ -101,6 +102,19 @@ var _ = Describe("ks pkg", func() {
 			assertExitStatus(o, 0)
 			assertOutput("pkg/list/output.txt", o.stdout)
 		})
+
+		Context("git spec cache has been been deleted", func() {
+			JustBeforeEach(func() {
+				err := os.RemoveAll(filepath.Join(a.dir, ".ksonnet"))
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("generates spec cache", func() {
+				o := a.runKs("pkg", "list")
+				assertExitStatus(o, 0)
+				assertOutput("pkg/list/output.txt", o.stdout)
+			})
+		})
 	})
 
 	Context("use", func() {
diff --git a/e2e/show_test.go b/e2e/show_test.go
index 5896f711771214745b31b7d7957996005593acfa..bbaadfd33dc3eec8bd4537f28641074e60084942 100644
--- a/e2e/show_test.go
+++ b/e2e/show_test.go
@@ -18,7 +18,11 @@
 package e2e
 
 import (
+	"os"
+	"path/filepath"
+
 	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
 )
 
 var _ = Describe("ks show", func() {
@@ -35,4 +39,17 @@ var _ = Describe("ks show", func() {
 		assertOutput("show/output.txt", o.stdout)
 	})
 
+	Context("lib does not exists", func() {
+		JustBeforeEach(func() {
+			err := os.RemoveAll(filepath.Join(a.dir, "lib"))
+			Expect(err).NotTo(HaveOccurred())
+		})
+
+		It("generates spec cache", func() {
+			o := a.runKs("show", "default")
+			assertExitStatus(o, 0)
+			assertOutput("show/output.txt", o.stdout)
+		})
+	})
+
 })
diff --git a/e2e/testdata/output/init/gitignore b/e2e/testdata/output/init/gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a67900298c57d166cc5364501bb2d21992282353
--- /dev/null
+++ b/e2e/testdata/output/init/gitignore
@@ -0,0 +1,2 @@
+/lib
+/.ksonnet/registries
diff --git a/metadata/app/app.go b/metadata/app/app.go
index 5f9b85a200317e514ff73f3164da6dc031d9080a..e82cbb2acf2015c35443bfa353d4c152dc3974e6 100644
--- a/metadata/app/app.go
+++ b/metadata/app/app.go
@@ -71,15 +71,19 @@ type App interface {
 }
 
 // Load loads the application configuration.
-func Load(fs afero.Fs, cwd string) (App, error) {
-	appRoot, err := findRoot(fs, cwd)
-	if err != nil {
-		return nil, err
+func Load(fs afero.Fs, cwd string, skipFindRoot bool) (App, error) {
+	appRoot := cwd
+	if !skipFindRoot {
+		var err error
+		appRoot, err = findRoot(fs, cwd)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	spec, err := read(fs, appRoot)
 	if err != nil {
-		return nil, err
+		return NewApp010(fs, appRoot), nil
 	}
 
 	switch spec.APIVersion {
diff --git a/metadata/manager.go b/metadata/manager.go
index 99b0d4f1033029208bf75128e5908427de44756a..fcdf5be7ea86cb23363ef677653ffe074ddcd20b 100644
--- a/metadata/manager.go
+++ b/metadata/manager.go
@@ -146,7 +146,7 @@ func (m *manager) Root() string {
 }
 
 func (m *manager) App() (app.App, error) {
-	return app.Load(m.appFS, m.rootPath)
+	return app.Load(m.appFS, m.rootPath, false)
 }
 
 func (m *manager) LibPaths() (envPath, vendorPath string) {
diff --git a/pkg/appinit/init.go b/pkg/appinit/init.go
index c2006a1844114d50931bcd12c6239d9d86f8fcdd..adc1a74094b25eb5fb55986cbe986f7b92bd057d 100644
--- a/pkg/appinit/init.go
+++ b/pkg/appinit/init.go
@@ -65,7 +65,7 @@ func (i *initApp) Run() error {
 	}
 
 	// Load application.
-	a, err := app.Load(i.fs, i.rootPath)
+	a, err := app.Load(i.fs, i.rootPath, false)
 	if err != nil {
 		return err
 	}
@@ -187,6 +187,10 @@ func (i *initApp) createAppDirTree() error {
 		path    string
 		content []byte
 	}{
+		{
+			filepath.Join(i.rootPath, ".gitignore"),
+			ignoreData,
+		},
 		{
 			filepath.Join(i.rootPath, "components", "params.libsonnet"),
 			component.GenParamsContent(),
@@ -210,3 +214,7 @@ func (i *initApp) createAppDirTree() error {
 
 	return nil
 }
+
+var ignoreData = []byte(`/lib
+/.ksonnet/registries
+`)
diff --git a/pkg/appinit/init_test.go b/pkg/appinit/init_test.go
index 8fd14eb8b1e47d44a432bc5f43b7bb54988165d7..291bbc20660b242765fbbdb26c0d82036956dcaf 100644
--- a/pkg/appinit/init_test.go
+++ b/pkg/appinit/init_test.go
@@ -94,6 +94,7 @@ func TestInit(t *testing.T) {
 
 func checkApp(t *testing.T, fs afero.Fs, rootPath, version, namespace string) {
 	expectedDirs := []string{
+		".gitignore",
 		"app.yaml",
 		filepath.Join(".ksonnet", "registries", "testdata", "registry.yaml"),
 		filepath.Join("components", "params.libsonnet"),
diff --git a/pkg/registry/add.go b/pkg/registry/add.go
index cc1cd8e49019b327062f9e56b9ef85d0264fb111..f6b231221466d1daa03a111ca89272945f54491b 100644
--- a/pkg/registry/add.go
+++ b/pkg/registry/add.go
@@ -16,8 +16,6 @@
 package registry
 
 import (
-	"path/filepath"
-
 	"github.com/ksonnet/ksonnet/metadata/app"
 	"github.com/pkg/errors"
 	"github.com/spf13/afero"
@@ -37,7 +35,7 @@ func Add(a app.App, name, protocol, uri, version string, isOverride bool) (*Spec
 
 	switch protocol {
 	case ProtocolGitHub:
-		r, err = githubFactory(initSpec)
+		r, err = githubFactory(a, initSpec)
 	case ProtocolFilesystem:
 		r, err = NewFs(a, initSpec)
 	default:
@@ -54,7 +52,7 @@ func Add(a app.App, name, protocol, uri, version string, isOverride bool) (*Spec
 	}
 
 	// Retrieve the contents of registry.
-	registrySpec, err := getOrCacheRegistry(a, r)
+	registrySpec, err := r.FetchRegistrySpec()
 	if err != nil {
 		return nil, errors.Wrap(err, "cache registry")
 	}
@@ -62,46 +60,6 @@ func Add(a app.App, name, protocol, uri, version string, isOverride bool) (*Spec
 	return registrySpec, nil
 }
 
-func getOrCacheRegistry(a app.App, gh Registry) (*Spec, error) {
-	// Check local disk cache.
-	registrySpecFile := makePath(a, gh)
-	registrySpec, exists, err := load(a, registrySpecFile)
-	if err != nil {
-		return nil, errors.Wrap(err, "load registry spec file")
-	}
-
-	if !exists {
-		// If failed, use the protocol to try to retrieve app specification.
-		registrySpec, err = gh.FetchRegistrySpec()
-		if err != nil {
-			return nil, err
-		}
-
-		registrySpecBytes, err := registrySpec.Marshal()
-		if err != nil {
-			return nil, err
-		}
-
-		// NOTE: We call mkdir after getting the registry spec, since a
-		// network call might fail and leave this half-initialized empty
-		// directory.
-		registrySpecDir := filepath.Join(root(a), gh.RegistrySpecDir())
-		err = a.Fs().MkdirAll(registrySpecDir, app.DefaultFolderPermissions)
-		if err != nil {
-			return nil, err
-		}
-
-		err = afero.WriteFile(a.Fs(), registrySpecFile, registrySpecBytes, app.DefaultFilePermissions)
-		if err != nil {
-			return nil, err
-		}
-	} else if err != nil {
-		return nil, err
-	}
-
-	return registrySpec, nil
-}
-
 func load(a app.App, path string) (*Spec, bool, error) {
 	exists, err := afero.Exists(a.Fs(), path)
 	if err != nil {
diff --git a/pkg/registry/add_test.go b/pkg/registry/add_test.go
index a48f9820914c1e31bce7e34c4a4ba500c7a03c65..b1962de0fb24355bb5acd523e7fa078fc4919246 100644
--- a/pkg/registry/add_test.go
+++ b/pkg/registry/add_test.go
@@ -69,8 +69,8 @@ func TestAdd(t *testing.T) {
 			Return(registryContent, nil, nil)
 
 		ghOpt := GitHubClient(ghMock)
-		githubFactory = func(registryRef *app.RegistryRefSpec) (*GitHub, error) {
-			return NewGitHub(registryRef, ghOpt)
+		githubFactory = func(a app.App, registryRef *app.RegistryRefSpec) (*GitHub, error) {
+			return NewGitHub(a, registryRef, ghOpt)
 		}
 
 		spec, err := Add(appMock, "new", ProtocolGitHub, "github.com/foo/bar", "", true)
diff --git a/pkg/registry/cache.go b/pkg/registry/cache.go
index ee56c09ebee5feb8db9608016c56ddb735ae9d93..759c2893e1fe3da1d180c877e61f4345cf44428a 100644
--- a/pkg/registry/cache.go
+++ b/pkg/registry/cache.go
@@ -26,7 +26,7 @@ import (
 	"github.com/spf13/afero"
 )
 
-// CacheDependency caches registry dependencies.
+// CacheDependency vendors registry dependencies.
 // TODO: create unit tests for this once mocks for this package are
 // worked out.
 func CacheDependency(a app.App, d pkg.Descriptor, customName string) error {
@@ -80,7 +80,6 @@ func CacheDependency(a app.App, d pkg.Descriptor, customName string) error {
 
 	// Add library to app specification, but wait to write it out until
 	// the end, in case one of the network calls fails.
-
 	log.Infof("Retrieved %d files", len(files))
 
 	for _, dir := range directories {
diff --git a/pkg/registry/cache_test.go b/pkg/registry/cache_test.go
index 4e8ceed46fddf14fca6053cf3af2056482ead279..e26fedc320ff46ee7f2723f949724f18cd3e1c13 100644
--- a/pkg/registry/cache_test.go
+++ b/pkg/registry/cache_test.go
@@ -16,62 +16,36 @@
 package registry
 
 import (
+	"path/filepath"
 	"testing"
 
 	"github.com/ksonnet/ksonnet/metadata/app"
 	amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
 	"github.com/ksonnet/ksonnet/pkg/pkg"
-	ghutil "github.com/ksonnet/ksonnet/pkg/util/github"
-	"github.com/ksonnet/ksonnet/pkg/util/github/mocks"
+	"github.com/ksonnet/ksonnet/pkg/util/test"
 	"github.com/spf13/afero"
-	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 )
 
 func Test_CacheDependency(t *testing.T) {
 	withApp(t, func(a *amocks.App, fs afero.Fs) {
+		test.StageDir(t, fs, "incubator", filepath.Join("/work", "incubator"))
+
 		libraries := app.LibraryRefSpecs{}
 		a.On("Libraries").Return(libraries, nil)
 
 		registries := app.RegistryRefSpecs{
 			"incubator": &app.RegistryRefSpec{
 				Name:     "incubator",
-				Protocol: ProtocolGitHub,
-				URI:      "github.com/foo/bar/tree/master/incubator",
-				GitVersion: &app.GitVersionSpec{
-					CommitSHA: "54321",
-					RefSpec:   "master",
-				},
+				Protocol: ProtocolFilesystem,
+				URI:      "file:///work/incubator",
 			},
 		}
 		a.On("Registries").Return(registries, nil)
 
-		ghMock := &mocks.GitHub{}
-		ghMock.On("CommitSHA1", mock.Anything, mock.Anything, "master").Return("54321", nil)
-
-		repo := ghutil.Repo{Org: "foo", Repo: "bar"}
-		mockPartFs(t, repo, ghMock, "incubator/apache", "54321")
-
-		registryContent := buildContent(t, registryYAMLFile)
-		ghMock.On(
-			"Contents",
-			mock.Anything,
-			registryYAMLFile,
-			"40285d8a14f1ac5787e405e1023cf0c07f6aa28c").
-			Return(registryContent, nil, nil)
-
-		ghOpt := GitHubClient(ghMock)
-		githubFactory = func(registryRef *app.RegistryRefSpec) (*GitHub, error) {
-			return NewGitHub(registryRef, ghOpt)
-		}
-
 		library := &app.LibraryRefSpec{
 			Name:     "apache",
 			Registry: "incubator",
-			GitVersion: &app.GitVersionSpec{
-				CommitSHA: "54321",
-				RefSpec:   "master",
-			},
 		}
 		a.On("UpdateLib", "apache", library).Return(nil)
 
@@ -79,5 +53,7 @@ func Test_CacheDependency(t *testing.T) {
 
 		err := CacheDependency(a, d, "")
 		require.NoError(t, err)
+
+		test.AssertExists(t, fs, filepath.Join(a.Root(), "vendor", "incubator", "apache", "parts.yaml"))
 	})
 }
diff --git a/pkg/registry/fs.go b/pkg/registry/fs.go
index 1165abbec70a1c8c31057684479900c5c650fa51..0fd8a687ed3fabf484d33b5fa39bb6a7ba4ccf76 100644
--- a/pkg/registry/fs.go
+++ b/pkg/registry/fs.go
@@ -118,6 +118,10 @@ func (fs *Fs) ResolveLibrarySpec(partName, libRefSpec string) (*parts.Spec, erro
 
 // ResolveLibrary fetches the part and creates a parts spec and library ref spec.
 func (fs *Fs) ResolveLibrary(partName, partAlias, libRefSpec string, onFile ResolveFile, onDir ResolveDirectory) (*parts.Spec, *app.LibraryRefSpec, error) {
+	if partAlias == "" {
+		partAlias = partName
+	}
+
 	partRoot := filepath.Join(fs.RegistrySpecDir(), partName)
 	parentDir := filepath.Dir(fs.RegistrySpecDir())
 
diff --git a/pkg/registry/github.go b/pkg/registry/github.go
index c9bf37fc5e1416c7c31d190929e74a2dccb547b8..74abb9d4c772728c6e6c86411c6e1aac8aaea998 100644
--- a/pkg/registry/github.go
+++ b/pkg/registry/github.go
@@ -20,12 +20,14 @@ import (
 	"fmt"
 	"net/url"
 	"path"
+	"path/filepath"
 	"strings"
 
 	"github.com/ksonnet/ksonnet/metadata/app"
 	"github.com/ksonnet/ksonnet/pkg/parts"
 	"github.com/ksonnet/ksonnet/pkg/util/github"
 	"github.com/pkg/errors"
+	"github.com/spf13/afero"
 )
 
 const (
@@ -37,12 +39,12 @@ var (
 	// errInvalidURI is an invalid github uri error.
 	errInvalidURI = fmt.Errorf("Invalid GitHub URI: try navigating in GitHub to the URI of the folder containing the 'yaml', and using that URI instead. Generally, this URI should be of the form 'github.com/{organization}/{repository}/tree/{branch}/[path-to-directory]'")
 
-	githubFactory = func(spec *app.RegistryRefSpec) (*GitHub, error) {
-		return NewGitHub(spec)
+	githubFactory = func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) {
+		return NewGitHub(a, spec)
 	}
 )
 
-type ghFactoryFn func(spec *app.RegistryRefSpec) (*GitHub, error)
+type ghFactoryFn func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error)
 
 // GitHubClient is an option for the setting a github client.
 func GitHubClient(c github.GitHub) GitHubOpt {
@@ -56,6 +58,7 @@ type GitHubOpt func(*GitHub)
 
 // GitHub is a Github Registry
 type GitHub struct {
+	app      app.App
 	name     string
 	hd       *hubDescriptor
 	ghClient github.GitHub
@@ -63,12 +66,13 @@ type GitHub struct {
 }
 
 // NewGitHub creates an instance of GitHub.
-func NewGitHub(registryRef *app.RegistryRefSpec, opts ...GitHubOpt) (*GitHub, error) {
+func NewGitHub(a app.App, registryRef *app.RegistryRefSpec, opts ...GitHubOpt) (*GitHub, error) {
 	if registryRef == nil {
 		return nil, errors.New("registry ref is nil")
 	}
 
 	gh := &GitHub{
+		app:      a,
 		name:     registryRef.Name,
 		spec:     registryRef,
 		ghClient: github.DefaultClient,
@@ -80,19 +84,21 @@ func NewGitHub(registryRef *app.RegistryRefSpec, opts ...GitHubOpt) (*GitHub, er
 	}
 	gh.hd = hd
 
-	for _, opt := range opts {
-		opt(gh)
-	}
+	if gh.spec.GitVersion == nil || gh.spec.GitVersion.CommitSHA == "" {
+		for _, opt := range opts {
+			opt(gh)
+		}
 
-	ctx := context.Background()
-	sha, err := gh.ghClient.CommitSHA1(ctx, hd.Repo(), hd.refSpec)
-	if err != nil {
-		return nil, errors.Wrap(err, "unable to find SHA1 for repo")
-	}
+		ctx := context.Background()
+		sha, err := gh.ghClient.CommitSHA1(ctx, hd.Repo(), hd.refSpec)
+		if err != nil {
+			return nil, errors.Wrap(err, "unable to find SHA1 for repo")
+		}
 
-	gh.spec.GitVersion = &app.GitVersionSpec{
-		RefSpec:   hd.refSpec,
-		CommitSHA: sha,
+		gh.spec.GitVersion = &app.GitVersionSpec{
+			RefSpec:   hd.refSpec,
+			CommitSHA: sha,
+		}
 	}
 
 	return gh, nil
@@ -133,6 +139,47 @@ func (gh *GitHub) RegistrySpecFilePath() string {
 
 // FetchRegistrySpec fetches the registry spec.
 func (gh *GitHub) FetchRegistrySpec() (*Spec, error) {
+	// Check local disk cache.
+	registrySpecFile := makePath(gh.app, gh)
+	registrySpec, exists, err := load(gh.app, registrySpecFile)
+	if err != nil {
+		return nil, errors.Wrap(err, "load registry spec file")
+	}
+
+	if !exists {
+		// If failed, use the protocol to try to retrieve app specification.
+		registrySpec, err = gh.cacheRegistrySpec()
+		if err != nil {
+			return nil, err
+		}
+
+		var registrySpecBytes []byte
+		registrySpecBytes, err = registrySpec.Marshal()
+		if err != nil {
+			return nil, err
+		}
+
+		// NOTE: We call mkdir after getting the registry spec, since a
+		// network call might fail and leave this half-initialized empty
+		// directory.
+		registrySpecDir := filepath.Join(root(gh.app), gh.RegistrySpecDir())
+		err = gh.app.Fs().MkdirAll(registrySpecDir, app.DefaultFolderPermissions)
+		if err != nil {
+			return nil, err
+		}
+
+		err = afero.WriteFile(gh.app.Fs(), registrySpecFile, registrySpecBytes, app.DefaultFilePermissions)
+		if err != nil {
+			return nil, err
+		}
+	} else if err != nil {
+		return nil, err
+	}
+
+	return registrySpec, nil
+}
+
+func (gh *GitHub) cacheRegistrySpec() (*Spec, error) {
 	ctx := context.Background()
 
 	file, _, err := gh.ghClient.Contents(ctx, gh.hd.Repo(), gh.hd.regSpecRepoPath,
diff --git a/pkg/registry/github_test.go b/pkg/registry/github_test.go
index 0701c0d771c8532e87135c9cd1c0ed8190bad342..f692a762a9b13cee803879006bbca9d59fac8bb9 100644
--- a/pkg/registry/github_test.go
+++ b/pkg/registry/github_test.go
@@ -24,15 +24,23 @@ import (
 
 	"github.com/google/go-github/github"
 	"github.com/ksonnet/ksonnet/metadata/app"
+	amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
 	"github.com/ksonnet/ksonnet/pkg/parts"
 	ghutil "github.com/ksonnet/ksonnet/pkg/util/github"
 	"github.com/ksonnet/ksonnet/pkg/util/github/mocks"
+	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 )
 
 func makeGh(t *testing.T, u, sha1 string) (*GitHub, *mocks.GitHub) {
+	fs := afero.NewMemMapFs()
+	appMock := &amocks.App{}
+	appMock.On("Fs").Return(fs)
+	appMock.On("Root").Return("/app")
+	appMock.On("LibPath", mock.AnythingOfType("string")).Return(filepath.Join("/app", "lib", "v1.8.7"), nil)
+
 	ghMock := &mocks.GitHub{}
 	ghMock.On("CommitSHA1", mock.Anything, ghutil.Repo{Org: "ksonnet", Repo: "parts"}, "master").
 		Return(sha1, nil)
@@ -45,7 +53,7 @@ func makeGh(t *testing.T, u, sha1 string) (*GitHub, *mocks.GitHub) {
 		URI:      "github.com/ksonnet/parts/tree/master/incubator",
 	}
 
-	g, err := NewGitHub(spec, optGh)
+	g, err := NewGitHub(appMock, spec, optGh)
 	require.NoError(t, err)
 
 	return g, ghMock
diff --git a/pkg/registry/manager.go b/pkg/registry/manager.go
index 104e50a44e171610d49ed5b71ef287a04b11225c..278c70085ea64113f5ca237032d6913bd42891ba 100644
--- a/pkg/registry/manager.go
+++ b/pkg/registry/manager.go
@@ -57,7 +57,7 @@ func Package(a app.App, name string) (*pkg.Package, error) {
 func Locate(a app.App, spec *app.RegistryRefSpec) (Registry, error) {
 	switch spec.Protocol {
 	case ProtocolGitHub:
-		return githubFactory(spec)
+		return githubFactory(a, spec)
 	case ProtocolFilesystem:
 		return NewFs(a, spec)
 	default:
diff --git a/pkg/registry/manager_test.go b/pkg/registry/manager_test.go
index 8e51ec36919d3e623c33acefdf565e339a06a18e..e0741044fbe3544ad0f3316f69e1f344bb41ccec 100644
--- a/pkg/registry/manager_test.go
+++ b/pkg/registry/manager_test.go
@@ -38,8 +38,8 @@ func Test_Package(t *testing.T) {
 			Return(content, nil, nil)
 
 		ghcOpt := GitHubClient(c)
-		githubFactory = func(spec *app.RegistryRefSpec) (*GitHub, error) {
-			return NewGitHub(spec, ghcOpt)
+		githubFactory = func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) {
+			return NewGitHub(a, spec, ghcOpt)
 		}
 
 		registries := app.RegistryRefSpecs{
@@ -66,8 +66,8 @@ func Test_List(t *testing.T) {
 			Return("12345", nil)
 
 		ghcOpt := GitHubClient(c)
-		githubFactory = func(spec *app.RegistryRefSpec) (*GitHub, error) {
-			return NewGitHub(spec, ghcOpt)
+		githubFactory = func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) {
+			return NewGitHub(a, spec, ghcOpt)
 		}
 
 		specs := app.RegistryRefSpecs{
diff --git a/pkg/registry/testdata/incubator/README.md b/pkg/registry/testdata/incubator/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..73e0907eccf3e1794bcad8a0433d62c409268d12
--- /dev/null
+++ b/pkg/registry/testdata/incubator/README.md
@@ -0,0 +1,33 @@
+# Incubator Registry
+
+## Overview
+
+This directory is an official ksonnet-compatible [registry][2]. If you are unfamiliar with ksonnet, we recommend browsing [the official site][1] to gain more context.
+
+Out of the box, ksonnet's CLI tool (`ks`) is aware of the `incubator` registry, and can download any of its libraries via `ks pkg install`.
+
+## Usage
+
+Assuming that you have the `ks` tool [installed][3], you can use any library in this registry as follows, by replacing `redis` with `<library-name>`:
+
+```
+# List all available packages (e.g. apache, efk, mariadb..)
+ks pkg list
+
+# Describe a specific package
+ks pkg describe incubator/redis@master
+
+# Download a specific package
+ks pkg install incubator/redis@master
+```
+
+## Library-specific Documentation
+
+Each of the libraries in this directory has its own README.md. These are autogenerated from the metadata in their `parts.yaml` file, using the [`doc-gen` script][4].
+
+Note that you can use the `ks` commands in your terminal to access this same documentation.
+
+[1]: https://ksonnet.io
+[2]: https://ksonnet.io/docs/concepts#registry
+[3]: https://ksonnet.io/#get-started
+[4]: /doc-gen/main.go
diff --git a/pkg/registry/testdata/incubator/apache/README.md b/pkg/registry/testdata/incubator/apache/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0efbda17d7f6fb7c77a46afa2471dbe6bf780829
--- /dev/null
+++ b/pkg/registry/testdata/incubator/apache/README.md
@@ -0,0 +1,60 @@
+# apache
+
+> Apache Ksonnet mixin library contains a simple prototype with pre-configured components to help you deploy a Apache HTTP Server app to a Kubernetes cluster with ease.
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.apache-simple](#io.ksonnet.pkg.apache-simple)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.apache-simple` prototype to generate Kubernetes YAML for apache, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.apache-simple apache \
+  --name apache \
+  --namespace default
+
+# Apply to server.
+$ ks apply -f apache.jsonnet
+```
+
+## Using the library
+
+The library files for apache define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure apache for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of apache, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.apache-simple
+
+Apache HTTP Server. Apache is deployed using a deployment, and exposed to the network using a service.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.apache-simple apache \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace to divvy up your cluster; default is 'default' [string]
+* `--name=<name>`: Name to identify all Kubernetes objects in this prototype [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/apache/apache.libsonnet b/pkg/registry/testdata/incubator/apache/apache.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..09e246529b2db7c7af13bb97339842af8086ec4e
--- /dev/null
+++ b/pkg/registry/testdata/incubator/apache/apache.libsonnet
@@ -0,0 +1,107 @@
+local k = import "k.libsonnet";
+local deployment = k.extensions.v1beta1.deployment;
+
+{
+  parts::{
+    svc(namespace, name, selector={app: name}):: {
+      apiVersion: "v1",
+      kind: "Service",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: {
+          app: name
+        },
+      },
+      spec: {
+        type: "LoadBalancer",
+        ports: [
+          {
+            name: "http",
+            port: 80,
+            targetPort: "http",
+          },
+          {
+            name: "https",
+            port: 443,
+            targetPort: "https",
+          },
+        ],
+        selector: selector
+      },
+    },
+
+    deployment(namespace, name, labels={app: name})::{
+      local defaults = {
+        // ref: https://hub.docker.com/r/bitnami/apache/tags/
+        imageTag:: "2.4.23-r12",
+        // ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images
+        imagePullPolicy:: "IfNotPresent",
+      },
+      apiVersion: "extensions/v1beta1",
+      kind: "Deployment",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: labels
+      },
+      spec: {
+        replicas: 1,
+        template: {
+          metadata: {
+            labels: labels
+          },
+          spec: {
+            containers: [
+              {
+                name: name,
+                image: "bitnami/apache:%s" % defaults.imageTag,
+                imagePullPolicy: defaults.imagePullPolicy,
+                ports: [
+                  {
+                    name: "http",
+                    containerPort: 80,
+                  },
+                  {
+                    name: "https",
+                    containerPort: 443,
+                  }
+                ],
+                livenessProbe: {
+                  httpGet: {
+                    path: "/",
+                    port: "http",
+                  },
+                  initialDelaySeconds: 30,
+                  timeoutSeconds: 5,
+                  failureThreshold: 6,
+                },
+                readinessProbe: {
+                  httpGet: {
+                    path: "/",
+                    port: "http",
+                  },
+                  initialDelaySeconds: 5,
+                  timeoutSeconds: 3,
+                  periodSeconds: 5,
+                },
+                volumeMounts: [
+                  {
+                    name: "apache-data",
+                    mountPath: "/bitnami/apache",
+                  }
+                ],
+              }
+            ],
+            volumes: [
+              {
+                name: "apache-data",
+                emptyDir: {},
+              }
+            ]
+          },
+        },
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/apache/examples/apache.jsonnet b/pkg/registry/testdata/incubator/apache/examples/apache.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..523d9d0c9ff05486217bb3b67b579732131a00b2
--- /dev/null
+++ b/pkg/registry/testdata/incubator/apache/examples/apache.jsonnet
@@ -0,0 +1,13 @@
+local k = import "k.libsonnet";
+local apache = import "../apache.libsonnet";
+
+
+local namespace = "default";
+local name = "apache-app";
+
+k.core.v1.list.new(
+  [
+    apache.parts.deployment(namespace, name),
+    apache.parts.svc(namespace, name)
+  ]
+)
diff --git a/pkg/registry/testdata/incubator/apache/examples/generated.yaml b/pkg/registry/testdata/incubator/apache/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f92073ce7d512387fe5d88eb79aa7e367bba8f77
--- /dev/null
+++ b/pkg/registry/testdata/incubator/apache/examples/generated.yaml
@@ -0,0 +1,61 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: apache-app
+  name: apache-app
+spec:
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: apache-app
+    spec:
+      containers:
+      - image: bitnami/apache:2.4.23-r12
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          failureThreshold: 6
+          httpGet:
+            path: /
+            port: http
+          initialDelaySeconds: 30
+          timeoutSeconds: 5
+        name: apache-app
+        ports:
+        - containerPort: 80
+          name: http
+        - containerPort: 443
+          name: https
+        readinessProbe:
+          httpGet:
+            path: /
+            port: http
+          initialDelaySeconds: 5
+          periodSeconds: 5
+          timeoutSeconds: 3
+        volumeMounts:
+        - mountPath: /bitnami/apache
+          name: apache-data
+      volumes:
+      - emptyDir: {}
+        name: apache-data
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: apache-app
+  name: apache-app
+spec:
+  ports:
+  - name: http
+    port: 80
+    targetPort: http
+  - name: https
+    port: "443"
+    targetPort: https
+  selector:
+    app: apache-app
+  type: LoadBalancer
\ No newline at end of file
diff --git a/pkg/registry/testdata/incubator/apache/parts.yaml b/pkg/registry/testdata/incubator/apache/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4ec21a77179498392897e6a8dbda21f95f71695d
--- /dev/null
+++ b/pkg/registry/testdata/incubator/apache/parts.yaml
@@ -0,0 +1,40 @@
+{
+  "name": "apache",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "Apache Ksonnet mixin library contains a simple prototype with pre-configured components to help you deploy a Apache HTTP Server app to a Kubernetes cluster with ease.",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "apache",
+    "server",
+    "http"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.apache-simple",
+    "componentName": "apache",
+    "flags": {
+      "name": "apache",
+      "namespace": "default"
+    },
+    "comment": "Run a simple Apache server"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/apache/prototypes/apache-simple.jsonnet b/pkg/registry/testdata/incubator/apache/prototypes/apache-simple.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..b3fce3b8e8066854780410e1b5005dd7a17324fd
--- /dev/null
+++ b/pkg/registry/testdata/incubator/apache/prototypes/apache-simple.jsonnet
@@ -0,0 +1,18 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.apache-simple
+// @description Apache HTTP Server. Apache is deployed using a deployment, and exposed to the
+//   network using a service.
+// @shortDescription A simple, stateless Apache HTTP server.
+// @param namespace string Namespace to divvy up your cluster; default is 'default'
+// @param name string Name to identify all Kubernetes objects in this prototype
+
+local k = import 'k.libsonnet';
+local apache = import 'incubator/apache/apache.libsonnet';
+
+local namespace = import 'param://namespace';
+local appName = import 'param://name';
+
+k.core.v1.list.new([
+  apache.parts.deployment(namespace, appName),
+  apache.parts.svc(namespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/efk/efk.libsonnet b/pkg/registry/testdata/incubator/efk/efk.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..0acd5db45bcc7d8b2cab09d84d9d2d5361263059
--- /dev/null
+++ b/pkg/registry/testdata/incubator/efk/efk.libsonnet
@@ -0,0 +1,313 @@
+local k = import 'k.libsonnet';
+local deployment = k.extensions.v1beta1.deployment;
+local container = deployment.mixin.spec.template.spec.containersType;
+local volume = deployment.mixin.spec.template.spec.volumesType;
+
+// TODO: Very clearly WIP, needs to be refactored
+{
+  parts:: {
+    kibana::{
+      deployment(namespace)::{
+          "apiVersion":"apps/v1beta1",
+          "kind":"Deployment",
+          "metadata":{
+            "name":"kibana-logging",
+            "labels":{
+                "k8s-app":"kibana-logging",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile"
+            }
+          },
+          "spec":{
+            "replicas":1,
+            "selector":{
+                "matchLabels":{
+                  "k8s-app":"kibana-logging"
+                }
+            },
+            "template":{
+                "metadata":{
+                  "labels":{
+                      "k8s-app":"kibana-logging"
+                  }
+                },
+                "spec":{
+                  "containers":[
+                      {
+                        "name":"kibana-logging",
+                        "image":"docker.elastic.co/kibana/kibana:5.6.2",
+                        "resources":{
+                            "limits":{
+                              "cpu":"1000m"
+                            },
+                            "requests":{
+                              "cpu":"100m"
+                            }
+                        },
+                        "env":[
+                            {
+                              "name":"ELASTICSEARCH_URL",
+                              "value":"http://elasticsearch-logging:9200"
+                            },
+                            {
+                              "name":"SERVER_BASEPATH",
+                              "value":"/api/v1/proxy/namespaces/" + namespace + "/services/kibana-logging"
+                            },
+                            {
+                              "name":"XPACK_MONITORING_ENABLED",
+                              "value":"false"
+                            },
+                            {
+                              "name":"XPACK_SECURITY_ENABLED",
+                              "value":"false"
+                            }
+                        ],
+                        "ports":[
+                            {
+                              "containerPort":5601,
+                              "name":"ui",
+                              "protocol":"TCP"
+                            }
+                        ]
+                      }
+                  ]
+                }
+            }
+          }
+      },
+      svc::{
+          "apiVersion":"v1",
+          "kind":"Service",
+          "metadata":{
+            "name":"kibana-logging",
+            "labels":{
+                "k8s-app":"kibana-logging",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile",
+                "kubernetes.io/name":"Kibana"
+            }
+          },
+          "spec":{
+            "ports":[
+                {
+                  "port":5601,
+                  "protocol":"TCP",
+                  "targetPort":"ui"
+                }
+            ],
+            "selector":{
+                "k8s-app":"kibana-logging"
+            }
+          }
+      },
+
+    },
+    elasticsearch::{
+      serviceAccount::{
+          "apiVersion":"v1",
+          "kind":"ServiceAccount",
+          "metadata":{
+            "name":"elasticsearch-logging",
+            "labels":{
+                "k8s-app":"elasticsearch-logging",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile"
+            }
+          }
+      },
+      clusterRole::{
+          "kind":"ClusterRole",
+          "apiVersion":"rbac.authorization.k8s.io/v1beta1",
+          "metadata":{
+            "name":"elasticsearch-logging",
+            "labels":{
+                "k8s-app":"elasticsearch-logging",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile"
+            }
+          },
+          "rules":[
+            {
+                "apiGroups":[
+                  ""
+                ],
+                "resources":[
+                  "services",
+                  "namespaces",
+                  "endpoints"
+                ],
+                "verbs":[
+                  "get"
+                ]
+            }
+          ]
+      },
+      clusterRoleBinding(namespace)::{
+          "kind":"ClusterRoleBinding",
+          "apiVersion":"rbac.authorization.k8s.io/v1beta1",
+          "metadata":{
+            "name":"elasticsearch-logging",
+            "namespace": namespace,
+            "labels":{
+                "k8s-app":"elasticsearch-logging",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile"
+            }
+          },
+          "subjects":[
+            {
+                "kind":"ServiceAccount",
+                "name":"elasticsearch-logging",
+                "apiGroup":""
+            }
+          ],
+          "roleRef":{
+            "kind":"ClusterRole",
+            "name":"elasticsearch-logging",
+            "apiGroup":""
+          }
+      },
+      statefulSet::{
+          "apiVersion":"apps/v1beta1",
+          "kind":"StatefulSet",
+          "metadata":{
+            "name":"elasticsearch-logging",
+            "labels":{
+                "k8s-app":"elasticsearch-logging",
+                "version":"v5.6.2",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile"
+            }
+          },
+          "spec":{
+            "serviceName":"elasticsearch-logging",
+            "replicas":2,
+            "selector":{
+                "matchLabels":{
+                  "k8s-app":"elasticsearch-logging",
+                  "version":"v5.6.2"
+                }
+            },
+            "template":{
+                "metadata":{
+                  "labels":{
+                      "k8s-app":"elasticsearch-logging",
+                      "version":"v5.6.2",
+                      "kubernetes.io/cluster-service":"true"
+                  }
+                },
+                "spec":{
+                  "serviceAccountName":"elasticsearch-logging",
+                  "containers":[
+                      {
+                        "image":"gcr.io/google-containers/elasticsearch:v5.6.2",
+                        "name":"elasticsearch-logging",
+                        "resources":{
+                            "limits":{
+                              "cpu":"1000m"
+                            },
+                            "requests":{
+                              "cpu":"100m"
+                            }
+                        },
+                        "ports":[
+                            {
+                              "containerPort":9200,
+                              "name":"db",
+                              "protocol":"TCP"
+                            },
+                            {
+                              "containerPort":9300,
+                              "name":"transport",
+                              "protocol":"TCP"
+                            }
+                        ],
+                        "volumeMounts":[
+                            {
+                              "name":"elasticsearch-logging",
+                              "mountPath":"/data"
+                            }
+                        ],
+                        "env":[
+                            {
+                              "name":"NAMESPACE",
+                              "valueFrom":{
+                                  "fieldRef":{
+                                    "fieldPath":"metadata.namespace"
+                                  }
+                              }
+                            }
+                        ]
+                      }
+                  ],
+                  "volumes":[
+                      {
+                        "name":"elasticsearch-logging",
+                        "emptyDir":{
+
+                        }
+                      }
+                  ],
+                  "initContainers":[
+                      {
+                        "image":"alpine:3.6",
+                        "command":[
+                            "/sbin/sysctl",
+                            "-w",
+                            "vm.max_map_count=262144"
+                        ],
+                        "name":"elasticsearch-logging-init",
+                        "securityContext":{
+                            "privileged":true
+                        }
+                      }
+                  ]
+                }
+            }
+          }
+      },
+      svc::{
+          "apiVersion":"v1",
+          "kind":"Service",
+          "metadata":{
+            "name":"elasticsearch-logging",
+            "labels":{
+                "k8s-app":"elasticsearch-logging",
+                "kubernetes.io/cluster-service":"true",
+                "addonmanager.kubernetes.io/mode":"Reconcile",
+                "kubernetes.io/name":"Elasticsearch"
+            }
+          },
+          "spec":{
+            "ports":[
+                {
+                  "port":9200,
+                  "protocol":"TCP",
+                  "targetPort":"db"
+                }
+            ],
+            "selector":{
+                "k8s-app":"elasticsearch-logging"
+            }
+          }
+      },
+    },
+    fluentd:: {
+      // TODO: Add daemonset
+      sidecar(containerName)::
+        local volumeName = "logs";
+
+        deployment.mapContainersWithName(
+          [containerName],
+          function(c) c + container.withVolumeMounts(container.volumeMountsType.new(volumeName, "/var/log"))
+        ) + deployment.mixin.spec.template.spec.withContainers(
+          container
+            .new("fluentd-sidecar", "alpinejay/fluentd-sidecar-es:v1.1")
+            .withEnv(container.envType.new("FILES_TO_COLLECT", "/mnt/log/apache2/access.log /mnt/log/apache2/error.log"))
+            .withVolumeMounts(container.volumeMountsType.new(volumeName, "/mnt/log", true))
+        ) + deployment.mixin.spec.template.spec.withVolumes(
+           volume.fromEmptyDir(volumeName)
+        ),
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/efk/parts.yaml b/pkg/registry/testdata/incubator/efk/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0be6e0ea672aa02e33175626b124c247297a79fd
--- /dev/null
+++ b/pkg/registry/testdata/incubator/efk/parts.yaml
@@ -0,0 +1,31 @@
+name: efk
+apiVersion: 0.0.1
+kind: ksonnet.io/parts
+description: >
+  EFK (elasticsearch-fluentd-kibana) is a common logging stack used with
+  kubernetes.
+author: ksonnet team <ksonnet-help@heptio.com>
+contributors:
+- name: Tehut Getahun
+  email: tehut@heptio.com
+- name: Tamiko Terada
+  email: tamiko@heptio.com
+repository:
+  type: git
+  url: https://github.com/ksonnet/mixins
+bugs:
+  url: https://github.com/ksonnet/mixins/issues
+keywords:
+- elasticsearch
+- fluentd
+- kibana
+- logging
+quickStart:
+  prototype: io.ksonnet.pkg.elasticsearch-kibana
+  componentName: elasticsearch-kibana
+  flags:
+    name: elasticsearch-kibana
+    namespace: default
+    password: boots
+  comment: Logging stack that processes input from fluentd.
+license: Apache 2.0
diff --git a/pkg/registry/testdata/incubator/efk/prototypes/elasticsearch-kibana.jsonnet b/pkg/registry/testdata/incubator/efk/prototypes/elasticsearch-kibana.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..3600ff20890758f92511c3079cd8e85f4e1f5398
--- /dev/null
+++ b/pkg/registry/testdata/incubator/efk/prototypes/elasticsearch-kibana.jsonnet
@@ -0,0 +1,21 @@
+// @apiVersion 0.1
+// @name io.ksonnet.pkg.elasticsearch-kibana
+// @description Elasticsearch and Kibana stack for logging. Elasticsearch
+//   indexes the logs, and kibana provides a queryable, interactive UI.
+// @shortDescription The Elasticsearch and Kibana setup for an EFK logging stack.
+// @optionalParam namespace string default Namespace in which to put the application
+
+local k = import 'k.libsonnet';
+local efk = import 'incubator/efk/efk.libsonnet';
+
+local namespace = import 'param://namespace';
+
+k.core.v1.list.new([
+  efk.parts.kibana.deployment(namespace),
+  efk.parts.kibana.svc,
+  efk.parts.elasticsearch.serviceAccount,
+  efk.parts.elasticsearch.clusterRole,
+  efk.parts.elasticsearch.clusterRoleBinding(namespace),
+  efk.parts.elasticsearch.statefulSet,
+  efk.parts.elasticsearch.svc,
+])
diff --git a/pkg/registry/testdata/incubator/efk/prototypes/fluentd-es-sidecar.jsonnet b/pkg/registry/testdata/incubator/efk/prototypes/fluentd-es-sidecar.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..19e0a2703cceba40ee90a02a503a673d7abf8723
--- /dev/null
+++ b/pkg/registry/testdata/incubator/efk/prototypes/fluentd-es-sidecar.jsonnet
@@ -0,0 +1,13 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.fluentd-es-sidecar
+// @description A fluentd sidecar that can be added to a container to scrape
+//   and preprocess logs for elasticsearch.
+// @shortDescription The fluentd sidecar to scrape logs for an EFK stack
+// @param containerName string Name of the main container to be logged
+
+local k = import 'k.libsonnet';
+local efk = import 'incubator/efk/efk.libsonnet';
+
+local containerName = import 'param://containerName';
+
+efk.parts.fluentd.sidecar(containerName)
diff --git a/pkg/registry/testdata/incubator/mariadb/README.md b/pkg/registry/testdata/incubator/mariadb/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a0ef8fc30fca51304ee8c401654f3bd898eae49d
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mariadb/README.md
@@ -0,0 +1,71 @@
+# mariadb
+
+> MariaDB is an open source relational database it provides a SQL interface for accessing data. The latest versions of MariaDB also include GIS and JSON features. This package deploys a maria container, a service and secret to your cluster
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.stateless-maria](#io.ksonnet.pkg.stateless-maria)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.simple-mariadb` prototype to generate Kubernetes YAML for mariadb, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.simple-mariadb mariadb \
+  --name mariadb \
+  --namespace default \
+  --mariaRootPassword boot
+
+# Apply to server.
+$ ks apply -f mariadb.jsonnet
+```
+
+## Using the library
+
+The library files for mariadb define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure mariadb for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of mariadb, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.stateless-maria
+
+Deploy stateless instance of MariaDB. This is NOT backed by a persistent volume.The MariaDB container is deployed using a deployment and exposed to the
+network as a service. The password is stored as a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.stateless-maria mariadb \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE \
+  --mariaRootPassword YOUR_MARIAROOTPASSWORD_HERE
+```
+
+Below is the Jsonnet file generated by this command.
+
+```
+// mariadb.jsonnet
+<JSONNET HERE>
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace in which to put the application [string]
+* `--name=<name>`: Metadata name for each of the deployment components [string]
+* `--mariaRootPassword=<mariaRootPassword>`: Password for root user [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/mariadb/examples/generated.yaml b/pkg/registry/testdata/incubator/mariadb/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8575dee77b9bdc6f9aa1d0843ecd73fa763100af
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mariadb/examples/generated.yaml
@@ -0,0 +1,146 @@
+---
+apiVersion: v1
+data:
+  my.cnf: |
+    [mysqld]
+    innodb_buffer_pool_size=2G
+kind: ConfigMap
+metadata:
+  labels:
+    app: mariadb-app
+  name: mariadb-app
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: mariadb-app
+  name: mariadb-app
+spec:
+  template:
+    metadata:
+      labels:
+        app: mariadb-app
+    spec:
+      containers:
+      - env:
+        - name: MARIADB_ROOT_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: mariadb-root-password
+              name: mariadb-app
+        - name: MARIADB_USER
+          value: ""
+        - name: MARIADB_DATABASE
+          value: ""
+        image: bitnami/mariadb:10.1.26-r2
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          exec:
+            command:
+            - mysqladmin
+            - ping
+          initialDelaySeconds: 30
+          timeoutSeconds: 5
+        name: mariadb
+        ports:
+        - containerPort: 3306
+          name: mysql
+        readinessProbe:
+          exec:
+            command:
+            - mysqladmin
+            - ping
+          initialDelaySeconds: 5
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 250m
+            memory: 256Mi
+        volumeMounts:
+        - mountPath: /bitnami/mariadb/conf/my_custom.cnf
+          name: config
+          subPath: my.cnf
+        - mountPath: /bitnami/mariadb
+          name: data
+      - command:
+        - sh
+        - -c
+        - DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter
+        env:
+        - name: MARIADB_ROOT_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: mariadb-root-password
+              name: mariadb-app
+        image: prom/mysqld-exporter:v0.10.0
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          httpGet:
+            path: /metrics
+            port: metrics
+          initialDelaySeconds: 15
+          timeoutSeconds: 5
+        name: metrics
+        ports:
+        - containerPort: 9104
+          name: metrics
+        readinessProbe:
+          httpGet:
+            path: /metrics
+            port: metrics
+          initialDelaySeconds: 5
+          timeoutSeconds: 1
+        resources: {}
+      volumes:
+      - configMap:
+          name: mariadb-app
+        name: config
+      - name: data
+        persistentVolumeClaim:
+          claimName: mariadb-app
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: mariadb-app
+  name: mariadb-app
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 8Gi
+---
+apiVersion: v1
+data:
+  mariadb-password: Ym9vdHM=
+  mariadb-root-password: YWxzb2Jvb3Rz
+kind: Secret
+metadata:
+  labels:
+    app: mariadb-app
+  name: mariadb-app
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    prometheus.io/port: "9104"
+    prometheus.io/scrape: "true"
+  labels:
+    app: mariadb-app
+  name: mariadb-app
+spec:
+  ports:
+  - name: mysql
+    port: 3306
+    targetPort: mysql
+  - name: metrics
+    port: 9104
+    targetPort: metrics
+  selector:
+    app: mariadb-app
+  type: ClusterIP
\ No newline at end of file
diff --git a/pkg/registry/testdata/incubator/mariadb/examples/maria.jsonnet b/pkg/registry/testdata/incubator/mariadb/examples/maria.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..c98e7e9b43a9dfd724a5beec7a18e14304138dc9
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mariadb/examples/maria.jsonnet
@@ -0,0 +1,10 @@
+local k = import 'k.libsonnet';
+local maria = import '../maria.libsonnet';
+
+k.core.v1.list.new([
+  maria.parts.configMap("dev-hoot", "mariadb-app"),
+  maria.parts.deployment.persistent("dev-hoot", "mariadb-app", "passwordSecret"),
+  maria.parts.pvc("dev-hoot", "mariadb-app"),
+  maria.parts.secret("dev-hoot", "mariadb-app", "mariaRootPassword", "mariadbPassword"),
+  maria.parts.svc("dev-hoot", "mariadb-app")
+])
diff --git a/pkg/registry/testdata/incubator/mariadb/maria.libsonnet b/pkg/registry/testdata/incubator/mariadb/maria.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..6447bd78c3a9798665a20149b3183cd46f92a02e
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mariadb/maria.libsonnet
@@ -0,0 +1,307 @@
+local k = import 'k.libsonnet';
+local deployment = k.extensions.v1beta1.deployment;
+
+{
+  parts:: {
+    svc(namespace, name, metricsEnabled=true, labels={app:name}, selector={app:name})::
+      {
+        apiVersion: "v1",
+        kind: "Service",
+        metadata: {
+          name: name,
+          labels: labels,
+          [if metricsEnabled then "annotations"]: {
+            "prometheus.io/scrape": "true",
+            "prometheus.io/port": "9104",
+          }
+        },
+        spec: {
+          type: "ClusterIP",
+          ports: [
+            {
+              name: "mysql",
+              port: 3306,
+              targetPort: "mysql",
+            },
+          ] + if metricsEnabled then [
+            {
+              name: "metrics",
+              port: 9104,
+              targetPort: "metrics",
+            },
+          ] else [],
+          selector: selector,
+        },
+      },
+
+    secret(namespace, name, mariaRootPassword, labels={app:name},):: {
+      apiVersion: "v1",
+      kind: "Secret",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: labels,
+      },
+      type: "Opaque",
+      data: {
+         "mariadb-root-password": std.base64(mariaRootPassword),
+      },
+    },
+
+    configMap(namespace, name, labels={app:name})::
+      local config = |||
+        [mysqld]
+        innodb_buffer_pool_size=2G
+      |||;
+      {
+        apiVersion: "v1",
+        kind: "ConfigMap",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: labels,
+          },
+        data: {
+          "my.cnf": config,
+        },
+      },
+
+    pvc(namespace, name, storageClassName="-", labels={app:name})::
+      local defaults = {
+        accessMode: "ReadWriteOnce",
+        size: "8Gi"
+      };
+      {
+        kind: "PersistentVolumeClaim",
+        apiVersion: "v1",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: labels,
+        },
+        spec: {
+          accessModes: [
+            defaults.accessMode,
+          ],
+          resources: {
+            requests: {
+              storage: defaults.size,
+            },
+          },
+          [if storageClassName != null then "storageClass"]:storageClassName,
+        },
+      },
+
+    deployment:: {
+      local defaults = {
+        image: "bitnami/mariadb:10.1.26-r2",
+        imagePullPolicy: "IfNotPresent",
+        serviceType: "ClusterIP",
+        persistence: {
+          accessMode: "ReadWriteOnce",
+          size: "8Gi",
+        },
+        resources: {
+          requests: {
+            memory: "256Mi",
+            cpu: "250m",
+          },
+        },
+        metrics: {
+          image: "prom/mysqld-exporter",
+          imageTag: "v0.10.0",
+          imagePullPolicy: "IfNotPresent",
+          resources: {},
+          annotations: {
+            "prometheus.io/scrape": "true",
+            "prometheus.io/port": "9104",
+          },
+        },
+        mariaConfig: {
+          user: "",
+          db: "",
+        },
+      },
+
+      persistent(namespace, name, passwordSecretName, mariaConfig=defaults.mariaConfig, metricsEnabled=true, existingClaim=name, labels={app:name}, configMapName=name)::
+        local volume = {
+          name: "data",
+          persistentVolumeClaim: {
+            claimName: existingClaim
+          }
+        };
+        base(namespace, name, passwordSecretName, mariaConfig, metricsEnabled, existingClaim, labels, configMapName) +
+          deployment.mixin.spec.template.spec.withVolumes(volume),
+
+      nonPersistent(namespace, name, passwordSecretName, mariaConfig=defaults.mariaConfig, metricsEnabled=true, existingClaim=name, labels={app:name}, configMapName=name)::
+         base(namespace, name, passwordSecretName, mariaConfig, metricsEnabled, existingClaim, labels, configMapName),
+
+      local secure(passwordSecretName) = [
+        {
+          name: "MARIADB_ROOT_PASSWORD",
+          valueFrom: {
+            secretKeyRef: {
+              name: passwordSecretName,
+              key: "mariadb-root-password",
+            },
+          },
+        },
+        {
+          name: "MARIADB_PASSWORD",
+          valueFrom: {
+            secretKeyRef: {
+              name: passwordSecretName,
+              key: "mariadb-password",
+            },
+          },
+        },
+      ],
+
+      local insecure(passwordSecretName) = [
+        {
+          name: "ALLOW_EMPTY_PASSWORD",
+          value: "yes",
+        },
+        {
+          name: "MARIADB_PASSWORD",
+          valueFrom: {
+            secretKeyRef: {
+              name: passwordSecretName,
+              key: "mariadb-password",
+            },
+          },
+        }
+      ],
+
+      local base(namespace, name, passwordSecretName, mariaConfig, metricsEnabled, existingClaim, labels, configMapName) =
+        local metricsContainer =
+          if !metricsEnabled then []
+          else [
+            {
+              name: "metrics",
+              image: "%s:%s" % [defaults.metrics.image, defaults.metrics.imageTag],
+              imagePullPolicy: defaults.metrics.imagePullPolicy,
+              env: [
+                {
+                  name: "MARIADB_ROOT_PASSWORD",
+                  valueFrom: {
+                    secretKeyRef: {
+                      name: name,
+                      key: "mariadb-root-password",
+                    },
+                  },
+                },
+              ],
+              command: [ 'sh', '-c', 'DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter' ],
+              ports: [
+                {
+                  name: "metrics",
+                  containerPort: 9104,
+                },
+              ],
+              livenessProbe: {
+                httpGet: {
+                  path: "/metrics",
+                  port: "metrics",
+                },
+                initialDelaySeconds: 15,
+                timeoutSeconds: 5,
+              },
+              readinessProbe: {
+                httpGet: {
+                  path: "/metrics",
+                  port: "metrics",
+                },
+                initialDelaySeconds: 5,
+                timeoutSeconds: 1,
+              },
+              resources: defaults.metrics.resources,
+            },
+          ];
+
+        {
+          apiVersion: "extensions/v1beta1",
+          kind: "Deployment",
+          metadata: {
+            name: name,
+            namespace: namespace,
+            labels: labels,
+          },
+          spec: {
+            template: {
+              metadata: {
+                namespace: namespace,
+                labels: labels,
+              },
+              spec: {
+                containers: [
+                  {
+                    name: "mariadb",
+                    image: defaults.image,
+                    imagePullPolicy: defaults.imagePullPolicy,
+                    env:
+                      secure(passwordSecretName) + [
+                        {
+                          name: "MARIADB_USER",
+                          value: mariaConfig.user
+                        },
+                        {
+                          name: "MARIADB_DATABASE",
+                          value:  mariaConfig.db
+                        },
+                      ],
+                    ports: [
+                      {
+                        name: "mysql",
+                        containerPort: 3306,
+                      },
+                    ],
+                    livenessProbe: {
+                      exec: {
+                        command: [
+                          "mysqladmin",
+                          "ping",
+                        ],
+                      },
+                      initialDelaySeconds: 30,
+                      timeoutSeconds: 5,
+                    },
+                    readinessProbe: {
+                      exec: {
+                        command: [
+                          "mysqladmin",
+                          "ping",
+                        ],
+                      },
+                      initialDelaySeconds: 5,
+                      timeoutSeconds: 1,
+                    },
+                    resources: defaults.resources,
+                    volumeMounts: [
+                      {
+                        name: "config",
+                        mountPath: "/bitnami/mariadb/conf/my_custom.cnf",
+                        subPath: "my.cnf",
+                      },
+                      {
+                        name: "data",
+                        mountPath: "/bitnami/mariadb",
+                      },
+                    ],
+                  },
+                ] + metricsContainer,
+                volumes: [
+                  {
+                    name: "config",
+                    configMap: {
+                      name: configMapName,
+                    },
+                  }
+                ],
+              },
+            },
+          },
+        },
+    },
+  }
+}
diff --git a/pkg/registry/testdata/incubator/mariadb/parts.yaml b/pkg/registry/testdata/incubator/mariadb/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d17c772d4451d69cedc1195db91879ef14a7d5c8
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mariadb/parts.yaml
@@ -0,0 +1,41 @@
+{
+  "name": "mariadb",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "MariaDB is an open source relational database it provides a SQL interface for accessing data. The latest versions of MariaDB also include GIS and JSON features. This package deploys a maria container, a service and secret to your cluster",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "mariadb",
+    "database",
+    "relational"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.simple-mariadb",
+    "componentName": "mariadb",
+    "flags": {
+      "name": "mariadb",
+      "namespace": "default",
+      "mariaRootPassword": "boot"
+    },
+    "comment": "Run a simple mariadb database"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/mariadb/prototypes/maria-stateless.jsonnet b/pkg/registry/testdata/incubator/mariadb/prototypes/maria-stateless.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..4ce9ea3eaf4faff05b34712ca8bc8e6e0d11e7af
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mariadb/prototypes/maria-stateless.jsonnet
@@ -0,0 +1,22 @@
+// @apiVersion 0.1
+// @name io.ksonnet.pkg.stateless-maria
+// @description Deploy stateless instance of MariaDB. This is NOT backed by a persistent volume.
+//   The MariaDB container is deployed using a deployment and exposed to the
+//   network as a service. The password is stored as a secret.
+// @shortDescription A simple, stateless MariaDB deployment.
+// @param namespace string Namespace in which to put the application
+// @param name string Metadata name for each of the deployment components
+// @param mariaRootPassword string Password for root user
+
+local k = import 'k.libsonnet';
+local maria = import 'incubator/mariadb/maria.libsonnet';
+
+local namespace = import 'param://namespace';
+local name = import 'param://name';
+local mariaRootPassword = import 'param://mariaRootPassword';
+
+k.core.v1.list.new([
+  maria.parts.deployment.nonPersistent(namespace, name, name),
+  maria.parts.secret(namespace, name, mariaRootPassword),
+  maria.parts.svc(namespace, name)
+  ])
diff --git a/pkg/registry/testdata/incubator/memcached/README.md b/pkg/registry/testdata/incubator/memcached/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e9f60473f4090923e0584b4dfdeb30c23e8b3c82
--- /dev/null
+++ b/pkg/registry/testdata/incubator/memcached/README.md
@@ -0,0 +1,68 @@
+# memcached
+
+> Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering.
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.memcached-simple](#io.ksonnet.pkg.memcached-simple)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.memcached-simple` prototype to generate Kubernetes YAML for memcached, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.memcached-simple memcached \
+  --name memcached \
+  --namespace default
+
+# Apply to server.
+$ ks apply -f memcached.jsonnet
+```
+
+## Using the library
+
+The library files for memcached define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure memcached for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of memcached, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.memcached-simple
+
+Deploys Memcached on a your Kubernetes cluster through a stateful set with 3replicas, pod distribution budget (pdb), and service. Memcached
+can be accessed via port 11211 within the cluster.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.memcached-simple memcached \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE
+```
+
+Below is the Jsonnet file generated by this command.
+
+```
+// memcached.jsonnet
+<JSONNET HERE>
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace in which to put the application [string]
+* `--name=<name>`: Name to give to each of the components [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/memcached/examples/generated.yaml b/pkg/registry/testdata/incubator/memcached/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..304dd9f8167ce21e7f93698401ccbf4a2901599d
--- /dev/null
+++ b/pkg/registry/testdata/incubator/memcached/examples/generated.yaml
@@ -0,0 +1,76 @@
+---
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: memcached
+  namespace: dev-alex
+spec:
+  minAvailable: 3
+  selector:
+    matchLabels:
+      app: memcached
+---
+apiVersion: apps/v1beta1
+kind: StatefulSet
+metadata:
+  labels:
+    app: memcached
+  name: memcached
+  namespace: dev-alex
+spec:
+  replicas: 3
+  serviceName: memcached
+  template:
+    metadata:
+      labels:
+        app: memcached
+    spec:
+      affinity:
+        podAntiAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchLabels:
+                app: memcached
+            topologyKey: kubernetes.io/hostname
+      containers:
+      - command:
+        - memcached
+        - -m 64
+        - -o
+        - modern
+        image: memcached:1.4.36-alpine
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          initialDelaySeconds: 30
+          tcpSocket:
+            port: memcache
+          timeoutSeconds: 5
+        name: memcached
+        ports:
+        - containerPort: 11211
+          name: memcache
+        readinessProbe:
+          initialDelaySeconds: 5
+          tcpSocket:
+            port: memcache
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 50m
+            memory: 64Mi
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: memcached
+  name: memcached
+  namespace: dev-alex
+spec:
+  clusterIP: None
+  ports:
+  - name: memcache
+    port: 11211
+    targetPort: memcache
+  selector:
+    app: memcached
diff --git a/pkg/registry/testdata/incubator/memcached/examples/memcached.jsonnet b/pkg/registry/testdata/incubator/memcached/examples/memcached.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..20455975e89ef165f698ec0f720bf2462af589bd
--- /dev/null
+++ b/pkg/registry/testdata/incubator/memcached/examples/memcached.jsonnet
@@ -0,0 +1,11 @@
+local k = import 'k.libsonnet';
+local memcached = import '../memcached.libsonnet';
+
+local myNamespace = "dev-alex";
+local appName = "memcached";
+
+k.core.v1.list.new([
+  memcached.parts.pbd(myNamespace, appName),
+  memcached.parts.statefulset.withHardAntiAffinity(myNamespace, appName),
+  memcached.parts.service(myNamespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/memcached/memcached.libsonnet b/pkg/registry/testdata/incubator/memcached/memcached.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..c0e93815649c8f3bed349c829a581b61d2e28dd9
--- /dev/null
+++ b/pkg/registry/testdata/incubator/memcached/memcached.libsonnet
@@ -0,0 +1,149 @@
+local k = import 'k.libsonnet';
+
+{
+  parts:: {
+    pdb(namespace, name, pdbMinAvailable=3, selector={matchLabels: {app: name}}):: {
+      apiVersion: "policy/v1beta1",
+      kind: "PodDisruptionBudget",
+      metadata: {
+        namespace: namespace,
+        name: name,
+      },
+      spec: {
+        selector: selector,
+        minAvailable: pdbMinAvailable,
+      },
+    },
+
+    statefulset:: {
+      local defaults = {
+        replicaCount: 3,
+        resources: {
+          requests: {
+            memory: "64Mi",
+            cpu: "50m",
+          },
+        },
+        image: "memcached:1.4.36-alpine",
+        imagePullPolicy: "IfNotPresent",
+        // imagePullPolicy: change to "Always" if the imageTag is "latest"
+        memcached: {
+          maxItemMemory: 64,
+          verbosity: "v",
+          extendedOptions: "modern",
+        },
+      },
+
+      withHardAntiAffinity(namespace, name, labels={app: name})::
+        local hardAntiAffinity = {
+          requiredDuringSchedulingIgnoredDuringExecution: [
+            {
+              topologyKey: "kubernetes.io/hostname",
+              labelSelector: { matchLabels: labels },
+            },
+          ],
+        };
+        base(namespace, name, labels) +
+        k.apps.v1beta1.statefulSet.mixin.spec.template.spec.affinity.podAntiAffinity.mixinInstance(hardAntiAffinity),
+
+      withSoftAntiAffinity(namespace, name, labels={app: name})::
+        local softAntiAffinity = {
+          preferredDuringSchedulingIgnoredDuringExecution: [
+            {
+              weight: 5,
+              podAffinityTerm: [
+                {
+                  topologyKey: "kubernetes.io/hostname",
+                  labelSelector: { matchLabels: labels },
+                },
+              ],
+            },
+          ],
+        };
+        base(namespace, name, labels) +
+        k.apps.v1beta1.statefulSet.mixin.spec.template.spec.affinity.podAntiAffinity.mixinInstance(softAntiAffinity),
+
+      withNoAntiAffinity(namespace, name, labels={app: name})::
+        base(namespace, name, labels),
+
+      local base(namespace, name, labels) = {
+        apiVersion: "apps/v1beta1",
+        kind: "StatefulSet",
+        metadata: {
+          namespace: namespace,
+          name: name,
+          labels: labels,
+        },
+        spec: {
+          serviceName: name,
+          replicas: defaults.replicaCount,
+          template: {
+            metadata: {
+              labels: labels
+            },
+            spec: {
+              affinity: {
+                podAntiAffinity: {}
+              },
+              containers: [
+                {
+                  name: name,
+                  image: defaults.image,
+                  imagePullPolicy: defaults.imagePullPolicy,
+                  command: [
+                    "memcached",
+                    "-m " + defaults.memcached.maxItemMemory
+                  ] +
+                    if "extendedOptions" in defaults.memcached
+                    then [ "-o", defaults.memcached.extendedOptions ]
+                    else [] +
+                    if "verbosity" in defaults.memcached
+                    then [ defaults.memcached.verbosity ]
+                    else [],
+                  ports: [
+                    {
+                      name: "memcache",
+                      containerPort: 11211,
+                    }
+                  ],
+                  livenessProbe: {
+                    tcpSocket: { port: "memcache" },
+                    initialDelaySeconds: 30,
+                    timeoutSeconds: 5,
+                  },
+                  readinessProbe: {
+                    tcpSocket: { port: "memcache" },
+                    initialDelaySeconds: 5,
+                    timeoutSeconds: 1,
+                  },
+                  resources: defaults.resources,
+                },
+              ],
+            },
+          },
+        },
+      },
+    },
+
+    service(namespace, name, selector={app: name}):: {
+      apiVersion: "v1",
+      kind: "Service",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: { app: name },
+      },
+      spec: {
+        clusterIP: "None",
+        ports: [
+          {
+            name: "memcache",
+            port: 11211,
+            targetPort: "memcache"
+          },
+        ],
+        selector: selector,
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/memcached/parts.yaml b/pkg/registry/testdata/incubator/memcached/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..842107024ead0e17688ed0fc254a2cf73ab6fe9f
--- /dev/null
+++ b/pkg/registry/testdata/incubator/memcached/parts.yaml
@@ -0,0 +1,40 @@
+{
+  "name": "memcached",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering.",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "memcached",
+    "database",
+    "cache"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.memcached-simple",
+    "componentName": "memcached",
+    "flags": {
+      "name": "memcached",
+      "namespace": "default"
+    },
+    "comment": "Run a simple memcached instance"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/memcached/prototypes/memcached-simple.jsonnet b/pkg/registry/testdata/incubator/memcached/prototypes/memcached-simple.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..cacf7e2431ad749ebd8027a979c08ea78d3b6089
--- /dev/null
+++ b/pkg/registry/testdata/incubator/memcached/prototypes/memcached-simple.jsonnet
@@ -0,0 +1,22 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.memcached-simple
+// @description Deploys Memcached on a your Kubernetes cluster through a stateful set with 3
+//   replicas, pod distribution budget (pdb), and service. Memcached
+//   can be accessed via port 11211 within the cluster.
+// @shortDescription Simple Memcached instance with 3 replicas.
+// @param namespace string Namespace in which to put the application
+// @param name string Name to give to each of the components
+
+// TODO: Add MaxItemMemory=64 as a param like the k8s/charts?
+
+local k = import 'k.libsonnet';
+local memcached = import 'incubator/memcached/memcached.libsonnet';
+
+local namespace = import 'param://namespace';
+local appName = import 'param://name';
+
+k.core.v1.list.new([
+  memcached.parts.pdb(namespace, appName),
+  memcached.parts.statefulset.withHardAntiAffinity(namespace, appName),
+  memcached.parts.service(namespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/mongodb/README.md b/pkg/registry/testdata/incubator/mongodb/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..75a7b7c62f48e140e5345524f46ead0d9bba7309
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mongodb/README.md
@@ -0,0 +1,67 @@
+# mongodb
+
+> MongoDB is a cross-platform document-oriented database. Classified as a NoSQL database, MongoDB eschews the traditional table-based relational database structure in favor of JSON-like documents with dynamic schemas, making the integration of data in certain types of applications easier and faster.
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.mongodb-simple](#io.ksonnet.pkg.mongodb-simple)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.mongodb-simple` prototype to generate Kubernetes YAML for mongodb, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.mongodb-simple mongo \
+  --rootPassword boots \
+  --password boots \
+  --name mongodb \
+  --namespace default
+
+# Apply to server.
+$ ks apply -f mongo.jsonnet
+```
+
+## Using the library
+
+The library files for mongodb define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure mongodb for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of mongodb, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.mongodb-simple
+
+Deploys a simple instance of mongodb, backed by a persistent volume claim. The mongodb container is deployed using a Kubernetes deployment, and exposed
+to the network using a service. Passwords are stored in a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.mongodb-simple mongo \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE \
+  --rootPassword YOUR_ROOTPASSWORD_HERE \
+  --password YOUR_PASSWORD_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace to specify destination in cluster; default is 'default' [string]
+* `--name=<name>`: Name of app to attach as identifier to all components [string]
+* `--rootPassword=<rootPassword>`: RootPassword for db admin password [string]
+* `--password=<password>`: Password for db user password [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/mongodb/examples/generated.yaml b/pkg/registry/testdata/incubator/mongodb/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..17259f17e636afc4ca0708a28b0f7a9d49e90ccb
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mongodb/examples/generated.yaml
@@ -0,0 +1,98 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: mongo
+  name: mongo
+spec:
+  template:
+    metadata:
+      labels:
+        app: mongo
+    spec:
+      containers:
+      - env:
+        - name: MONGODB_ROOT_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: mongodb-root-password
+              name: mongo
+        - name: MONGODB_USERNAME
+          value: ""
+        - name: MONGODB_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: mongodb-password
+              name: mongo
+        - name: MONGODB_DATABASE
+          value: ""
+        image: bitnami/mongodb:3.4.7-r0
+        livenessProbe:
+          exec:
+            command:
+            - mongo
+            - --eval
+            - db.adminCommand('ping')
+          initialDelaySeconds: 30
+          timeoutSeconds: 5
+        name: mongo
+        ports:
+        - containerPort: 27017
+          name: mongodb
+        readinessProbe:
+          exec:
+            command:
+            - mongo
+            - --eval
+            - db.adminCommand('ping')
+          initialDelaySeconds: 5
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 100m
+            memory: 256Mi
+        volumeMounts:
+        - mountPath: /bitnami/mongodb
+          name: data
+      volumes:
+      - name: data
+        persistentVolumeClaim:
+          claimName: mongo
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: mongo
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 8Gi
+---
+apiVersion: v1
+data:
+  mongodb-password: YmFy
+  mongodb-root-password: Zm9vYmFy
+kind: Secret
+metadata:
+  labels:
+    app: mongo
+  name: mongo
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: mongo
+  name: mongo
+spec:
+  ports:
+  - name: mongodb
+    port: 27017
+    targetPort: mongodb
+  selector:
+    app: mongo
+  type: ClusterIP
diff --git a/pkg/registry/testdata/incubator/mongodb/examples/mongodb.jsonnet b/pkg/registry/testdata/incubator/mongodb/examples/mongodb.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..10fe1b1d08c698d757ca8c54386a8453b6f92ae7
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mongodb/examples/mongodb.jsonnet
@@ -0,0 +1,15 @@
+local k = import 'k.libsonnet';
+local mongo = import '../mongodb.libsonnet';
+
+local namespace = "dev-alex";
+local appName = "mongo";
+local rootPassword = "foobar";
+local password = "bar";
+
+
+k.core.v1.list.new([
+  mongo.parts.deployment.persistent(namespace, appName),
+  mongo.parts.pvc(namespace, appName),
+  mongo.parts.secrets(namespace, appName, rootPassword, password),
+  mongo.parts.service(namespace, appName),
+])
diff --git a/pkg/registry/testdata/incubator/mongodb/mongodb.libsonnet b/pkg/registry/testdata/incubator/mongodb/mongodb.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..7daac00c042a6a876982cbfdf6312d63061156b6
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mongodb/mongodb.libsonnet
@@ -0,0 +1,182 @@
+local k = import 'k.libsonnet';
+local deployment = k.extensions.v1beta1.deployment;
+
+{
+  parts:: {
+    deployment:: {
+      local defaults = {
+        image: "bitnami/mongodb:3.4.7-r0",
+        imagePullPolicy: "IfNotPresent",
+        resources: {
+          requests: {
+            memory: "256Mi",
+            cpu: "100m",
+          },
+        },
+
+        persistence: {
+          enabled: true,
+          storageClass: "-",
+          accessMode: "ReadWriteOnce",
+          size: "8Gi",
+        },
+
+        mongoConfig: {
+          username: "",
+          database: "",
+        },
+      },
+
+      persistent(namespace, name, mongoConfig=defaults.mongoConfig,  labels={app: name}, pvcName={claimName: name})::
+        base(namespace, name, mongoConfig, labels)
+        .withVolumes({
+          name: "data",
+          persistentVolumeClaim: pvcName,
+        }),
+
+      nonPersistent(namespace, name, labels={app: name}, mongoConfig=defaults.mongoConfig)::
+        base(namespace, name, mongoConfig, labels)
+        .withVolumes({
+          name: "data",
+          emptyDir: {},
+        }),
+
+      local base(namespace, name, mongoConfig, labels) = {
+        apiVersion: "extensions/v1beta1",
+        kind: "Deployment",
+        metadata: {
+          namespace: namespace,
+          name: name,
+          labels: labels,
+        },
+        spec: {
+          template: {
+            metadata: { labels: labels },
+            spec: {
+              containers: [{
+                name: name,
+                image: defaults.image,
+                imagePullPolicy: defaults.imagePullPolicy,
+                env: [
+                  {
+                    name: "MONGODB_ROOT_PASSWORD",
+                    valueFrom: {
+                      secretKeyRef: {
+                        name: name,
+                        key: "mongodb-root-password",
+                      },
+                    },
+                  },
+                  {
+                    name: "MONGODB_USERNAME",
+                    value: mongoConfig.username,
+                  },
+                  {
+                    name: "MONGODB_PASSWORD",
+                    valueFrom: {
+                      secretKeyRef: {
+                        name: name,
+                        key: "mongodb-password",
+                      },
+                    },
+                  },
+                  {
+                    name: "MONGODB_DATABASE",
+                    value: mongoConfig.database,
+                  },
+                ],
+                ports: [{
+                  name: "mongodb",
+                  containerPort: 27017,
+                }],
+                livenessProbe: {
+                  exec: {
+                    command: [
+                      "mongo",
+                      "--eval",
+                      "db.adminCommand('ping')",
+                    ],
+                  },
+                  initialDelaySeconds: 30,
+                  timeoutSeconds: 5,
+                },
+                readinessProbe: {
+                  exec: {
+                    command: [
+                      "mongo",
+                      "--eval",
+                      "db.adminCommand('ping')",
+                    ],
+                  },
+                  initialDelaySeconds: 5,
+                  timeoutSeconds: 1,
+                },
+                volumeMounts: [{
+                  name: "data",
+                  mountPath: "/bitnami/mongodb",
+                }],
+                resources: defaults.resources,
+              }],
+            },
+          },
+        },
+      },
+    },
+
+    secrets(namespace, name, mongodbRootPassword, mongodbPassword):: {
+      apiVersion: "v1",
+      kind: "Secret",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: { app: name },
+      },
+      type: "Opaque",
+      data: {
+        "mongodb-root-password": std.base64(mongodbRootPassword),
+        "mongodb-password": std.base64(mongodbPassword)
+      },
+    },
+
+    service(namespace, name, serviceType="ClusterIP", selector={app: name}):: {
+      apiVersion: "v1",
+      kind: "Service",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: { app: name },
+      },
+      spec: {
+        type: serviceType,
+        ports: [{
+          name: "mongodb",
+          port: 27017,
+          targetPort: "mongodb",
+        }],
+        selector: selector,
+      },
+    },
+
+    pvc(namespace, name, storageClass="-"):: {
+      local defaults = {
+        size: "8Gi",
+        accessMode: "ReadWriteOnce",
+        enabled: true,
+      },
+
+      kind: "PersistentVolumeClaim",
+      apiVersion: "v1",
+      metadata: {
+        namespace: namespace,
+        name: name,
+      },
+      spec: {
+        accessModes: [ defaults.accessMode ],
+        resources: {
+          requests: { storage: defaults.size },
+        },
+        storageClass: storageClass,
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/mongodb/parts.yaml b/pkg/registry/testdata/incubator/mongodb/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..16632ad6ecbf678fbbabf1a2078de9c83fc4d30f
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mongodb/parts.yaml
@@ -0,0 +1,43 @@
+{
+  "name": "mongodb",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "MongoDB is a cross-platform document-oriented database. Classified as a NoSQL database, MongoDB eschews the traditional table-based relational database structure in favor of JSON-like documents with dynamic schemas, making the integration of data in certain types of applications easier and faster.",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "mongodb",
+    "database",
+    "mongo",
+    "nosql"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.mongodb-simple",
+    "componentName": "mongo",
+    "flags": {
+      "name": "mongodb",
+      "namespace": "default",
+      "rootPassword": "boots",
+      "password": "boots"
+    },
+    "comment": "Run a simple instance of MongoDB"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/mongodb/prototypes/mongodb-simple.jsonnet b/pkg/registry/testdata/incubator/mongodb/prototypes/mongodb-simple.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..501414893365ae75deb0f2965a48ddf8acedf87e
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mongodb/prototypes/mongodb-simple.jsonnet
@@ -0,0 +1,25 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.mongodb-simple
+// @description Deploys a simple instance of mongodb, backed by a persistent volume claim. The
+//   mongodb container is deployed using a Kubernetes deployment, and exposed
+//   to the network using a service. Passwords are stored in a secret.
+// @shortDescription A simple MongoDB deployment, backed by persistent storage.
+// @param namespace string Namespace to specify destination in cluster; default is 'default'
+// @param name string Name of app to attach as identifier to all components
+// @param rootPassword string RootPassword for db admin password
+// @param password string Password for db user password
+
+local k = import 'k.libsonnet';
+local mongo = import 'incubator/mongodb/mongodb.libsonnet';
+
+local namespace = import 'param://namespace/';
+local appName = import 'param://name';
+local rootPassword = import 'param://rootPassword';
+local password = import 'param://password';
+
+k.core.v1.list.new([
+  mongo.parts.deployment.persistent(namespace, appName),
+  mongo.parts.pvc(namespace, appName),
+  mongo.parts.secrets(namespace, appName, rootPassword, password),
+  mongo.parts.service(namespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/mysql/README.md b/pkg/registry/testdata/incubator/mysql/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bf300afefab98c9dd1ba38f60a80595a6118fba2
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mysql/README.md
@@ -0,0 +1,65 @@
+# mysql
+
+> MySQL is one of the most popular database servers in the world. Notable users include Wikipedia, Facebook and Google.
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.simple-mysql](#io.ksonnet.pkg.simple-mysql)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.simple-mysql` prototype to generate Kubernetes YAML for mysql, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.simple-mysql mysql \
+  --name mysql \
+  --namespace default
+
+# Apply to server.
+$ ks apply -f mysql.jsonnet
+```
+
+## Using the library
+
+The library files for mysql define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure mysql for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of mysql, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.simple-mysql
+
+Deploys MySQL backed by a persistent volume. The MySQL container is deployed using a deployment and exposed to the network with a service. The
+passwords are stored in a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.simple-mysql mysql \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE \
+  --mysqlRootPassword YOUR_MYSQLROOTPASSWORD_HERE \
+  --mysqlPassword YOUR_MYSQLPASSWORD_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace in which to put the application [string]
+* `--name=<name>`: Name to give to each of the components [string]
+* `--mysqlRootPassword=<mysqlRootPassword>`: Password for root user [string]
+* `--mysqlPassword=<mysqlPassword>`: Password for new user [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/mysql/examples/generated.yaml b/pkg/registry/testdata/incubator/mysql/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cac246f5dc670ecc4f25d20d32d4b09e2d772794
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mysql/examples/generated.yaml
@@ -0,0 +1,126 @@
+---
+apiVersion: v1
+data:
+  configurationFiles:
+    mysql.cnf: |
+      - [mysqld]
+      skip-name-resolve
+kind: ConfigMap
+metadata:
+  name: mysql
+  namespace: dev-hoot
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: mysql
+  name: mysql
+spec:
+  template:
+    metadata:
+      labels:
+        app: mysql
+        namespace: dev-hoot
+    spec:
+      containers:
+      - env:
+        - name: MYSQL_ROOT_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: mysql-root-password
+              name: mysql
+        - name: MYSQL_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: mysql-password
+              name: mysql
+        - name: MYSQL_USER
+          value: ""
+        - name: MYSQL_DATABASE
+          value: ""
+        image: mysql:5.7.14
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          exec:
+            command:
+            - sh
+            - -c
+            - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}
+          initialDelaySeconds: 30
+          timeoutSeconds: 5
+        name: mysql
+        ports:
+        - containerPort: 3306
+          name: mysql
+        readinessProbe:
+          exec:
+            command:
+            - sh
+            - -c
+            - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}
+          initialDelaySeconds: 5
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 100m
+            memory: 256Mi
+        volumeMounts:
+        - mountPath: /var/lib/mysql
+          name: data
+        - mountPath: /etc/mysql/conf.d
+          name: configurations
+      initContainers:
+      - command:
+        - rm
+        - -fr
+        - /var/lib/mysql/lost+found
+        image: busybox:1.25.0
+        imagePullPolicy: IfNotPresent
+        name: remove-lost-found
+        volumeMounts:
+        - mountPath: /var/lib/mysql
+          name: data
+      volumes:
+      - configMap:
+          name: mysql
+        name: configurations
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: mysql
+  name: mysql
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 8Gi
+---
+apiVersion: v1
+data:
+  mysql-password: Zm9v
+  mysql-root-password: YmFy
+kind: Secret
+metadata:
+  labels:
+    app: mysql
+    namespace: dev-hoot
+  name: mysql
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: mysql
+  name: mysql
+spec:
+  ports:
+  - name: mysql
+    port: 3306
+    targetPort: mysql
+  selector:
+    app: mysql
diff --git a/pkg/registry/testdata/incubator/mysql/examples/mysql.jsonnet b/pkg/registry/testdata/incubator/mysql/examples/mysql.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..caeb5e98e2c1898dce3c9e68802cbb60ca2009be
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mysql/examples/mysql.jsonnet
@@ -0,0 +1,11 @@
+local k = import 'k.libsonnet';
+local mql = import "../mysql.libsonnet";
+
+
+k.core.v1.list.new([
+  mql.parts.configMap("dev-hoot", "mysql"),
+  mql.parts.deployment.persistent("dev-hoot", "mysql", "mysql", "claimName"),
+  mql.parts.pvc("dev-hoot", "mysql"),
+  mql.parts.secret("dev-hoot", "mysql", "foo", "bar"),
+  mql.parts.svc("dev-hoot", "mysql")
+])
diff --git a/pkg/registry/testdata/incubator/mysql/mysql.libsonnet b/pkg/registry/testdata/incubator/mysql/mysql.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..fd59c673864223e2a4ada61646f40526f5e8bca5
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mysql/mysql.libsonnet
@@ -0,0 +1,274 @@
+local k = import 'k.libsonnet';
+
+{
+  parts:: {
+    svc(namespace, name, selector= {app:name}):: {
+      apiVersion: "v1",
+      kind: "Service",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: {
+          app: name
+        },
+      },
+      spec: {
+        ports: [
+          {
+            name: "mysql",
+            port: 3306,
+            targetPort: "mysql",
+          },
+        ],
+        selector: selector
+      },
+    },
+
+    secret(namespace, name, mysqlPassword, mysqlRootPassword):: {
+      apiVersion: "v1",
+      kind: "Secret",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: {
+          app: name,
+        },
+      },
+      type: "Opaque",
+      data: {
+        "mysql-root-password": std.base64(mysqlRootPassword),
+        "mysql-password": std.base64(mysqlPassword),
+      },
+    },
+
+    pvc(namespace, name, storageClassName=null):: {
+      local defaults = {
+       persistence: {
+          enabled: true,
+          accessMode: "ReadWriteOnce",
+          size: "8Gi",
+        },
+      },
+
+      kind: "PersistentVolumeClaim",
+      apiVersion: "v1",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: {
+          app: name
+        },
+      },
+      spec: {
+        accessModes: [
+          defaults.persistence.accessMode,
+        ],
+        resources: {
+          requests: {
+            storage: defaults.persistence.size,
+          },
+        },
+        // I chose this route to avoid passing both storageClass and storageClassName
+        // it does feel a bit hacky and I'm willing to change it
+      [if storageClassName != null then "storageClass"]:
+        storageClassName,
+      },
+    },
+
+    configMap(namespace,name,configurationFiles=
+      {configurationFiles: {
+        "mysql.cnf":
+         |||
+         - [mysqld]
+         skip-name-resolve
+        |||,
+    }}):: {
+        apiVersion: "v1",
+        kind: "ConfigMap",
+        metadata: {
+          name: name,
+          namespace: namespace,
+        },
+        data: configurationFiles,
+      },
+
+    deployment:: {
+      local defaults = {
+        imagePullPolicy: "IfNotPresent",
+        image: "mysql",
+        imageTag: "5.7.14",
+        persistence: {
+          enabled: true,
+          accessMode: "ReadWriteOnce",
+          size: "8Gi",
+        },
+        resources: {
+          requests: {
+            memory: "256Mi",
+            cpu: "100m",
+          },
+        },
+      },
+
+      persistent(namespace, name, secretKeyName, claimName, mysqlUser="", mysqlDatabase="", mysqlAllowEmptyPassword=false, subPath=null, persistenceEnabled=true)::
+        base(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase,  mysqlAllowEmptyPassword, subPath, persistenceEnabled),
+
+      nonpersistent(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase, mysqlAllowEmptyPassword=false, subPath=null, persistenceEnabled=false)::
+        base(namespace, name, secretKeyName,  claimName, mysqlUser, mysqlDatabase, mysqlAllowEmptyPassword, subPath, persistenceEnabled),
+
+      local base(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase,  mysqlAllowEmptyPassword, subPath, persistenceEnabled)= {
+        apiVersion: "extensions/v1beta1",
+          kind: "Deployment",
+          metadata: {
+            name: name,
+            labels: {
+              app: name,
+            },
+          },
+          spec: {
+            template: {
+              metadata: {
+                labels: {
+                  app: name,
+                  namespace: namespace
+                },
+              },
+              spec: {
+                initContainers: [
+                  {
+                    name: "remove-lost-found",
+                    image: "busybox:1.25.0",
+                    // imagePullPolicy: change to "Always" if the imageTag is "latest"
+                    imagePullPolicy: defaults.imagePullPolicy,
+                    command:  ["rm", "-fr", "/var/lib/mysql/lost+found"],
+                    volumeMounts: [
+                      {
+                        name: "data",
+                        mountPath: "/var/lib/mysql",
+                        [if subPath != null then "subPath"]: subPath
+                      },
+                    ]
+                  },
+                ],
+                containers: [
+                  {
+                    name: name,
+                    image: "%s:%s" % [defaults.image, defaults.imageTag],
+                    imagePullPolicy: defaults.imagePullPolicy,
+                    resources: defaults.resources,
+                    env:
+                      if mysqlAllowEmptyPassword then [
+                        {
+                          name: "MYSQL_ALLOW_EMPTY_PASSWORD",
+                          value: "true",
+                        },
+                      ] else [
+                        {
+                          name: "MYSQL_ROOT_PASSWORD",
+                          valueFrom: {
+                            secretKeyRef: {
+                              name: secretKeyName,
+                              key: "mysql-root-password",
+                            }
+                          },
+                        },
+                        {
+                          name: "MYSQL_PASSWORD",
+                          valueFrom: {
+                            secretKeyRef: {
+                              name: secretKeyName,
+                              key: "mysql-password",
+                            },
+                          },
+                        },
+                      ] + [
+                        {
+                          name: "MYSQL_USER",
+                          value: if mysqlUser != null then mysqlUser else "",
+                        },
+                        {
+                          name: "MYSQL_DATABASE",
+                          value: if mysqlDatabase != null then mysqlDatabase else "",
+                        },
+                      ],
+                    ports: [
+                      {
+                        name: "mysql",
+                        containerPort: 3306,
+                      },
+                    ],
+                    livenessProbe: {
+                      exec: {
+                        command:
+                          if mysqlAllowEmptyPassword then [
+                            "mysqladmin",
+                            "ping",
+                          ] else [
+                            "sh",
+                            "-c",
+                            "mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}",
+                          ],
+                      },
+                      initialDelaySeconds: 30,
+                      timeoutSeconds: 5,
+                    },
+                    readinessProbe: {
+                      exec: {
+                        command:
+                          if mysqlAllowEmptyPassword then [
+                            "mysqladmin",
+                            "ping",
+                          ] else [
+                            "sh",
+                            "-c",
+                            "mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}",
+                          ],
+                      },
+                      initialDelaySeconds: 5,
+                      timeoutSeconds: 1,
+                    },
+                    volumeMounts: [
+                      {
+                        name: "data",
+                        mountPath: "/var/lib/mysql",
+                        [if subPath != null then "subPath"]: subPath,
+                      },
+                    ] + if "configurationFiles" != null then [
+                      {
+                        name: "configurations",
+                        mountPath: "/etc/mysql/conf.d",
+                      }
+                    ] else [],
+                  },
+                ],
+                volumes:
+                  if "configurationFiles" != null then [
+                    {
+                      name: "configurations",
+                      configMap: {
+                        name: name,
+                        // using the app name here though this could be expecting the configmap name
+                        // my expectation would be that the config map has the same name
+                      },
+                    },
+                  ] else [] + [
+                    if persistenceEnabled then {
+                      name: "data",
+                      persistentVolumeClaim: {
+                        claimName:
+                          if claimName != null
+                          then claimName
+                          else name,
+                      },
+                    } else {
+                      name: "data",
+                      emptyDir: {},
+                    }
+                  ],
+              },
+            },
+          },
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/mysql/parts.yaml b/pkg/registry/testdata/incubator/mysql/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..14f5ae29f99003a2748ce54f19e4a4571f918f86
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mysql/parts.yaml
@@ -0,0 +1,40 @@
+{
+  "name": "mysql",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "MySQL is one of the most popular database servers in the world. Notable users include Wikipedia, Facebook and Google.",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "mysql",
+    "database",
+    "relational"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.simple-mysql",
+    "componentName": "mysql",
+    "flags": {
+      "name": "mysql",
+      "namespace": "default"
+    },
+    "comment": "Run a simple MySQL server"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/mysql/prototypes/simple-mysql.jsonnet b/pkg/registry/testdata/incubator/mysql/prototypes/simple-mysql.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..d1fc1d1c41e153eb7bc0e34b7f013dc6a2727e2a
--- /dev/null
+++ b/pkg/registry/testdata/incubator/mysql/prototypes/simple-mysql.jsonnet
@@ -0,0 +1,26 @@
+// @apiVersion 0.1
+// @name io.ksonnet.pkg.simple-mysql
+// @description Deploys MySQL backed by a persistent volume. The MySQL container is deployed
+//   using a deployment and exposed to the network with a service. The
+//   passwords are stored in a secret.
+// @shortDescription A simple MySQL deployment, backed by persistent storage.
+// @param namespace string Namespace in which to put the application
+// @param name string Name to give to each of the components
+// @param mysqlRootPassword string Password for root user
+// @param mysqlPassword string Password for new user
+
+local k = import 'k.libsonnet';
+local mysql = import '../mysql.libsonnet';
+
+local namespace = import 'param://namespace';
+local name = import 'param://name';
+local mysqlRootPassword = import 'param://mysqlRootPassword';
+local mysqlPassword = import 'param://mysqlPassword';
+
+k.core.v1.list.new([
+  mysql.parts.configMap(namespace, name),
+  mysql.parts.deployment.persistent(namespace, name, name, name),
+  mysql.parts.pvc(namespace, name),
+  mysql.parts.secret(namespace, name, mysqlPassword, mysqlRootPassword),
+  mysql.parts.svc(namespace, name)
+])
diff --git a/pkg/registry/testdata/incubator/nginx/README.md b/pkg/registry/testdata/incubator/nginx/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..443a766582924420445edf05e08a1e0f30b2f89e
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/README.md
@@ -0,0 +1,82 @@
+# nginx
+
+> Nginx is an open source reverse proxy server that supports HTTP, HTTPS, SMTP, POP3, and IMAP protocols. It can be used as a load balancer, HTTP cache, and web server. It is designed for high concurrency, high performance, and low memory usage.
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.nginx-simple](#io.ksonnet.pkg.nginx-simple)
+  * [io.ksonnet.pkg.nginx-server-block](#io.ksonnet.pkg.nginx-server-block)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.nginx-simple` prototype to generate Kubernetes YAML for nginx, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.nginx-simple nginx \
+  --name nginx \
+  --namespace default
+
+# Apply to server.
+$ ks apply -f nginx.jsonnet
+```
+
+## Using the library
+
+The library files for nginx define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure nginx for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of nginx, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.nginx-simple
+
+Deploys a simple, stateless nginx server. The nginx container is deployed using a Kubernetes deployment, and is exposed to a network with a
+service.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.nginx-simple nginx \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--name=<name>`: Name to give to each of the components [string]
+
+### io.ksonnet.pkg.nginx-server-block
+
+Deploys a simple, stateless nginx server with server blocks (roughly equivalent to nginx virtual hosts). The nginx container is deployed using a
+Kubernetes deployment, and is exposed to a network with a service.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.nginx-server-block nginx \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace in which to put the application [string]
+* `--name=<name>`: Name to give to all components. [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/nginx/examples/generated.yaml b/pkg/registry/testdata/incubator/nginx/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e08c62958f78d2719ae695a3c9e10eec0e6040ad
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/examples/generated.yaml
@@ -0,0 +1,69 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: nginx-app
+  name: nginx-app
+spec:
+  replicas: 1
+  template:
+    metadata
+      labels:
+        app: nginx-app
+    spec:
+      containers:
+        - image: 'bitnami/nginx:1.10.2-r3'
+          imagePullPolicy: IfNotPresent
+          livenessProbe:
+            failureThreshold: 6
+            httpGet:
+              path: /
+              port: http
+            initialDelaySeconds: 30
+            timeoutSeconds: 5
+          name: nginx-app
+          ports:
+            - containerPort: 80
+              name: http
+            - containerPort: 443
+              name: https
+          readinessProbe:
+            httpGet:
+              path: /
+              port: http
+            initialDelaySeconds: 5
+            periodSeconds: 5
+            timeoutSeconds: 3
+          volumeMounts:
+            - mountPath: /bitnami/nginx
+              name: nginx-data
+            - mountPath: /bitnami/nginx/conf/vhosts
+              name: nginx-vhost
+      volumes:
+        - emptyDir: {}
+          name: nginx-data
+        - configMap:
+            name: nginx-app
+          name: nginx-vhost
+---
+apiVersion: v1
+data:
+  vhost.conf: |
+    server {
+      listen 0.0.0.0:80;
+      root /app;
+      location / {
+        index index.html index.php;
+      }
+      location ~ \.php$ {
+        fastcgi_pass phpfpm-server:9000;
+        fastcgi_index index.php;
+        include fastcgi.conf;
+      }
+    }
+kind: ConfigMap
+metadata:
+  labels:
+    app: nginx-app
+  name: nginx-app
diff --git a/pkg/registry/testdata/incubator/nginx/examples/nginx.jsonnet b/pkg/registry/testdata/incubator/nginx/examples/nginx.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..ebd45c79a71c6753ce9fcc36ef63896ef02e2c6b
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/examples/nginx.jsonnet
@@ -0,0 +1,11 @@
+local k = import 'k.libsonnet';
+local nginx = import '../nginx.libsonnet';
+
+local namespace = "dev-alex";
+local appName = "nginx-app";
+
+k.core.v1.list.new([
+  nginx.parts.deployment.withServerBlock(namespace, appName),
+  nginx.parts.service(namespace, appName),
+  nginx.parts.serverBlockConfigMap(namespace, appName),
+])
diff --git a/pkg/registry/testdata/incubator/nginx/nginx.libsonnet b/pkg/registry/testdata/incubator/nginx/nginx.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..a0123797a52fe22182952ad6c535001a88fb69c5
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/nginx.libsonnet
@@ -0,0 +1,149 @@
+local k = import 'k.libsonnet';
+local deployment = k.extensions.v1beta1.deployment;
+local container = deployment.mixin.spec.template.spec.containersType;
+
+{
+  parts:: {
+    deployment:: {
+      local defaults = {
+        imageTag: "1.10.2-r3",
+        imagePullPolicy: "IfNotPresent",
+      },
+
+      simple(namespace, name, labels={app: name})::
+        base(namespace, name, labels),
+
+      withServerBlock(namespace, name, configMapName="nginx-vhost", labels={app: name})::
+        local volume = {
+          name:: configMapName,
+          configMap:: { name: name },
+        };
+        local dataMount = {
+          name:: configMapName,
+          mountPath:: "/bitnami/nginx/conf/vhosts",
+        };
+        base(namespace, name, labels) +
+        deployment.mixin.spec.template.spec.withVolumes(volume) +
+        deployment.mapContainersWithName(
+          [name],
+          function(c) c + container.withVolumeMounts(dataMount)
+        ),
+
+      local base(namespace, name, labels) = {
+        apiVersion: "extensions/v1beta1",
+        kind: "Deployment",
+        metadata: {
+          namespace: namespace,
+          name: name,
+          labels: { app: name },
+        },
+        spec: {
+          replicas: 1,
+          template: {
+            metadata: { labels: labels },
+            spec: {
+              containers: [{
+                name: name,
+                image: "bitnami/nginx:" + defaults.imageTag,
+                imagePullPolicy: defaults.imagePullPolicy,
+                ports: [
+                  {
+                    name: "http",
+                    containerPort: 80,
+                  },
+                  {
+                    name: "https",
+                    containerPort: 443,
+                  },
+                ],
+                livenessProbe: {
+                  httpGet: {
+                    path: "/",
+                    port: "http",
+                  },
+                  initialDelaySeconds: 30,
+                  timeoutSeconds: 5,
+                  failureThreshold: 6,
+                },
+                readinessProbe: {
+                  httpGet: {
+                    path: "/",
+                    port: "http",
+                  },
+                  initialDelaySeconds: 5,
+                  timeoutSeconds: 3,
+                  periodSeconds: 5,
+                },
+                volumeMounts: [{
+                  name: "nginx-data",
+                  mountPath: "/bitnami/nginx",
+                }]
+              }],
+              volumes: [{
+                name: "nginx-data",
+                emptyDir: {},
+              }]
+            },
+          },
+        },
+      },
+    },
+
+    service(namespace, name, selector={app: name}):: {
+      apiVersion: "v1",
+      kind: "Service",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: { app: name },
+      },
+      spec: {
+        type: "LoadBalancer",
+        ports: [
+          {
+            name: "http",
+            port: 80,
+            targetPort: "http",
+          },
+          {
+            name: "https",
+            port: 443,
+            targetPort: "https",
+          },
+        ],
+        selector: selector,
+      },
+    },
+
+    serverBlockConfigMap(namespace, name):: {
+      local defaults = {
+        // example PHP-FPM vhost
+        vhost:
+        |||
+        server {
+          listen 0.0.0.0:80;
+          root /app;
+          location / {
+            index index.html index.php;
+          }
+          location ~ \.php$ {
+            fastcgi_pass phpfpm-server:9000;
+            fastcgi_index index.php;
+            include fastcgi.conf;
+          }
+        }
+      |||
+      },
+      apiVersion: "v1",
+      kind: "ConfigMap",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: { app: name },
+      },
+      data: {
+        "vhost.conf": defaults.vhost,
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/nginx/parts.yaml b/pkg/registry/testdata/incubator/nginx/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..57b85949034869a76d427f27fa668a0832522179
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/parts.yaml
@@ -0,0 +1,41 @@
+{
+  "name": "nginx",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "Nginx is an open source reverse proxy server that supports HTTP, HTTPS, SMTP, POP3, and IMAP protocols. It can be used as a load balancer, HTTP cache, and web server. It is designed for high concurrency, high performance, and low memory usage.",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "nginx",
+    "server",
+    "vhost",
+    "server block"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.nginx-simple",
+    "componentName": "nginx",
+    "flags": {
+      "name": "nginx",
+      "namespace": "default"
+    },
+    "comment": "Run a simple NGINX server"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/nginx/prototypes/nginx-server-block.jsonnet b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-server-block.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..a777ed3e66e223e97858b71d6be6be58b4019ede
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-server-block.jsonnet
@@ -0,0 +1,22 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.nginx-server-block
+// @description Deploys a simple, stateless nginx server with server blocks (roughly equivalent
+//   to nginx virtual hosts). The nginx container is deployed using a
+//   Kubernetes deployment, and is exposed to a network with a service.
+// @shortDescription A simple, stateless nginx server with server blocks.
+// @param namespace string Namespace in which to put the application
+// @param name string Name to give to all components.
+
+// TODO: How should the ServerBlockConf be exposed to the user? Not quite sure what the default does except for setting web server to port 80.
+
+local k = import 'k.libsonnet';
+local nginx = import 'incubator/nginx/nginx.libsonnet';
+
+local namespace = import 'param://namespace';
+local appName = import 'param://name';
+
+k.core.v1.list.new([
+  nginx.parts.deployment.withServerBlock(namespace, appName),
+  nginx.parts.service(namespace, appName),
+  nginx.parts.serverBlockConfigMap(namespace, appName),
+])
diff --git a/pkg/registry/testdata/incubator/nginx/prototypes/nginx-simple.jsonnet b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-simple.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..5198d6118d07d4a2f2d116803cb6c394d1242413
--- /dev/null
+++ b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-simple.jsonnet
@@ -0,0 +1,20 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.nginx-simple
+// @description Deploys a simple, stateless nginx server. The nginx container is
+//   deployed using a Kubernetes deployment, and is exposed to a network with a
+//   service.
+// @shortDescription A simple, stateless nginx server.
+// @optionalParam namespace string default Namespace in which to put the application
+// @param name string Name to give to each of the components
+
+
+local k = import 'k.libsonnet';
+local nginx = import 'incubator/nginx/nginx.libsonnet';
+
+local namespace = import 'param://namespace';
+local appName = import 'param://name';
+
+k.core.v1.list.new([
+  nginx.parts.deployment.simple(namespace, appName),
+  nginx.parts.service(namespace, appName),
+])
diff --git a/pkg/registry/testdata/incubator/node/README.md b/pkg/registry/testdata/incubator/node/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3d3b06d9c4c208e19d939cb76c4ba6f778453760
--- /dev/null
+++ b/pkg/registry/testdata/incubator/node/README.md
@@ -0,0 +1,82 @@
+# nodejs
+
+> Node is an event-driven I/O server-side JavaScript environment based on V8
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.nodejs-simple](#io.ksonnet.pkg.nodejs-simple)
+  * [io.ksonnet.pkg.nodejs-nonpersistent](#io.ksonnet.pkg.nodejs-nonpersistent)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.nodejs-simple` prototype to generate Kubernetes YAML for nodejs, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.nodejs-simple nodejs \
+  --name nodejs \
+  --namespace default
+
+# Apply to server.
+$ ks apply -f nodejs.jsonnet
+```
+
+## Using the library
+
+The library files for nodejs define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure nodejs for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of nodejs, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.nodejs-simple
+
+Deploy a node.js server with persistent volumes. The node container is deployed using a deployment, and exposed to the network using a service.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.nodejs-simple nodejs \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace to specify location of app; default is 'default' [string]
+* `--name=<name>`: Name of app to identify all K8s objects in this prototype [string]
+
+### io.ksonnet.pkg.nodejs-nonpersistent
+
+Deploy a node.js server with no persistent volumes. The node container is deployed using a deployment, and exposed to the network using a service.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.nodejs-nonpersistent nodejs \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace to specify location of app; default is 'default' [string]
+* `--name=<name>`: Name of app to identify all K8s objects in this prototype [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/node/examples/generated.yaml b/pkg/registry/testdata/incubator/node/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b24f3f59e52bc74c37872321869d7cfb8d4d7196
--- /dev/null
+++ b/pkg/registry/testdata/incubator/node/examples/generated.yaml
@@ -0,0 +1,100 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: node-app
+  name: node-app
+  namespace: default
+spec:
+  replicas: 1
+  template:
+    metadata:
+      annotations:
+        pod.beta.kubernetes.io/init-containers: '[{"command": "[ /bin/sh, -c , git
+          clone https://github.com/jbianquetti-nami/simple-node-app.git /app && git
+          checkout 26679f6]", "image": "bitnami/node:8.4.0-r1", "imagePullPolicy":
+          "IfNotPresent", "name": "git-clone-app", "volumeMounts": [{"mountPath":
+          "/app", "name": "app"}]}, {"command": "[ \"npm\", \"install\" ]", "image":
+          "bitnami/node:8.4.0-r1", "imagePullPolicy": "IfNotPresent", "name": "npm-install",
+          "volumeMounts": [{"mountPath": "/app", "name": "app"}]}]'
+      labels:
+        app: node-app
+      test: it worked
+    spec:
+      containers:
+      - command:
+        - npm
+        - start
+        env:
+        - name: GIT_REPO
+          value: https://github.com/jbianquetti-nami/simple-node-app.git
+        image: bitnami/node:8.4.0-r1
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          failureThreshold: 6
+          httpGet:
+            path: /
+            port: http
+          initialDelaySeconds: 180
+          timeoutSeconds: 5
+        name: node-app
+        ports:
+        - containerPort: 3000
+          name: http
+        readinessProbe:
+          httpGet:
+            path: /
+            port: http
+          initialDelaySeconds: 30
+          periodSeconds: 5
+          timeoutSeconds: 3
+        resources:
+          requests:
+            cpu: 300m
+            memory: 512Mi
+        securityContext:
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /app
+          name: app
+        - mountPath: /app/data
+          name: data
+      volumes:
+      - emptyDir: {}
+        name: app
+      - name: data
+        persistentVolumeClaim:
+          claimName: node-app
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  annotations:
+    volume.alpha.kubernetes.io/storage-class: default
+  labels:
+    app: node-app
+  name: node-app
+  namespace: default
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 1Gi
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: node-app
+  name: node-app
+  namespace: default
+spec:
+  ports:
+  - name: http
+    port: 80
+    targetPort: http
+  selector:
+    app: node-app
+  type: LoadBalancer
diff --git a/pkg/registry/testdata/incubator/node/nodejs.libsonnet b/pkg/registry/testdata/incubator/node/nodejs.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..ba6c039056dc53d66e68e890048c7b7baf27ce9f
--- /dev/null
+++ b/pkg/registry/testdata/incubator/node/nodejs.libsonnet
@@ -0,0 +1,210 @@
+local k = import "k.libsonnet";
+local deployment = k.extensions.v1beta1.deployment;
+
+{
+  parts:: {
+    pvc(namespace, name, storageClassName=null):: {
+      local defaults = {
+        size:: "1Gi",
+        accessMode:: "ReadWriteOnce",
+      },
+
+      apiVersion: "v1",
+      kind: "PersistentVolumeClaim",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: {
+          app: name,
+        },
+        annotations:
+          if storageClassName == null
+          then {
+            "volume.alpha.kubernetes.io/storage-class": "default",
+          } else {
+            "volume.beta.kubernetes.io/storage-class": storageClassName,
+          },
+      },
+      spec: {
+        accessModes: [
+          defaults.accessMode,
+        ],
+        resources: {
+          requests: {
+            storage: defaults.size,
+          },
+        },
+      },
+    },
+
+    svc(namespace, name, selector={app: name}):: {
+      local defaults = {
+        serviceType:: "LoadBalancer",
+      },
+
+      apiVersion: "v1",
+      kind: "Service",
+      metadata: {
+        namespace: namespace,
+        name: name,
+        labels: {
+          app: name,
+        },
+      },
+      spec: {
+        type: defaults.serviceType,
+        ports: [
+          {
+            name: "http",
+            port: 80,
+            targetPort: "http",
+          },
+        ],
+        selector: selector,
+      },
+    },
+
+    deployment:: {
+      local defaults = {
+        image:: "bitnami/node:8.4.0-r1",
+        repository:: "https://github.com/jbianquetti-nami/simple-node-app.git",
+        revision:: "26679f6",
+        imagePullPolicy:: "IfNotPresent",
+        resources:: {
+          "requests": {
+            "memory": "512Mi",
+            "cpu": "300m",
+          },
+        },
+        mountPath:: "/app/data",
+        labels(name):: {"app": name},
+      },
+
+      // Note: volumes are added to deployment as overlays based on persistence
+      persistent(namespace, name, labels=defaults.labels(name), claimName=name)::
+        local volume = {
+          name: "data",
+          persistentVolumeClaim: {
+            claimName: claimName
+          },
+        };
+        base(namespace, name, labels) +
+        deployment.mixin.spec.template.spec.withVolumes(volume),
+
+      nonPersistent(namespace, name, labels=defaults.labels(name))::
+        local volume = {
+          name: "data",
+          emptyDir: {},
+        };
+        base(namespace, name, labels) +
+        deployment.mixin.spec.template.spec.withVolumes(volume),
+
+      local base(namespace, name, labels) = {
+        apiVersion: "extensions/v1beta1",
+        kind: "Deployment",
+        metadata: {
+          namespace: namespace,
+          name: name,
+          labels: labels
+        },
+        spec: {
+          replicas: 1,
+          template: {
+            metadata: {
+              labels: labels,
+              annotations: {
+                "pod.beta.kubernetes.io/init-containers": std.toString([
+                  {
+                    name: "git-clone-app",
+                    image: defaults.image,
+                    imagePullPolicy: defaults.imagePullPolicy,
+                    command: "[ /bin/sh, -c , git clone " + defaults.repository+ " /app && git checkout " + defaults.revision + "]",
+                    volumeMounts: [
+                      {
+                        name: "app",
+                        mountPath: "/app"
+                      }
+                    ],
+                  },
+                  {
+                    name: "npm-install",
+                    image: defaults.image,
+                    imagePullPolicy: defaults.imagePullPolicy,
+                    command: '[ "npm", "install" ]',
+                    volumeMounts: [
+                      {
+                        name: "app",
+                        mountPath: "/app"
+                      }
+                    ],
+                  },
+                ]),
+              },
+              test: "it worked",
+            },
+            spec: {
+              containers: [
+                {
+                  name: name,
+                  securityContext: {
+                    readOnlyRootFilesystem: true,
+                  },
+                  image: defaults.image,
+                  imagePullPolicy: defaults.imagePullPolicy,
+                  env: [
+                    {
+                      name: "GIT_REPO",
+                      value: defaults.repository,
+                    },
+                  ],
+                  command: [ "npm", "start" ],
+                  ports: [
+                    {
+                      name: "http",
+                      containerPort: 3000,
+                    }
+                  ],
+                  livenessProbe: {
+                    httpGet: {
+                      path: "/",
+                      port: "http",
+                    },
+                    initialDelaySeconds: 180,
+                    timeoutSeconds: 5,
+                    failureThreshold: 6,
+                  },
+                  readinessProbe: {
+                    httpGet: {
+                      path: "/",
+                      port: "http",
+                    },
+                    initialDelaySeconds: 30,
+                    timeoutSeconds: 3,
+                    periodSeconds: 5,
+                  },
+                  resources: defaults.resources,
+                  volumeMounts: [
+                    {
+                      name: "app",
+                      mountPath: "/app",
+                    },
+                    {
+                      name: "data",
+                      mountPath: defaults.mountPath,
+                    }
+                  ],
+                }
+              ],
+              volumes: [
+                {
+                  name: "app",
+                  emptyDir: {},
+                },
+              ],
+            }
+          },
+        },
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/node/parts.yaml b/pkg/registry/testdata/incubator/node/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6622a3ca94f6739d584c03cd73fe77670b25841d
--- /dev/null
+++ b/pkg/registry/testdata/incubator/node/parts.yaml
@@ -0,0 +1,41 @@
+{
+  "name": "nodejs",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "Node is an event-driven I/O server-side JavaScript environment based on V8",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "nodejs",
+    "node",
+    "server",
+    "javascript"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.nodejs-simple",
+    "componentName": "nodejs",
+    "flags": {
+      "name": "nodejs",
+      "namespace": "default"
+    },
+    "comment": "Run a simple NodeJS server"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/node/prototypes/nodejs-nonpersistent.jsonnet b/pkg/registry/testdata/incubator/node/prototypes/nodejs-nonpersistent.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..5303a2e5859edef298c1f85ef5465b49fc26f8de
--- /dev/null
+++ b/pkg/registry/testdata/incubator/node/prototypes/nodejs-nonpersistent.jsonnet
@@ -0,0 +1,18 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.nodejs-nonpersistent
+// @description Deploy a node.js server with no persistent volumes. The node container is
+//   deployed using a deployment, and exposed to the network using a service.
+// @shortDescription A simple, stateless NodeJS app server.
+// @param namespace string Namespace to specify location of app; default is 'default'
+// @param name string Name of app to identify all K8s objects in this prototype
+
+local k = import 'k.libsonnet';
+local nodeJS = import 'incubator/node/nodejs.libsonnet';
+
+local namespace = "import 'param://namespace'";
+local appName = "import 'param://name'";
+
+k.core.v1.list.new([
+  nodeJS.parts.deployment.nonPersistent(namespace, appName),
+  nodeJS.parts.svc(namespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/node/prototypes/nodejs-simple.jsonnet b/pkg/registry/testdata/incubator/node/prototypes/nodejs-simple.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..5c3f064745e83b5f697c8354df8424aa8f18091c
--- /dev/null
+++ b/pkg/registry/testdata/incubator/node/prototypes/nodejs-simple.jsonnet
@@ -0,0 +1,20 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.nodejs-simple
+// @description Deploy a node.js server with persistent volumes. The node container is
+//   deployed using a deployment, and exposed to the network using a service.
+// @shortDescription A simple NodeJS app server with persistent storage.
+// @param namespace string Namespace to specify location of app; default is 'default'
+// @param name string Name of app to identify all K8s objects in this prototype
+
+
+local k = import 'k.libsonnet';
+local nodeJS = import 'incubator/node/nodejs.libsonnet';
+
+local appName = import 'param://name';
+local namespace = import 'param://namespace';
+
+k.core.v1.list.new([
+  nodeJS.parts.deployment.persistent(namespace, appName),
+  nodeJS.parts.pvc(namespace, appName),
+  nodeJS.parts.svc(namespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/postgres/README.md b/pkg/registry/testdata/incubator/postgres/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f94bc77e999afc75b125c29bcdf828d7d8085ca7
--- /dev/null
+++ b/pkg/registry/testdata/incubator/postgres/README.md
@@ -0,0 +1,71 @@
+# postgres
+
+> PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness.
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.postgres-simple](#io.ksonnet.pkg.postgres-simple)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.postgres-simple` prototype to generate Kubernetes YAML for postgres, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.postgres-simple postgres \
+  --name postgres \
+  --namespace default \
+  --password boots
+
+# Apply to server.
+$ ks apply -f postgres.jsonnet
+```
+
+## Using the library
+
+The library files for postgres define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure postgres for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of postgres, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.postgres-simple
+
+Deploy postgres backed by a persistent volume. Postgres container is managed by a deployment object and exposed to the network with a service. The
+passwords are stored in a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.postgres-simple postgres \
+  --name YOUR_NAME_HERE \
+  --namespace YOUR_NAMESPACE_HERE \
+  --password YOUR_PASSWORD_HERE
+```
+
+Below is the Jsonnet file generated by this command.
+
+```
+// postgres.jsonnet
+<JSONNET HERE>
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--name=<name>`: Name of app to attach as identifier to all components [string]
+* `--namespace=<namespace>`: Namespace to specify destination in cluster; default is 'default' [string]
+* `--password=<password>`: Password for the root/admin user. [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/postgres/examples/generated.yaml b/pkg/registry/testdata/incubator/postgres/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6c29c2388aaf0ecc7a3504db02705c4075ea4bbb
--- /dev/null
+++ b/pkg/registry/testdata/incubator/postgres/examples/generated.yaml
@@ -0,0 +1,136 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: postgres-app
+  name: postgres-app
+  namespace: dev-alex
+spec:
+  template:
+    metadata:
+      labels:
+        app: postgres-app
+    spec:
+      containers:
+      - env:
+        - name: POSTGRES_USER
+          value: postgres
+        - name: PGUSER
+          value: postgres
+        - name: POSTGRES_DB
+          value: ""
+        - name: POSTGRES_INITDB_ARGS
+          value: ""
+        - name: PGDATA
+          value: /var/lib/postgresql/data/pgdata
+        - name: POSTGRES_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: postgres-password
+              name: postgres-app
+        - name: POD_IP
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        image: postgres:9.6.2
+        imagePullPolicy: Always
+        livenessProbe:
+          exec:
+            command:
+            - sh
+            - -c
+            - exec pg_isready --host $POD_IP
+          failureThreshold: 6
+          initialDelaySeconds: 60
+          timeoutSeconds: 5
+        name: postgres-app
+        ports:
+        - containerPort: 5432
+          name: postgresql
+        readinessProbe:
+          exec:
+            command:
+            - sh
+            - -c
+            - exec pg_isready --host $POD_IP
+          initialDelaySeconds: 5
+          periodSeconds: 5
+          timeoutSeconds: 3
+        resources:
+          requests:
+            cpu: 100m
+            memory: 256Mi
+        volumeMounts:
+        - mountPath: /var/lib/postgresql/data/pgdata
+          name: data
+          subPath: postgresql-db
+      nodeSelector: {}
+      tolerations: []
+      volumes:
+      - name: data
+        persistentVolumeClaim:
+          claimName: postgres-app
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  labels:
+    app: postgres-app
+  name: postgres-app
+  namespace: dev-alex
+spec:
+  ingress:
+  - from:
+    - podSelector:
+        matchLabels:
+          postgres-app-client: "true"
+    ports:
+    - port: 5432
+  - ports:
+    - port: 9187
+  podSelector:
+    matchLabels:
+      app: postgres-app
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: postgres-app
+  name: postgres-app
+  namespace: dev-alex
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 8Gi
+---
+apiVersion: v1
+data:
+  postgres-password: c2ltcGxlUGFzc3dvcmQ=
+kind: Secret
+metadata:
+  labels:
+    app: postgres-app
+  name: postgres-app
+  namespace: dev-alex
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: postgres-app
+  name: postgres-app
+  namespace: dev-alex
+spec:
+  externalIPs: []
+  ports:
+  - name: postgresql
+    port: 5432
+    targetPort: postgresql
+  selector:
+    app: postgres-app
+  type: ClusterIP
diff --git a/pkg/registry/testdata/incubator/postgres/examples/postgres.jsonnet b/pkg/registry/testdata/incubator/postgres/examples/postgres.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..41e97e43649e2f19c142b05ce4bb15298cca5e26
--- /dev/null
+++ b/pkg/registry/testdata/incubator/postgres/examples/postgres.jsonnet
@@ -0,0 +1,11 @@
+local k = import 'k.libsonnet';
+local service = k.core.v1.service.mixin;
+local psg = import '../postgres.libsonnet';
+
+k.core.v1.list.new([
+  psg.parts.deployment.persistent('dev-alex', "postgress-app"),
+  psg.parts.networkPolicy.allowExternal('dev-alex', true),
+  psg.parts.pvc('dev-alex', "postgress-app"),
+  psg.parts.secrets('dev-alex', "postgress-app", "GOODPASSWORD"),
+  psg.parts.service('dev-alex', "postgress-app", false),
+])
diff --git a/pkg/registry/testdata/incubator/postgres/parts.yaml b/pkg/registry/testdata/incubator/postgres/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ca71e49ad27e9cf058e09efc48c9b640f10712a1
--- /dev/null
+++ b/pkg/registry/testdata/incubator/postgres/parts.yaml
@@ -0,0 +1,40 @@
+{
+  "name": "postgres",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness.",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "postgres",
+    "postgresql",
+    "database"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.postgres-simple",
+    "componentName": "postgres",
+    "flags": {
+      "name": "posgres",
+      "namespace": "default",
+      "password": "boots"
+    }
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/postgres/postgres.libsonnet b/pkg/registry/testdata/incubator/postgres/postgres.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..4f6fdddfca24acf49b0ab6a028432dc9b81aa408
--- /dev/null
+++ b/pkg/registry/testdata/incubator/postgres/postgres.libsonnet
@@ -0,0 +1,313 @@
+local k = import 'k.libsonnet';
+local service = k.core.v1.service.mixin;
+
+{
+  parts:: {
+    service(namespace, name, metricsEnabled=false, externalIpArray=null, selector={app:name})::
+      local defaults= {
+        type: 'ClusterIP',
+        port: 5432
+      };
+
+      {
+        apiVersion: "v1",
+        kind: "Service",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: {
+            app: name,
+          },
+          [if metricsEnabled then "annotations"]: {
+            "prometheus.io/scrape": "true",
+            "prometheus.io/port": "9187"
+          },
+        },
+        spec: {
+          type: defaults.type,
+          ports: [
+            {
+              name: "postgresql",
+              port: defaults.port,
+              targetPort: "postgresql",
+            }
+          ],
+          [if externalIpArray != null then "externalIPs"]:
+            externalIpArray,
+          selector: selector
+        },
+      },
+
+    pvc(namespace, name, storageClassName="-", labels={app:name})::
+      local defaults = {
+        accessMode:"ReadWriteOnce",
+        size: "8Gi"
+      };
+
+      {
+        kind: "PersistentVolumeClaim",
+        apiVersion: "v1",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: labels,
+        },
+        spec: {
+          accessModes: [
+            defaults.accessMode
+          ],
+          resources: {
+            requests: {
+              storage: defaults.size,
+            },
+          },
+          storageClassName: storageClassName
+        },
+      },
+
+    secrets(namespace, name, postgresPassword, labels={app:name})::
+      {
+        apiVersion: "v1",
+        kind: "Secret",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: labels,
+        },
+        type: "Opaque",
+        data: {
+          "postgres-password": std.base64(postgresPassword),
+        },
+      },
+
+    networkPolicy:: {
+      allowExternal(namespace, name, allowInbound=false, labels={app:name}, podSelector={matchLabels: {app: name}})::
+        base(namespace, name, allowInbound, labels, podSelector),
+
+      local base(namespace, name, allowInbound, labels, podSelector) = {
+        kind: "NetworkPolicy",
+        apiVersion: "networking.k8s.io/v1",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: labels
+        },
+        spec: {
+          podSelector: podSelector,
+          ingress: [
+            {
+              # Allow inbound connections
+              ports: [
+                {port: 5432},
+              ],
+              [if allowInbound then "from"]:[
+                {
+                  podSelector: {
+                    matchLabels: {
+                      [name + "-client"]: "true",
+                    },
+                  },
+                },
+              ],
+            },
+            {
+              # Allow prometheus scrapes
+              ports: [
+                {port: 9187},
+              ]
+            }
+          ],
+        }
+      },
+    },
+    deployment::{
+      local defaults ={
+        image: "postgres",
+        imageTag:   "9.6.2",
+        imagePullPolicy: "Always",
+        persistence::{
+          enabled: true,
+          accessMode:"ReadWriteOnce",
+          size:"8Gi",
+          subPath:"postgresql-db"
+        },
+        resources:: {
+          requests: {
+            memory: "256Mi",
+            cpu: "100m"
+          }
+        },
+        metrics::{
+          enabled: false,
+          image: "wrouesnel/postgres_exporter",
+          imageTag: "v0.1.1",
+          imagePullPolicy: "IfNotPresent",
+          resources: {
+            requests: {
+              memory: "256Mi",
+              cpu: "100m"
+            }
+          }
+        },
+
+        postgresConfig:: {
+          user:: "postgres",
+          db:: "",
+          initDbArgs:: "",
+        },
+      },
+
+      persistent(namespace, name, pgConfig=defaults.postgresConfig, metricsEnabled=false, existingClaim=name, labels={app:name}):
+        local volume = {
+          name: "data",
+          persistentVolumeClaim: {
+            claimName: existingClaim
+          }
+        };
+        base(namespace, name, pgConfig, metricsEnabled, existingClaim, labels) +
+        k.extensions.v1beta1.deployment.mixin.spec.template.spec.withVolumes(volume),
+
+      nonPersistent(namespace, name, pgConfig=defaults.postgresConfig, metricsEnabled=false, existingClaim=name, labels={app:name})::
+        local volume = {
+          name: "data",
+          emptyDir: {}
+        };
+        base(namespace, name, pgConfig, metricsEnabled, existingClaim, labels) +
+        k.extensions.v1beta1.deployment.mixin.spec.template.spec.withVolumes(volume),
+
+      local base(namespace, name, pgConfig, metricsEnabled, existingClaim, labels) =
+        local metricsContainer = [
+          {
+            name: "metrics",
+            image: defaults.metrics.image + ":" + defaults.metrics.imageTag,
+            imagePullPolicy: defaults.metrics.imagePullPolicy,
+            env: [
+              {
+                name: "DATA_SOURCE_NAME",
+                value: "postgresql://postgres@127.0.0.1:5432?sslmode=disable",
+              },
+            ],
+            ports: [
+              {
+                name: "metrics",
+                containerPort: 9187,
+              },
+            ],
+            resources: defaults.metrics.resources
+          }
+        ];
+
+        {
+          apiVersion: "extensions/v1beta1",
+          kind: "Deployment",
+          metadata: {
+            name: name,
+            namespace: namespace,
+              labels: {
+                app: name
+              },
+          },
+          spec: {
+            template: {
+              metadata: {
+                labels: {
+                  app: name,
+                }
+              },
+              spec: {
+                containers: [
+                  {
+                    name: name,
+                    image: defaults.image + ":" + defaults.imageTag,
+                    imagePullPolicy: defaults.imagePullPolicy,
+                    env: [
+                      {
+                        name: "POSTGRES_USER",
+                        value: pgConfig.user
+                      },
+                      {
+                        # Required for pg_isready in the health probes.
+                        name: "PGUSER",
+                        value:
+                          pgConfig.user
+                      },
+                      {
+                        name: "POSTGRES_DB",
+                        value: pgConfig.db
+                      },
+                      {
+                        name: "POSTGRES_INITDB_ARGS",
+                        value: pgConfig.initDbArgs
+                      },
+                      {
+                        name: "PGDATA",
+                        value: "/var/lib/postgresql/data/pgdata",
+                      },
+                      {
+                        name: "POSTGRES_PASSWORD",
+                        valueFrom: {
+                          secretKeyRef: {
+                            name: name,
+                            key: "postgres-password"
+                          },
+                        },
+                      },
+                      {
+                        name: "POD_IP",
+                        valueFrom: {
+                          fieldRef: {
+                            fieldPath: "status.podIP",
+                          }
+                        }
+                      }
+                    ],
+                    ports: [
+                      {
+                        name: "postgresql",
+                        containerPort: 5432,
+                      },
+                    ],
+                    livenessProbe: {
+                      exec: {
+                        command: [
+                          "sh",
+                          "-c",
+                          "exec pg_isready --host $POD_IP",
+                        ],
+                      },
+                      initialDelaySeconds: 60,
+                      timeoutSeconds: 5,
+                      failureThreshold: 6,
+                    },
+                    readinessProbe: {
+                      exec: {
+                        command: [
+                          "sh",
+                          "-c",
+                          "exec pg_isready --host $POD_IP",
+                        ],
+                      },
+                      initialDelaySeconds: 5,
+                      timeoutSeconds: 3,
+                      periodSeconds: 5,
+                    },
+                    resources: defaults.metrics.resources,
+                    volumeMounts: [
+                      {
+                        name: "data",
+                        mountPath: "/var/lib/postgresql/data/pgdata",
+                        subPath: defaults.persistence.subPath
+                      },
+                    ],
+                  },
+                ] +
+                if metricsEnabled then metricsContainer
+                  else [],
+                volumes: [],
+              },
+            },
+          },
+        },
+    }
+  }
+}
diff --git a/pkg/registry/testdata/incubator/postgres/prototypes/postgres-simple.jsonnet b/pkg/registry/testdata/incubator/postgres/prototypes/postgres-simple.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..1e223c89f8bd805747caf47b4535327b4fdc3d95
--- /dev/null
+++ b/pkg/registry/testdata/incubator/postgres/prototypes/postgres-simple.jsonnet
@@ -0,0 +1,23 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.postgres-simple
+// @description Deploy postgres backed by a persistent volume. Postgres container is managed by
+//   a deployment object and exposed to the network with a service. The
+//   passwords are stored in a secret.
+// @shortDescription A simple Postgres deployment, backed by persistent storage.
+// @param name string Name of app to attach as identifier to all components
+// @param namespace string Namespace to specify destination in cluster; default is 'default'
+// @param password string Password for the root/admin user.
+
+local k = import 'k.libsonnet';
+local psg = import 'incubator/postgres/postgres.libsonnet';
+
+local appName = import 'param://name';
+local namespace = import 'param://namespace';
+local password = import 'param://password';
+
+k.core.v1.list.new([
+  psg.parts.deployment.persistent(namespace, appName),
+  psg.parts.pvc(namespace, appName),
+  psg.parts.secrets(namespace, appName, password),
+  psg.parts.service(namespace, appName)
+])
diff --git a/pkg/registry/testdata/incubator/redis/README.md b/pkg/registry/testdata/incubator/redis/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..cafb05a9b2baf81e3fbfc596ea632a626850b281
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/README.md
@@ -0,0 +1,101 @@
+# redis
+
+> Redis is an advanced key-value cache and store. Often referred to as a data structure server since keys can contain structures as simple as strings, hashes and as complex as bitmaps and hyperloglogs. This package will deploy redis backed by a mounted persistent volume, a secret to hold your database password and a service to expose your deployment.
+
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.redis-stateless](#io.ksonnet.pkg.redis-stateless)
+  * [io.ksonnet.pkg.redis-persistent](#io.ksonnet.pkg.redis-persistent)
+  * [io.ksonnet.pkg.redis-all-features](#io.ksonnet.pkg.redis-all-features)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.redis-persistent` prototype to generate Kubernetes YAML for redis, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.redis-persistent redis \
+  --name redis \
+  --namespace default \
+  --password boots
+
+# Apply to server.
+$ ks apply -f redis.jsonnet
+```
+
+## Using the library
+
+The library files for redis define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure redis for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of redis, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.redis-stateless
+
+Stateless redis, backed with NO persistent volume claim. Redis is deployed using a Kubernetes deployment, exposed to the network with a service, with
+a password stored in a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.redis-stateless redis \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--name=<name>`: Name to give to each of the components. [string]
+
+### io.ksonnet.pkg.redis-persistent
+
+Redis backed by a persistent volume claim. Redis is deployed using a Kubernetes deployment, exposed to the network with a service, with a password stored in a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.redis-persistent redis \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--name=<name>`: Name to give to each of the components [string]
+
+### io.ksonnet.pkg.redis-all-features
+
+Redis with all the features supported by redis.libsonnet (e.g. secret, metrics, ingress, PVC)
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.redis-all-features redis \
+  --name YOUR_NAME_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--name=<name>`: Name to give to each of the components [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/redis/examples/generated.yaml b/pkg/registry/testdata/incubator/redis/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c0023986a687c8fbdc630710ab03e003310b69bb
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/examples/generated.yaml
@@ -0,0 +1,126 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: redis-app
+  name: redis-app
+  namespace: dev-alex
+spec:
+  template:
+    metadata:
+      labels:
+        app: redis-app
+    spec:
+      containers:
+      - env:
+        - name: REDIS_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: redis-password
+              name: redis-app
+        image: bitnami/redis:3.2.9-r2
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          exec:
+            command:
+            - redis-cli
+            - ping
+          initialDelaySeconds: 30
+          timeoutSeconds: 5
+        name: redis-app
+        ports:
+        - containerPort: 6379
+          name: redis
+        readinessProbe:
+          exec:
+            command:
+            - redis-cli
+            - ping
+          initialDelaySeconds: 5
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 100m
+            memory: 256Mi
+        volumeMounts:
+        - mountPath: /bitnami/redis
+          name: redis-data
+      - env:
+        - name: REDIS_ALIAS
+          value: redis-app
+        - name: REDIS_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              key: redis-password
+              name: redis-app
+        image: oliver006/redis_exporter:v0.11
+        imagePullPolicy: IfNotPresent
+        name: metrics
+        ports:
+        - containerPort: 9121
+          name: metrics
+      volumes:
+      - name: redis-data
+        persistentVolumeClaim:
+          claimName: redis-app
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  labels:
+    app: redis-app
+  name: redis-app
+  namespace: dev-alex
+spec:
+  ingress:
+  - ports:
+    - port: 9121
+  - from:
+    - podSelector: null
+    ports:
+    - port: 6379
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: redis-app
+  name: redis-app
+  namespace: dev-alex
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 8Gi
+  storageClassName: '-'
+---
+apiVersion: v1
+data:
+  redis-password: Wm05dlltRnk=
+kind: Secret
+metadata:
+  labels:
+    app: redis-app
+  name: redis-app
+  namespace: dev-alex
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    prometheus.io/port: "9121"
+    prometheus.io/scrape: "true"
+  labels:
+    app: redis-app
+  name: redis-app
+  namespace: dev-alex
+spec:
+  ports:
+  - name: redis
+    port: 6379
+    targetPort: redis
+  selector:
+    app: redis-app
diff --git a/pkg/registry/testdata/incubator/redis/examples/redis.jsonnet b/pkg/registry/testdata/incubator/redis/examples/redis.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..b06295ac5721e34e72e3208b474b76ef46dc0970
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/examples/redis.jsonnet
@@ -0,0 +1,10 @@
+local k = import 'k.libsonnet';
+local redis = import '../redis.libsonnet';
+
+k.core.v1.list.new([
+redis.parts.deployment.persistent("dev-alex", "redis-app","redis-app", true),
+  redis.parts.networkPolicy.allowExternal('dev-alex', "redis-app", true, true),
+  redis.parts.pvc('dev-alex', "redis-app", "-"),
+  redis.parts.secret('dev-alex', "redis-app", 'Zm9vYmFy'),
+  redis.parts.svc.metricEnabled("dev-alex", "redis-app"),
+])
diff --git a/pkg/registry/testdata/incubator/redis/parts.yaml b/pkg/registry/testdata/incubator/redis/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f59cadc17b57043bb12cdc07e763adf01f1edfe6
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/parts.yaml
@@ -0,0 +1,40 @@
+{
+   "name": "redis",
+   "apiVersion": "0.0.1",
+   "kind": "ksonnet.io/parts",
+   "description": "Redis is an advanced key-value cache and store. Often referred to as a data structure server since keys can contain structures as simple as strings, hashes and as complex as bitmaps and hyperloglogs. This package will deploy redis backed by a mounted persistent volume, a secret to hold your database password and a service to expose your deployment.\n",
+   "author": "ksonnet team <ksonnet-help@heptio.com>",
+   "contributors": [
+      {
+         "name": "Tehut Getahun",
+         "email": "tehut@heptio.com"
+      },
+      {
+         "name": "Tamiko Terada",
+         "email": "tamiko@heptio.com"
+      }
+   ],
+   "repository": {
+      "type": "git",
+      "url": "https://github.com/ksonnet/mixins"
+   },
+   "bugs": {
+      "url": "https://github.com/ksonnet/mixins/issues"
+   },
+   "keywords": [
+      "redis",
+      "persistent",
+      "database"
+   ],
+   "quickStart": {
+      "prototype": "io.ksonnet.pkg.redis-persistent",
+      "componentName": "redis",
+      "flags": {
+         "name": "redis",
+         "namespace": "default",
+         "password": "boots"
+      },
+      "comment": "Run simple Redis, backed by a PersistentVolume"
+   },
+   "license": "Apache 2.0"
+}
diff --git a/pkg/registry/testdata/incubator/redis/prototypes/redis-all-features.jsonnet b/pkg/registry/testdata/incubator/redis/prototypes/redis-all-features.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..8eae75c2689a4dc46fd17bbf2ec6397122e73c94
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/prototypes/redis-all-features.jsonnet
@@ -0,0 +1,29 @@
+// @apiVersion 0.1
+// @name io.ksonnet.pkg.redis-all-features
+// @description Redis with all the features supported by redis.libsonnet
+//   (e.g. secret, metrics, ingress, PVC)
+// @shortDescription A Redis deployment with metrics, ingress, and persistent storage.
+// @param name string Name to give to each of the components
+// @optionalParam redisPassword string null User password to supply to redis
+
+local k = import 'k.libsonnet';
+local redis = import 'incubator/redis/redis.libsonnet';
+
+local name = import 'param://name';
+local redisPassword = import 'param://redisPassword';
+
+local secretName =
+  if redisPassword != "null" then name else null;
+
+local optionalSecret =
+  if redisPassword != "null"
+  then redis.parts.secret(name, redisPassword)
+  else null;
+
+std.prune(k.core.v1.list.new([
+  redis.parts.deployment.persistent(name, secretName),
+  redis.parts.networkPolicy.allowExternal(name, true, true),
+  redis.parts.pvc(name),
+  redis.parts.svc.metricEnabled(name),
+  optionalSecret,
+]))
diff --git a/pkg/registry/testdata/incubator/redis/prototypes/redis-persistent.jsonnet b/pkg/registry/testdata/incubator/redis/prototypes/redis-persistent.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..6628215d45cc2a8fc65dc8568687f3e315707887
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/prototypes/redis-persistent.jsonnet
@@ -0,0 +1,29 @@
+// @apiVersion 0.1
+// @name io.ksonnet.pkg.redis-persistent
+// @description Redis backed by a persistent volume claim. Redis is deployed using a Kubernetes
+//   deployment, exposed to the network with a service, with a password stored
+//   in a secret.
+// @param name string Name to give to each of the components
+// @shortDescription A simple Redis deployment, backed by persistent storage.
+// @optionalParam redisPassword string null User password to supply to redis
+
+local k = import 'k.libsonnet';
+local redis = import 'incubator/redis/redis.libsonnet';
+
+local name = import 'param://name';
+local redisPassword = import 'param://redisPassword';
+
+local secretName =
+  if redisPassword != "null" then name else null;
+
+local optionalSecret =
+  if redisPassword != "null"
+  then redis.parts.secret(name, redisPassword)
+  else null;
+
+std.prune(k.core.v1.list.new([
+  redis.parts.deployment.persistent(name, secretName),
+  redis.parts.pvc(name),
+  redis.parts.svc.metricDisabled(name),
+  optionalSecret,
+]))
diff --git a/pkg/registry/testdata/incubator/redis/prototypes/redis-stateless.jsonnet b/pkg/registry/testdata/incubator/redis/prototypes/redis-stateless.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..163d1a2743328ff6acd14fe0fdc459ae79ac30fc
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/prototypes/redis-stateless.jsonnet
@@ -0,0 +1,28 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.redis-stateless
+// @description Stateless redis, backed with NO persistent volume claim. Redis is deployed
+//   using a Kubernetes deployment, exposed to the network with a service, with
+//   a password stored in a secret.
+// @shortDescription A simple, stateless Redis deployment,
+// @param name string Name to give to each of the components.
+// @optionalParam redisPassword string null User password to supply to redis
+
+local k = import 'k.libsonnet';
+local redis = import 'incubator/redis/redis.libsonnet';
+
+local name = import 'param://name';
+local redisPassword = import 'param://redisPassword';
+
+local secretName =
+  if redisPassword != "null" then name else null;
+
+local optionalSecret =
+  if redisPassword != "null"
+  then redis.parts.secret(name, redisPassword)
+  else null;
+
+std.prune(k.core.v1.list.new([
+  redis.parts.deployment.nonPersistent(name, secretName),
+  redis.parts.svc.metricDisabled(name),
+  optionalSecret,
+]))
diff --git a/pkg/registry/testdata/incubator/redis/redis.libsonnet b/pkg/registry/testdata/incubator/redis/redis.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..0ffb84230499c9705805e44965b844e0d8599332
--- /dev/null
+++ b/pkg/registry/testdata/incubator/redis/redis.libsonnet
@@ -0,0 +1,277 @@
+local k = import 'k.libsonnet';
+local deployment = k.extensions.v1beta1.deployment;
+local container = deployment.mixin.spec.template.spec.containersType;
+local storageClass = k.storage.v1beta1.storageClass;
+local service = k.core.v1.service;
+local networkPolicy = k.extensions.v1beta1.networkPolicy;
+local networkSpec = networkPolicy.mixin.spec;
+
+{
+  parts:: {
+    networkPolicy:: {
+      local defaults = {
+        inboundPort: {
+          ports: [{port:6379}]
+        },
+      },
+
+      allowExternal(name, allowInbound, metricEnabled, podSelector=null, labels={app:name},)::
+        base(name, metricEnabled, podSelector, labels) +
+        networkSpec.withIngress(defaults.inboundPort),
+
+      denyExternal(name, allowInbound, metricEnabled, podSelector={matchLabels:{[name + "-client"]: "true"}}, labels={app:name}, )::
+        local ingressRule = defaults.inboundPort + {
+          from: [
+            {
+              podSelector: podSelector
+            },
+          ],
+        };
+        base(name, metricEnabled, podSelector, labels)+
+        networkSpec.withIngress(ingressRule),
+
+      local base(name, metricEnabled, podSelector, labels) = {
+        kind: "NetworkPolicy",
+        apiVersion: "networking.k8s.io/v1",
+        metadata: {
+          name: name,
+          labels: labels,
+        },
+        spec: {
+          [if podSelector != null then "podSelector"]: podSelector,
+          [if metricEnabled then "ingress"]: [
+            {
+              # Allow prometheus scrapes for metrics
+              ports: [
+                {port: 9121},
+              ]
+            }
+          ],
+        },
+      },
+    },
+
+    secret(name, redisPassword, labels={app:name})::
+      local defaults = {
+        usePassword: true,
+      };
+
+      {
+        apiVersion: "v1",
+        kind: "Secret",
+        metadata: {
+          name: name,
+          labels: labels,
+        },
+        type: "Opaque",
+        data: {
+          "redis-password": std.base64(redisPassword),
+        },
+      },
+
+    svc::{
+      metricDisabled(name, labels={app:name}, selector={app:name})::
+      svcBase(name, labels, selector),
+
+      metricEnabled(name, labels={app:name}, selector={app:name})::
+        local annotations = {
+          "prometheus.io/scrape": "true",
+          "prometheus.io/port": "9121"
+        };
+        svcBase(name, labels, selector) +
+          service.mixin.metadata.withAnnotations(annotations),
+
+      local svcBase(name, labels, selector)= {
+        apiVersion: "v1",
+        kind: "Service",
+        metadata: {
+          name: name,
+          labels: labels,
+        },
+        spec: {
+          ports: [
+            {
+              name: "redis",
+              port: 6379,
+              targetPort: "redis",
+            }
+          ],
+          selector: selector,
+        }
+      },
+    },
+
+    pvc(name, storageClass="-", labels={app:name})::
+      local defaults = {
+        accessMode: "ReadWriteOnce",
+        size: '8Gi'
+      };
+
+      {
+        kind: "PersistentVolumeClaim",
+        apiVersion: "v1",
+        metadata: {
+          name: name,
+          labels: labels
+        },
+        spec: {
+          accessModes: [
+            defaults.accessMode,
+          ],
+          storageClassName: storageClass,
+          resources: {
+            requests: {
+              storage: defaults.size,
+            },
+          },
+        },
+      },
+
+    deployment:: {
+      local defaults = {
+        image:: "bitnami/redis:3.2.9-r2",
+        imagePullPolicy:: "IfNotPresent",
+        resources:: {
+          "requests": {
+            "memory": "256Mi",
+            "cpu": "100m"
+          },
+        },
+        dataMount:: {
+          name: "redis-data",
+          mountPath: "/bitnami/redis",
+        },
+        metrics:: {
+          image: "oliver006/redis_exporter",
+          imageTag: "v0.11",
+          imagePullPolicy: "IfNotPresent",
+        },
+      },
+
+      nonPersistent(name, secretName, metricEnabled=false, labels={app:name},):
+        local volume = {
+          name: "redis-data",
+          emptyDir: {}
+        };
+        base(name, secretName, metricEnabled, labels) +
+        deployment.mixin.spec.template.spec.withVolumes(volume) +
+        deployment.mapContainersWithName(
+          [name],
+          function(c) c + container.withVolumeMounts(defaults.dataMount)
+        ),
+
+      persistent(name, secretName, metricEnabled=false, claimName=name, labels={app:name})::
+        local volume = {
+          name: "redis-data",
+          persistentVolumeClaim: {
+            claimName: claimName
+          }
+        };
+        base(name, secretName, metricEnabled, labels) +
+        deployment.mixin.spec.template.spec.withVolumes(volume) +
+        deployment.mapContainersWithName(
+          [name],
+          function(c) c + container.withVolumeMounts(defaults.dataMount)
+        ),
+
+      local base(name, secretName, metricsEnabled, labels) =
+        local metricsContainer =
+          if !metricsEnabled then []
+          else [{
+            name: "metrics",
+            image: defaults.metrics.image + ':' + defaults.metrics.imageTag,
+            imagePullPolicy: defaults.metrics.imagePullPolicy,
+            env: [
+              {
+                name: "REDIS_ALIAS",
+                value: name,
+              }
+            ] + if secretName == null then []
+            else [
+              {
+                name: "REDIS_PASSWORD",
+                valueFrom: {
+                  secretKeyRef: {
+                    name: name,
+                    key: "redis-password",
+                  }
+                },
+              },
+            ],
+            ports: [
+              {
+                name: "metrics",
+                containerPort: 9121,
+              }
+            ],
+          }];
+      {
+        apiVersion: "extensions/v1beta1",
+        kind: "Deployment",
+        metadata: {
+          name: name,
+          labels: labels,
+        },
+        spec: {
+          template: {
+            metadata: {
+              labels: labels
+            },
+            spec: {
+              containers: [
+                {
+                  name: name,
+                  image: defaults.image,
+                  imagePullPolicy: defaults.imagePullPolicy,
+                  env: [
+                    if secretName != null then
+                    {
+                      name: "REDIS_PASSWORD",
+                      valueFrom: {
+                        secretKeyRef: {
+                          name: secretName,
+                          key: "redis-password",
+                        },
+                      }
+                    }
+                    else{
+                      name: "ALLOW_EMPTY_PASSWORD",
+                      value: "yes",
+                    },
+                  ],
+                  ports: [
+                    {
+                      name: "redis",
+                      containerPort: 6379,
+                    },
+                  ],
+                  livenessProbe: {
+                    exec: {
+                      command: [
+                        "redis-cli",
+                        "ping",
+                      ],
+                    },
+                    initialDelaySeconds: 30,
+                    timeoutSeconds: 5,
+                  },
+                  readinessProbe: {
+                    exec: {
+                      command: [
+                        "redis-cli",
+                        "ping",
+                      ],
+                    },
+                    initialDelaySeconds: 5,
+                    timeoutSeconds: 1,
+                  },
+                  resources: defaults.resources,
+                },
+              ] + metricsContainer,
+            },
+          },
+        },
+      },
+    },
+  },
+}
diff --git a/pkg/registry/testdata/incubator/registry.yaml b/pkg/registry/testdata/incubator/registry.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..57e71c2d9cacce48baffb309c2fd718db4cf9ad0
--- /dev/null
+++ b/pkg/registry/testdata/incubator/registry.yaml
@@ -0,0 +1,36 @@
+apiVersion: '0.1'
+kind: ksonnet.io/registry
+libraries:
+  apache:
+    version: master
+    path: apache
+  efk:
+    version: master
+    path: efk
+  mariadb:
+    version: master
+    path: mariadb
+  memcached:
+    version: master
+    path: memcached
+  mongodb:
+    version: master
+    path: mongodb
+  mysql:
+    version: master
+    path: mysql
+  node:
+    version: master
+    path: node
+  nginx:
+    version: master
+    path: nginx
+  postgres:
+    version: master
+    path: postgres
+  redis:
+    version: master
+    path: redis
+  tomcat:
+    version: master
+    path: tomcat
diff --git a/pkg/registry/testdata/incubator/tomcat/README.md b/pkg/registry/testdata/incubator/tomcat/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..39a111ee706ecf9fcfa0fc89a847cf1dfec1fc76
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/README.md
@@ -0,0 +1,93 @@
+# tomcat
+
+> Apache Tomcat, or Tomcat, is an open-source web server and servlet container. This package deploys a stateless tomcat container, service, and secret to your cluster
+
+* [Quickstart](#quickstart)
+* [Using Prototypes](#using-prototypes)
+  * [io.ksonnet.pkg.non-persistent-tomcat](#io.ksonnet.pkg.non-persistent-tomcat)
+  * [io.ksonnet.pkg.persistent-tomcat](#io.ksonnet.pkg.persistent-tomcat)
+
+## Quickstart
+
+*The following commands use the `io.ksonnet.pkg.persistent-tomcat` prototype to generate Kubernetes YAML for tomcat, and then deploys it to your Kubernetes cluster.*
+
+First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)).
+
+If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`.
+
+Finally, in the ksonnet application directory, run the following:
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.persistent-tomcat tomcat \
+  --name tomcat \
+  --namespace default \
+  --tomcatUser frank \
+  --tomcatPassword boots
+
+# Apply to server.
+$ ks apply -f tomcat.jsonnet
+```
+
+## Using the library
+
+The library files for tomcat define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure tomcat for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache.
+
+This library provides a set of pre-fabricated "flavors" (or "distributions") of tomcat, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs.
+
+These prototypes, as well as how to use them, are enumerated below.
+
+### io.ksonnet.pkg.non-persistent-tomcat
+
+Deploys a stateless Tomcat server. Server is deployed using a Kubernetes deployment, and exposed to the network using a service. The password is stored as a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.non-persistent-tomcat tomcat \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE \
+  --tomcatUser YOUR_TOMCATUSER_HERE \
+  --tomcatPassword YOUR_TOMCATPASSWORD_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace in which to put the application [string]
+* `--name=<name>`: Name to give to each of the components. [string]
+* `--tomcatUser=<tomcatUser>`: Username for tomcat manager page, if not specified tomcat will not assign users [string]
+* `--tomcatPassword=<tomcatPassword>`: Tomcat manager page password, to be encrypted and included in Secret API object [string]
+
+### io.ksonnet.pkg.persistent-tomcat
+
+Deploys a stateful Tomcat server, backed by a persistent volume. Server is deployed using a Kubernetes deployment, and exposed to the network using a
+service. The password is stored as a secret.
+
+#### Example
+
+```shell
+# Expand prototype as a Jsonnet file, place in a file in the
+# `components/` directory. (YAML and JSON are also available.)
+$ ks prototype use io.ksonnet.pkg.persistent-tomcat tomcat \
+  --namespace YOUR_NAMESPACE_HERE \
+  --name YOUR_NAME_HERE \
+  --tomcatUser YOUR_TOMCATUSER_HERE \
+  --tomcatPassword YOUR_TOMCATPASSWORD_HERE
+```
+
+#### Parameters
+
+The available options to pass prototype are:
+
+* `--namespace=<namespace>`: Namespace in which to put the application [string]
+* `--name=<name>`: Name to give to each of the components [string]
+* `--tomcatUser=<tomcatUser>`: Username for tomcat manager page, if not specified tomcat will not assign users [string]
+* `--tomcatPassword=<tomcatPassword>`: Tomcat manager page password, to be encrypted and included in Secret API object [string]
+
+
+[rootReadme]: https://github.com/ksonnet/mixins
diff --git a/pkg/registry/testdata/incubator/tomcat/examples/generated.yaml b/pkg/registry/testdata/incubator/tomcat/examples/generated.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d3cbfc76374bb9dc842622b69c71ba6cc0ff5648
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/examples/generated.yaml
@@ -0,0 +1,97 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  labels:
+    app: tomcat-app
+  name: tomcat-app
+spec:
+  template:
+    metadata:
+      labels:
+        app: tomcat-app
+    spec:
+      containers:
+      - env:
+        - name: TOMCAT_USERNAME
+          value: mockUser
+        - name: TOMCAT_ALLOW_REMOTE_MANAGEMENT
+          value: 0
+          valueFrom:
+            secretKeyRef:
+              key: tomcat-password
+              name: mockSecretName
+        image: bitnami/tomcat:8.0.46-r0
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          failureThreshold: 6
+          httpGet:
+            path: /
+            port: http
+          initialDelaySeconds: 120
+          timeoutSeconds: 5
+        name: tomcat-app
+        ports:
+        - containerPort: 8080
+          name: http
+        readinessProbe:
+          httpGet:
+            path: /
+            port: http
+          initialDelaySeconds: 30
+          periodSeconds: 51
+          timeoutSeconds: 3
+        resources:
+          requests:
+            cpu: 300m
+            memory: 512Mi
+        volumeMounts:
+        - mountPath: /bitnami/tomcat
+          name: tomcat-data
+      volumes:
+      - name: tomcat-data
+        persistentVolumeClaim:
+          claimName: mockClaimName
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  annotations:
+    volume.alpha.kubernetes.io/storage-class: default
+  labels:
+    app: tomcat-app
+  name: tomcat-app
+  namespace: hoot-dev
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 8Gi
+---
+apiVersion: v1
+data:
+  tomcat-password: Ym9vdHM=
+kind: Secret
+metadata:
+  labels:
+    app: tomcat-app
+  name: tomcat-app
+  namespace: hoot-dev
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: tomcat-app
+  name: tomcat-app
+  namespace: hoot-dev
+spec:
+  ports:
+  - name: http
+    port: 80
+    targetPort: http
+  selector:
+    app: tomcat-app
+  type: LoadBalancer
diff --git a/pkg/registry/testdata/incubator/tomcat/examples/tomcat.jsonnet b/pkg/registry/testdata/incubator/tomcat/examples/tomcat.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..26c888d6abce7d88d5396939c8c3c221bd091046
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/examples/tomcat.jsonnet
@@ -0,0 +1,12 @@
+local k = import 'k.libsonnet';
+local tc = import '../tomcat.libsonnet';
+
+
+
+k.core.v1.list.new([
+  tc.parts.deployment.persistent("hoot-dev","tomcat-app", "mockUser", "mockSecretName", "mockClaimName"),
+  tc.parts.pvc("hoot-dev", "tomcat-app"),
+  tc.parts.secret("hoot-dev", "tomcat-app", "boots"),
+  tc.parts.svc("hoot-dev","tomcat-app")
+  ])
+
diff --git a/pkg/registry/testdata/incubator/tomcat/parts.yaml b/pkg/registry/testdata/incubator/tomcat/parts.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1ebb96c50dca38a0d76a860e9b96e23592c0d5af
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/parts.yaml
@@ -0,0 +1,42 @@
+{
+  "name": "tomcat",
+  "apiVersion": "0.0.1",
+  "kind": "ksonnet.io/parts",
+  "description": "Apache Tomcat, or Tomcat, is an open-source web server and servlet container. This package deploys a stateless tomcat container, service, and secret to your cluster",
+  "author": "ksonnet team <ksonnet-help@heptio.com>",
+  "contributors": [
+    {
+    "name": "Tehut Getahun",
+    "email": "tehut@heptio.com"
+    },
+    {
+    "name": "Tamiko Terada",
+    "email": "tamiko@heptio.com"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ksonnet/mixins"
+  },
+  "bugs": {
+    "url": "https://github.com/ksonnet/mixins/issues"
+  },
+  "keywords": [
+    "tomcat",
+    "server",
+    "serverlet"
+  ],
+  "quickStart": {
+    "prototype": "io.ksonnet.pkg.persistent-tomcat",
+    "componentName": "tomcat",
+    "flags": {
+      "name": "tomcat",
+      "namespace": "default",
+      "tomcatUser": "frank",
+      "tomcatPassword": "boots"
+    },
+    "comment": "Run a simple Tomcat server, backed by a PersistentVolume"
+  },
+  "license": "Apache 2.0"
+}
+
diff --git a/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-persistent.jsonnet b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-persistent.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..852d228d9b7cb1125a52f441cae48e2ea391d08a
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-persistent.jsonnet
@@ -0,0 +1,27 @@
+// @apiVersion 0.1
+// @name io.ksonnet.pkg.persistent-tomcat
+// @description Deploys a stateful Tomcat server, backed by a persistent volume. Server is
+//   deployed using a Kubernetes deployment, and exposed to the network using a
+//   service. The password is stored as a secret.
+// @shortDescription A simple Tomcat app server, backed with persistent storage.
+// @param namespace string Namespace in which to put the application
+// @param name string Name to give to each of the components
+// @param tomcatUser string Username for tomcat manager page, if not specified tomcat will not assign users
+// @param tomcatPassword string Tomcat manager page password, to be encrypted and included in Secret API object
+
+local k = import 'k.libsonnet';
+local tc = import 'incubator/tomcat/tomcat.libsonnet';
+
+local namespace = import 'param://namespace';
+local name = import 'param://name';
+local tomcatUser = import 'param://tomcatUser';
+local tomcatPassword = import 'param://tomcatPassword';
+local passwordSecretName = import 'param://passwordSecretName/name';
+
+
+k.core.v1.list.new([
+  tc.parts.deployment.persistent(namespace, name, tomcatUser, passwordSecretName, name),
+  tc.parts.pvc(namespace, name),
+  tc.parts.secret(namespace, name, tomcatPassword),
+  tc.parts.svc(namespace,name)
+  ])
diff --git a/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-stateless.jsonnet b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-stateless.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..491c952f2686d8a95eaed3f75a6cc54b4a99b9c9
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-stateless.jsonnet
@@ -0,0 +1,23 @@
+// @apiVersion 0.0.1
+// @name io.ksonnet.pkg.non-persistent-tomcat
+// @description Deploys a stateless Tomcat server. Server is deployed using a Kubernetes
+//   deployment, and exposed to the network using a service. The password is stored as a secret.
+// @shortDescription A simple, stateless Tomcat app server.
+// @param namespace string Namespace in which to put the application
+// @param name string Name to give to each of the components.
+// @param tomcatUser string Username for tomcat manager page, if not specified tomcat will not assign users
+// @param tomcatPassword string Tomcat manager page password, to be encrypted and included in Secret API object
+
+local k = import 'k.libsonnet';
+local tc = import 'incubator/tomcat/tomcat.libsonnet';
+
+local namespace = import 'param://namespace';
+local name = import 'param://name';
+local tomcatUser = import 'param://tomcatUser';
+local tomcatPassword = import 'param://tomcatPassword';
+
+k.core.v1.list.new([
+  tc.parts.deployment.nonPersistent(namespace, name, tomcatUser, name),
+  tc.parts.secret(namespace, name, tomcatPassword),
+  tc.parts.svc(namespace, name)
+])
diff --git a/pkg/registry/testdata/incubator/tomcat/tomcat.libsonnet b/pkg/registry/testdata/incubator/tomcat/tomcat.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..3156348d744649a639f9aac6acba9852cae71f17
--- /dev/null
+++ b/pkg/registry/testdata/incubator/tomcat/tomcat.libsonnet
@@ -0,0 +1,199 @@
+local k = import 'k.libsonnet';
+
+{
+  parts::{
+    local defaults = {
+      serviceType: "LoadBalancer"
+    },
+
+    svc(namespace, name, selector={app: name})::{
+      apiVersion: "v1",
+        kind: "Service",
+        metadata: {
+          name: name,
+          namespace: namespace,
+          labels: {
+            app: name
+          },
+        },
+        spec: {
+          type: defaults.serviceType,
+          ports: [
+            {
+              name: "http",
+              port: 80,
+              targetPort: "http",
+            },
+          ],
+          selector: selector
+        },
+    },
+
+    secret(namespace, name, tomcatPassword):: {
+      apiVersion: "v1",
+      kind: "Secret",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: {
+          app: name
+        },
+      },
+      type: "Opaque",
+      data:
+        if tomcatPassword != null then {
+          "tomcat-password": std.base64(tomcatPassword)
+        } else error "Please set password"
+    },
+
+    pvc(namespace, name, storageClass=null):: {
+      local defaults = {
+        persistence: {
+          accessMode: "ReadWriteOnce",
+          size: "8Gi",
+        },
+      },
+
+      kind: "PersistentVolumeClaim",
+      apiVersion: "v1",
+      metadata: {
+        name: name,
+        namespace: namespace,
+        labels: {
+          app: name,
+        },
+        annotations:
+          if storageClass != null then {
+            "volume.beta.kubernetes.io/storage-class": storageClass,
+          } else {
+            "volume.alpha.kubernetes.io/storage-class": "default",
+          },
+      },
+      spec: {
+        accessModes: [
+          defaults.persistence.accessMode
+        ],
+        resources: {
+          requests: {
+            storage: defaults.persistence.size,
+          },
+        },
+      },
+    },
+
+    deployment:: {
+      local defaults = {
+        image: "bitnami/tomcat:8.0.46-r0",
+        imagePullPolicy: "IfNotPresent",
+        tomcatAllowRemoteManagement: 0,
+        persistence:{
+          accessMode: "ReadWriteOnce",
+          size: "8Gi",
+        },
+        resources:{
+          requests: {
+            memory: "512Mi",
+            cpu: "300m",
+            },
+        },
+      },
+
+      persistent(namespace, name, tomcatUser, passwordSecretName, claimName)::
+        base(namespace, name, tomcatUser, passwordSecretName) +
+        k.apps.v1beta1.deployment.mixin.spec.template.spec.withVolumes(
+          {
+            name: "tomcat-data",
+            persistentVolumeClaim: {
+              claimName: claimName,
+            },
+          }),
+
+      nonPersistent(namespace, name, tomcatUser, passwordSecretName)::
+        base(namespace, name, tomcatUser, passwordSecretName) +
+        k.apps.v1beta1.deployment.mixin.spec.template.spec.withVolumes(
+          {
+            name: "tomcat-data",
+            emptyDir: {}
+          }),
+
+      local base(namespace, name, tomcatUser, passwordSecretName) = {
+        apiVersion: "extensions/v1beta1",
+        kind: "Deployment",
+        metadata: {
+          name: name,
+          labels: {
+            app: name,
+          },
+        },
+        spec: {
+          template: {
+            metadata: {
+              labels: {
+                app: name,
+              },
+            },
+            spec: {
+              containers: [
+                {
+                  name: name,
+                  image: defaults.image,
+                  imagePullPolicy: defaults.imagePullPolicy,
+                  env: [
+                    {
+                      name: "TOMCAT_USERNAME",
+                      value: tomcatUser,
+                    },
+                    {
+                      name: "TOMCAT_PASSWORD",
+                      valueFrom: {
+                        secretKeyRef: {
+                          name: passwordSecretName,
+                          key: "tomcat-password",
+                        },
+                      },
+                    }
+                    {
+                      name: "TOMCAT_ALLOW_REMOTE_MANAGEMENT",
+                      value: defaults.tomcatAllowRemoteManagement,
+                    },
+                  ],
+                  ports: [
+                    {
+                      name: "http",
+                      containerPort: 8080,
+                    },
+                  ],
+                  livenessProbe: {
+                    httpGet: {
+                      path: "/",
+                      port: "http",
+                    },
+                    initialDelaySeconds: 120,
+                    timeoutSeconds: 5,
+                    failureThreshold: 6,
+                  },
+                  readinessProbe: {
+                    httpGet: {
+                      path: "/",
+                      port: "http",
+                    },
+                    initialDelaySeconds: 30,
+                    timeoutSeconds: 3,
+                    periodSeconds: 51,
+                  },
+                  resources: defaults.resources,
+                  volumeMounts: [
+                    {
+                      name: "tomcat-data",
+                      mountPath: "/bitnami/tomcat",
+                    },
+                  ],
+                },
+              ],
+            },
+          },
+        },
+      }
+    },
+  },
+}
diff --git a/pkg/util/github/github.go b/pkg/util/github/github.go
index 9f11ae682de74f0df54661bc45b9c1d5e0ddf8d0..1de95d1bf31ece331c073424b0b4a2462b35fc83 100644
--- a/pkg/util/github/github.go
+++ b/pkg/util/github/github.go
@@ -19,6 +19,7 @@ import (
 	"context"
 	"net/http"
 	"os"
+	"runtime/debug"
 
 	"github.com/google/go-github/github"
 	"github.com/sirupsen/logrus"
@@ -51,6 +52,8 @@ func (dg *defaultGitHub) CommitSHA1(ctx context.Context, repo Repo, refSpec stri
 		refSpec = "master"
 	}
 
+	debug.PrintStack()
+
 	logrus.Debugf("github: fetching SHA1 for %s/%s - %s", repo.Org, repo.Repo, refSpec)
 	sha, _, err := dg.client().Repositories.GetCommitSHA1(ctx, repo.Org, repo.Repo, refSpec, "")
 	return sha, err