From 734caef0f4560d7aa71502a206514a641b45d94b Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Wed, 13 Jul 2016 16:41:51 -0700 Subject: [PATCH 01/19] Fix storage drivers dropping non EOF errors when listing repositories This fixes errors other than io.EOF from being dropped when a storage driver lists repositories. For example, filesystem driver may point to a missing directory and errors, which then gets subsequently dropped. Signed-off-by: Edgar Lee --- registry/storage/catalog.go | 10 ++++---- registry/storage/catalog_test.go | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 3b13b7ad..51f31691 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -25,12 +25,12 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri return 0, errors.New("no space in slice") } - root, err := pathFor(repositoriesRootPathSpec{}) - if err != nil { - return 0, err + root, errVal := pathFor(repositoriesRootPathSpec{}) + if errVal != nil { + return 0, errVal } - err = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { + errVal = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { filePath := fileInfo.Path() // lop the base path off @@ -58,7 +58,7 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri n = copy(repos, foundRepos) // Signal that we have no more entries by setting EOF - if len(foundRepos) <= len(repos) && err != ErrFinishedWalk { + if len(foundRepos) <= len(repos) && (errVal == nil || errVal == ErrSkipDir) { errVal = io.EOF } diff --git a/registry/storage/catalog_test.go b/registry/storage/catalog_test.go index eb062c5b..40fa909d 100644 --- a/registry/storage/catalog_test.go +++ b/registry/storage/catalog_test.go @@ -1,6 +1,7 @@ package storage import ( + "fmt" "io" "testing" @@ -123,3 +124,42 @@ func testEq(a, b []string, size int) bool { } return true } + +func setupBadWalkEnv(t *testing.T) *setupEnv { + d := newBadListDriver() + ctx := context.Background() + registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect) + if err != nil { + t.Fatalf("error creating registry: %v", err) + } + + return &setupEnv{ + ctx: ctx, + driver: d, + registry: registry, + } +} + +type badListDriver struct { + driver.StorageDriver +} + +var _ driver.StorageDriver = &badListDriver{} + +func newBadListDriver() *badListDriver { + return &badListDriver{StorageDriver: inmemory.New()} +} + +func (d *badListDriver) List(ctx context.Context, path string) ([]string, error) { + return nil, fmt.Errorf("List error") +} + +func TestCatalogWalkError(t *testing.T) { + env := setupBadWalkEnv(t) + p := make([]string, 1) + + _, err := env.registry.Repositories(env.ctx, p, "") + if err == io.EOF { + t.Errorf("Expected catalog driver list error") + } +} From 22a59f25125cac602cb1bb3ded9743dd9973f1b3 Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Thu, 14 Jul 2016 13:28:08 -0700 Subject: [PATCH 02/19] Refactor errVal named parameter for catalog repositories to err Signed-off-by: Edgar Lee --- registry/storage/catalog.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 51f31691..aec5f2e6 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -18,19 +18,19 @@ var ErrFinishedWalk = errors.New("finished walk") // Returns a list, or partial list, of repositories in the registry. // Because it's a quite expensive operation, it should only be used when building up // an initial set of repositories. -func (reg *registry) Repositories(ctx context.Context, repos []string, last string) (n int, errVal error) { +func (reg *registry) Repositories(ctx context.Context, repos []string, last string) (n int, err error) { var foundRepos []string if len(repos) == 0 { return 0, errors.New("no space in slice") } - root, errVal := pathFor(repositoriesRootPathSpec{}) - if errVal != nil { - return 0, errVal + root, err := pathFor(repositoriesRootPathSpec{}) + if err != nil { + return 0, err } - errVal = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { + err = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { filePath := fileInfo.Path() // lop the base path off @@ -58,11 +58,11 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri n = copy(repos, foundRepos) // Signal that we have no more entries by setting EOF - if len(foundRepos) <= len(repos) && (errVal == nil || errVal == ErrSkipDir) { - errVal = io.EOF + if len(foundRepos) <= len(repos) && (err == nil || err == ErrSkipDir) { + err = io.EOF } - return n, errVal + return n, err } // Enumerate applies ingester to each repository From 0567fa3c2ad0414d68e73b88007170b5521e591c Mon Sep 17 00:00:00 2001 From: Sebastien Coavoux Date: Thu, 21 Jul 2016 10:38:42 -0400 Subject: [PATCH 03/19] Fix: Compare path properly when list repository in catalog. #1854 Signed-off-by: Sebastien Coavoux --- registry/storage/catalog.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index aec5f2e6..89616402 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -39,7 +39,7 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri _, file := path.Split(repoPath) if file == "_layers" { repoPath = strings.TrimSuffix(repoPath, "/_layers") - if repoPath > last { + if pathGreaterThan(repoPath, last) { foundRepos = append(foundRepos, repoPath) } return ErrSkipDir @@ -95,3 +95,23 @@ func (reg *registry) Enumerate(ctx context.Context, ingester func(string) error) return nil } + +func pathGreaterThan(pathX, pathY string) (b bool) { + splitPathX := strings.SplitN(pathX, "/", 2) + splitPathY := strings.SplitN(pathY, "/", 2) + + if splitPathX[0] == splitPathY[0] { + if len(splitPathX) == 1 && len(splitPathY) == 1 { + return false + } else if len(splitPathX) == 1 && len(splitPathY) != 1 { + return false + } else if len(splitPathX) != 1 && len(splitPathY) == 1 { + return true + } + + return pathGreaterThan(splitPathX[1], splitPathY[1]) + + } + + return splitPathX[0] > splitPathY[0] +} From fdc51bb1f271696f9a3c52592ebb4c74fe64515c Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Fri, 5 Aug 2016 17:21:48 -0700 Subject: [PATCH 04/19] Stop ErrFinishedWalk from escaping from Repositories walk Signed-off-by: Edgar Lee --- registry/storage/catalog.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 89616402..5a00f7cd 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -10,11 +10,6 @@ import ( "github.com/docker/distribution/registry/storage/driver" ) -// ErrFinishedWalk is used when the called walk function no longer wants -// to accept any more values. This is used for pagination when the -// required number of repos have been found. -var ErrFinishedWalk = errors.New("finished walk") - // Returns a list, or partial list, of repositories in the registry. // Because it's a quite expensive operation, it should only be used when building up // an initial set of repositories. @@ -30,6 +25,10 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri return 0, err } + // errFinishedWalk signals an early exit to the walk when the current query + // is satisfied. + errFinishedWalk := errors.New("finished walk") + err = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { filePath := fileInfo.Path() @@ -49,7 +48,7 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri // if we've filled our array, no need to walk any further if len(foundRepos) == len(repos) { - return ErrFinishedWalk + return errFinishedWalk } return nil @@ -57,9 +56,14 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri n = copy(repos, foundRepos) - // Signal that we have no more entries by setting EOF - if len(foundRepos) <= len(repos) && (err == nil || err == ErrSkipDir) { + switch err { + case nil: + // nil means that we completed walk and didn't fill buffer. No more + // records are available. err = io.EOF + case errFinishedWalk: + // more records are available. + err = nil } return n, err From 2aa09ff9a822d523f71db6d37f378366cf55d42f Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Fri, 5 Aug 2016 17:18:43 -0700 Subject: [PATCH 05/19] registry/storage: more efficient path compare in catalog Previous component-wise path comparison is recursive and generates a large amount of garbage. This more efficient version simply replaces the path comparison with the zero-value to sort before everything. We do this by replacing the byte-wise comparison that swaps a single character inline for the separator comparison, such that separators sort first. The resulting implementation provides component-wise path comparison with no cost incurred for allocation or stack frame. Direction of the comparison is also reversed to match Go style. Signed-off-by: Stephen J Day --- registry/storage/catalog.go | 59 +++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 5a00f7cd..0e119a88 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -38,7 +38,7 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri _, file := path.Split(repoPath) if file == "_layers" { repoPath = strings.TrimSuffix(repoPath, "/_layers") - if pathGreaterThan(repoPath, last) { + if lessPath(last, repoPath) { foundRepos = append(foundRepos, repoPath) } return ErrSkipDir @@ -100,22 +100,49 @@ func (reg *registry) Enumerate(ctx context.Context, ingester func(string) error) } -func pathGreaterThan(pathX, pathY string) (b bool) { - splitPathX := strings.SplitN(pathX, "/", 2) - splitPathY := strings.SplitN(pathY, "/", 2) - - if splitPathX[0] == splitPathY[0] { - if len(splitPathX) == 1 && len(splitPathY) == 1 { - return false - } else if len(splitPathX) == 1 && len(splitPathY) != 1 { - return false - } else if len(splitPathX) != 1 && len(splitPathY) == 1 { - return true - } - - return pathGreaterThan(splitPathX[1], splitPathY[1]) +// lessPath returns true if one path a is less than path b. +// +// A component-wise comparison is done, rather than the lexical comparison of +// strings. +func lessPath(a, b string) bool { + // we provide this behavior by making separator always sort first. + return compareReplaceInline(a, b, '/', '\x00') < 0 +} +// compareReplaceInline modifies runtime.cmpstring to replace old with new +// during a byte-wise comparison. +func compareReplaceInline(s1, s2 string, old, new byte) int { + l := len(s1) + if len(s2) < l { + l = len(s2) } - return splitPathX[0] > splitPathY[0] + for i := 0; i < l; i++ { + c1, c2 := s1[i], s2[i] + if c1 == old { + c1 = new + } + + if c2 == old { + c2 = new + } + + if c1 < c2 { + return -1 + } + + if c1 > c2 { + return +1 + } + } + + if len(s1) < len(s2) { + return -1 + } + + if len(s1) > len(s2) { + return +1 + } + + return 0 } From a405d3e88b8b455ac1e46e38a5cbaec23c64ccd8 Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Tue, 9 Aug 2016 17:42:26 -0700 Subject: [PATCH 06/19] Improve catalog enumerate runtime by an order of magnitude Signed-off-by: Edgar Lee --- registry/storage/catalog.go | 87 ++++++++++++++++---------------- registry/storage/catalog_test.go | 21 ++++++++ 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 0e119a88..7a8a2ac6 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -10,6 +10,10 @@ import ( "github.com/docker/distribution/registry/storage/driver" ) +// errFinishedWalk signals an early exit to the walk when the current query +// is satisfied. +var errFinishedWalk = errors.New("finished walk") + // Returns a list, or partial list, of repositories in the registry. // Because it's a quite expensive operation, it should only be used when building up // an initial set of repositories. @@ -25,25 +29,13 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri return 0, err } - // errFinishedWalk signals an early exit to the walk when the current query - // is satisfied. - errFinishedWalk := errors.New("finished walk") - err = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { - filePath := fileInfo.Path() - - // lop the base path off - repoPath := filePath[len(root)+1:] - - _, file := path.Split(repoPath) - if file == "_layers" { - repoPath = strings.TrimSuffix(repoPath, "/_layers") - if lessPath(last, repoPath) { - foundRepos = append(foundRepos, repoPath) - } - return ErrSkipDir - } else if strings.HasPrefix(file, "_") { - return ErrSkipDir + err := handleRepository(fileInfo, root, last, func(repoPath string) error { + foundRepos = append(foundRepos, repoPath) + return nil + }) + if err != nil { + return err } // if we've filled our array, no need to walk any further @@ -71,33 +63,16 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri // Enumerate applies ingester to each repository func (reg *registry) Enumerate(ctx context.Context, ingester func(string) error) error { - repoNameBuffer := make([]string, 100) - var last string - for { - n, err := reg.Repositories(ctx, repoNameBuffer, last) - if err != nil && err != io.EOF { - return err - } - - if n == 0 { - break - } - - last = repoNameBuffer[n-1] - for i := 0; i < n; i++ { - repoName := repoNameBuffer[i] - err = ingester(repoName) - if err != nil { - return err - } - } - - if err == io.EOF { - break - } + root, err := pathFor(repositoriesRootPathSpec{}) + if err != nil { + return err } - return nil + err = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { + return handleRepository(fileInfo, root, "", ingester) + }) + + return err } // lessPath returns true if one path a is less than path b. @@ -146,3 +121,29 @@ func compareReplaceInline(s1, s2 string, old, new byte) int { return 0 } + +// handleRepository calls function fn with a repository path if fileInfo +// has a path of a repository under root and that it is lexographically +// after last. Otherwise, it will return ErrSkipDir. This should be used +// with Walk to do handling with repositories in a storage. +func handleRepository(fileInfo driver.FileInfo, root, last string, fn func(repoPath string) error) error { + filePath := fileInfo.Path() + + // lop the base path off + repo := filePath[len(root)+1:] + + _, file := path.Split(repo) + if file == "_layers" { + repo = strings.TrimSuffix(repo, "/_layers") + if lessPath(last, repo) { + if err := fn(repo); err != nil { + return err + } + } + return ErrSkipDir + } else if strings.HasPrefix(file, "_") { + return ErrSkipDir + } + + return nil +} diff --git a/registry/storage/catalog_test.go b/registry/storage/catalog_test.go index 40fa909d..7fe133f6 100644 --- a/registry/storage/catalog_test.go +++ b/registry/storage/catalog_test.go @@ -113,7 +113,28 @@ func TestCatalogInParts(t *testing.T) { if !testEq(p, env.expected[chunkLen*2:chunkLen*3-1], numFilled) { t.Errorf("Expected catalog third chunk err") } +} +func TestCatalogEnumerate(t *testing.T) { + env := setupFS(t) + + var repos []string + repositoryEnumerator := env.registry.(distribution.RepositoryEnumerator) + err := repositoryEnumerator.Enumerate(env.ctx, func(repoName string) error { + repos = append(repos, repoName) + return nil + }) + if err != nil { + t.Errorf("Expected catalog enumerate err") + } + + if len(repos) != len(env.expected) { + t.Errorf("Expected catalog enumerate doesn't have correct number of values") + } + + if !testEq(repos, env.expected, len(env.expected)) { + t.Errorf("Expected catalog enumerate not over all values") + } } func testEq(a, b []string, size int) bool { From 8160a430be34e16abe38f8a48754c5e43fd23323 Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Thu, 14 Jul 2016 13:24:16 -0700 Subject: [PATCH 07/19] Handle new errors returned from catalog repository listing Signed-off-by: Edgar Lee --- registry/handlers/catalog.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/registry/handlers/catalog.go b/registry/handlers/catalog.go index 6ec1fe55..083ebfd0 100644 --- a/registry/handlers/catalog.go +++ b/registry/handlers/catalog.go @@ -6,9 +6,12 @@ import ( "io" "net/http" "net/url" + "reflect" "strconv" "github.com/docker/distribution/registry/api/errcode" + "github.com/docker/distribution/registry/storage" + "github.com/docker/distribution/registry/storage/driver" "github.com/gorilla/handlers" ) @@ -45,9 +48,10 @@ func (ch *catalogHandler) GetCatalog(w http.ResponseWriter, r *http.Request) { repos := make([]string, maxEntries) filled, err := ch.App.registry.Repositories(ch.Context, repos, lastEntry) - if err == io.EOF { + + if err == io.EOF || reflect.TypeOf(err) == reflect.TypeOf(driver.PathNotFoundError{}) { moreEntries = false - } else if err != nil { + } else if err != nil && err != storage.ErrFinishedWalk { ch.Errors = append(ch.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) return } From 45b84c95129de5f4bcf7f8c1e013d23ca9b9f136 Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Thu, 14 Jul 2016 15:03:18 -0700 Subject: [PATCH 08/19] Use typecast over reflect for error type checking Signed-off-by: Edgar Lee --- registry/handlers/catalog.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/handlers/catalog.go b/registry/handlers/catalog.go index 083ebfd0..4e95bfb0 100644 --- a/registry/handlers/catalog.go +++ b/registry/handlers/catalog.go @@ -6,7 +6,6 @@ import ( "io" "net/http" "net/url" - "reflect" "strconv" "github.com/docker/distribution/registry/api/errcode" @@ -48,8 +47,9 @@ func (ch *catalogHandler) GetCatalog(w http.ResponseWriter, r *http.Request) { repos := make([]string, maxEntries) filled, err := ch.App.registry.Repositories(ch.Context, repos, lastEntry) + _, pathNotFound := err.(driver.PathNotFoundError) - if err == io.EOF || reflect.TypeOf(err) == reflect.TypeOf(driver.PathNotFoundError{}) { + if err == io.EOF || pathNotFound { moreEntries = false } else if err != nil && err != storage.ErrFinishedWalk { ch.Errors = append(ch.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) From 12acdf0a6c1e56d965ac6eb395d2bce687bf22fc Mon Sep 17 00:00:00 2001 From: Edgar Lee Date: Fri, 5 Aug 2016 17:21:48 -0700 Subject: [PATCH 09/19] Stop ErrFinishedWalk from escaping from Repositories walk Signed-off-by: Edgar Lee --- registry/handlers/catalog.go | 3 +-- registry/storage/catalog.go | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/registry/handlers/catalog.go b/registry/handlers/catalog.go index 4e95bfb0..eca98468 100644 --- a/registry/handlers/catalog.go +++ b/registry/handlers/catalog.go @@ -9,7 +9,6 @@ import ( "strconv" "github.com/docker/distribution/registry/api/errcode" - "github.com/docker/distribution/registry/storage" "github.com/docker/distribution/registry/storage/driver" "github.com/gorilla/handlers" ) @@ -51,7 +50,7 @@ func (ch *catalogHandler) GetCatalog(w http.ResponseWriter, r *http.Request) { if err == io.EOF || pathNotFound { moreEntries = false - } else if err != nil && err != storage.ErrFinishedWalk { + } else if err != nil { ch.Errors = append(ch.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) return } diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 7a8a2ac6..eb669b77 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -29,6 +29,10 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri return 0, err } + // errFinishedWalk signals an early exit to the walk when the current query + // is satisfied. + errFinishedWalk := errors.New("finished walk") + err = Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error { err := handleRepository(fileInfo, root, last, func(repoPath string) error { foundRepos = append(foundRepos, repoPath) From 0a22649f662c924eadcc5fdaf6ee69453748606a Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 5 Oct 2016 17:47:12 -0700 Subject: [PATCH 10/19] Update to fix lint errors Context should use type values instead of strings. Updated direct calls to WithValue, but still other uses of string keys. Update Acl to ACL in s3 driver. Cherry-picked to release/2.5 branch Signed-off-by: Derek McGowan (github: dmcgowan) Signed-off-by: Misty Stanley-Jones --- contrib/token-server/main.go | 36 ++++++++++++++++++---------- registry/auth/silly/access_test.go | 2 +- registry/auth/token/token_test.go | 2 +- registry/handlers/app.go | 38 ++++++++++++++++++++---------- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/contrib/token-server/main.go b/contrib/token-server/main.go index edd894f4..f53d5c8d 100644 --- a/contrib/token-server/main.go +++ b/contrib/token-server/main.go @@ -176,6 +176,18 @@ func filterAccessList(ctx context.Context, scope string, requestedAccessList []a return grantedAccessList } +type acctSubject struct{} + +func (acctSubject) String() string { return "acctSubject" } + +type requestedAccess struct{} + +func (requestedAccess) String() string { return "requestedAccess" } + +type grantedAccess struct{} + +func (grantedAccess) String() string { return "grantedAccess" } + // getToken handles authenticating the request and authorizing access to the // requested scopes. func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *http.Request) { @@ -218,17 +230,17 @@ func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *h username := context.GetStringValue(ctx, "auth.user.name") - ctx = context.WithValue(ctx, "acctSubject", username) - ctx = context.WithLogger(ctx, context.GetLogger(ctx, "acctSubject")) + ctx = context.WithValue(ctx, acctSubject{}, username) + ctx = context.WithLogger(ctx, context.GetLogger(ctx, acctSubject{})) context.GetLogger(ctx).Info("authenticated client") - ctx = context.WithValue(ctx, "requestedAccess", requestedAccessList) - ctx = context.WithLogger(ctx, context.GetLogger(ctx, "requestedAccess")) + ctx = context.WithValue(ctx, requestedAccess{}, requestedAccessList) + ctx = context.WithLogger(ctx, context.GetLogger(ctx, requestedAccess{})) grantedAccessList := filterAccessList(ctx, username, requestedAccessList) - ctx = context.WithValue(ctx, "grantedAccess", grantedAccessList) - ctx = context.WithLogger(ctx, context.GetLogger(ctx, "grantedAccess")) + ctx = context.WithValue(ctx, grantedAccess{}, grantedAccessList) + ctx = context.WithLogger(ctx, context.GetLogger(ctx, grantedAccess{})) token, err := ts.issuer.CreateJWT(username, service, grantedAccessList) if err != nil { @@ -340,17 +352,17 @@ func (ts *tokenServer) postToken(ctx context.Context, w http.ResponseWriter, r * return } - ctx = context.WithValue(ctx, "acctSubject", subject) - ctx = context.WithLogger(ctx, context.GetLogger(ctx, "acctSubject")) + ctx = context.WithValue(ctx, acctSubject{}, subject) + ctx = context.WithLogger(ctx, context.GetLogger(ctx, acctSubject{})) context.GetLogger(ctx).Info("authenticated client") - ctx = context.WithValue(ctx, "requestedAccess", requestedAccessList) - ctx = context.WithLogger(ctx, context.GetLogger(ctx, "requestedAccess")) + ctx = context.WithValue(ctx, requestedAccess{}, requestedAccessList) + ctx = context.WithLogger(ctx, context.GetLogger(ctx, requestedAccess{})) grantedAccessList := filterAccessList(ctx, subject, requestedAccessList) - ctx = context.WithValue(ctx, "grantedAccess", grantedAccessList) - ctx = context.WithLogger(ctx, context.GetLogger(ctx, "grantedAccess")) + ctx = context.WithValue(ctx, grantedAccess{}, grantedAccessList) + ctx = context.WithLogger(ctx, context.GetLogger(ctx, grantedAccess{})) token, err := ts.issuer.CreateJWT(subject, service, grantedAccessList) if err != nil { diff --git a/registry/auth/silly/access_test.go b/registry/auth/silly/access_test.go index a7c14cb9..0a5103e6 100644 --- a/registry/auth/silly/access_test.go +++ b/registry/auth/silly/access_test.go @@ -16,7 +16,7 @@ func TestSillyAccessController(t *testing.T) { } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := context.WithValue(nil, "http.request", r) + ctx := context.WithRequest(context.Background(), r) authCtx, err := ac.Authorized(ctx) if err != nil { switch err := err.(type) { diff --git a/registry/auth/token/token_test.go b/registry/auth/token/token_test.go index 827dbbd7..3140d473 100644 --- a/registry/auth/token/token_test.go +++ b/registry/auth/token/token_test.go @@ -284,7 +284,7 @@ func TestAccessController(t *testing.T) { Action: "baz", } - ctx := context.WithValue(nil, "http.request", req) + ctx := context.WithRequest(context.Background(), req) authCtx, err := accessController.Authorized(ctx, testAccess) challenge, ok := err.(auth.Challenge) if !ok { diff --git a/registry/handlers/app.go b/registry/handlers/app.go index 33f49670..b80b8b9d 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -425,6 +425,8 @@ func (app *App) configureEvents(configuration *configuration.Configuration) { } } +type redisStartAtKey struct{} + func (app *App) configureRedis(configuration *configuration.Configuration) { if configuration.Redis.Addr == "" { ctxu.GetLogger(app).Infof("redis not configured") @@ -434,11 +436,11 @@ func (app *App) configureRedis(configuration *configuration.Configuration) { pool := &redis.Pool{ Dial: func() (redis.Conn, error) { // TODO(stevvooe): Yet another use case for contextual timing. - ctx := context.WithValue(app, "redis.connect.startedat", time.Now()) + ctx := context.WithValue(app, redisStartAtKey{}, time.Now()) done := func(err error) { logger := ctxu.GetLoggerWithField(ctx, "redis.connect.duration", - ctxu.Since(ctx, "redis.connect.startedat")) + ctxu.Since(ctx, redisStartAtKey{})) if err != nil { logger.Errorf("redis: error connecting: %v", err) } else { @@ -671,6 +673,18 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler { }) } +type errCodeKey struct{} + +func (errCodeKey) String() string { return "err.code" } + +type errMessageKey struct{} + +func (errMessageKey) String() string { return "err.message" } + +type errDetailKey struct{} + +func (errDetailKey) String() string { return "err.detail" } + func (app *App) logError(context context.Context, errors errcode.Errors) { for _, e1 := range errors { var c ctxu.Context @@ -678,23 +692,23 @@ func (app *App) logError(context context.Context, errors errcode.Errors) { switch e1.(type) { case errcode.Error: e, _ := e1.(errcode.Error) - c = ctxu.WithValue(context, "err.code", e.Code) - c = ctxu.WithValue(c, "err.message", e.Code.Message()) - c = ctxu.WithValue(c, "err.detail", e.Detail) + c = ctxu.WithValue(context, errCodeKey{}, e.Code) + c = ctxu.WithValue(c, errMessageKey{}, e.Code.Message()) + c = ctxu.WithValue(c, errDetailKey{}, e.Detail) case errcode.ErrorCode: e, _ := e1.(errcode.ErrorCode) - c = ctxu.WithValue(context, "err.code", e) - c = ctxu.WithValue(c, "err.message", e.Message()) + c = ctxu.WithValue(context, errCodeKey{}, e) + c = ctxu.WithValue(c, errMessageKey{}, e.Message()) default: // just normal go 'error' - c = ctxu.WithValue(context, "err.code", errcode.ErrorCodeUnknown) - c = ctxu.WithValue(c, "err.message", e1.Error()) + c = ctxu.WithValue(context, errCodeKey{}, errcode.ErrorCodeUnknown) + c = ctxu.WithValue(c, errMessageKey{}, e1.Error()) } c = ctxu.WithLogger(c, ctxu.GetLogger(c, - "err.code", - "err.message", - "err.detail")) + errCodeKey{}, + errMessageKey{}, + errDetailKey{})) ctxu.GetResponseLogger(c).Errorf("response completed with error") } } From a8402a225369bb32e57285fbe1acf64efd8e62ee Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Fri, 14 Oct 2016 13:13:32 -0700 Subject: [PATCH 11/19] Merge pull request #1985 from johndmulhausen/master Remove old documentation source, add README on migration (cherry picked from commit c372264f17c47d91c7c614162bab6121edbb9c30) Signed-off-by: Misty Stanley-Jones (cherry picked from commit f1219102a421c15f5c6fc437c1e1ec951424d9b5) Signed-off-by: Misty Stanley-Jones --- docs/Dockerfile | 9 - docs/Makefile | 38 --- docs/README.md | 16 + docs/architecture.md | 54 ---- docs/compatibility.md | 84 ----- docs/deploying.md | 237 -------------- docs/deprecated.md | 27 -- docs/garbage-collection.md | 137 -------- docs/glossary.md | 70 ----- docs/help.md | 24 -- docs/images/notifications.gliffy | 1 - docs/images/notifications.png | Bin 37836 -> 0 bytes docs/images/notifications.svg | 1 - docs/index.md | 67 ---- docs/insecure.md | 114 ------- docs/introduction.md | 55 ---- docs/menu.md | 23 -- docs/migration.md | 30 -- docs/notifications.md | 350 --------------------- docs/recipes/apache.md | 215 ------------- docs/recipes/index.md | 37 --- docs/recipes/menu.md | 21 -- docs/recipes/mirror.md | 74 ----- docs/recipes/nginx.md | 190 ----------- docs/recipes/osx-setup-guide.md | 81 ----- docs/recipes/osx/com.docker.registry.plist | 42 --- docs/recipes/osx/config.yml | 16 - docs/storage-drivers/azure.md | 78 ----- docs/storage-drivers/filesystem.md | 24 -- docs/storage-drivers/gcs.md | 78 ----- docs/storage-drivers/index.md | 66 ---- docs/storage-drivers/inmemory.md | 23 -- docs/storage-drivers/menu.md | 13 - docs/storage-drivers/oss.md | 126 -------- docs/storage-drivers/s3.md | 268 ---------------- docs/storage-drivers/swift.md | 246 --------------- 36 files changed, 16 insertions(+), 2919 deletions(-) delete mode 100644 docs/Dockerfile delete mode 100644 docs/Makefile create mode 100644 docs/README.md delete mode 100644 docs/architecture.md delete mode 100644 docs/compatibility.md delete mode 100644 docs/deploying.md delete mode 100644 docs/deprecated.md delete mode 100644 docs/garbage-collection.md delete mode 100644 docs/glossary.md delete mode 100644 docs/help.md delete mode 100644 docs/images/notifications.gliffy delete mode 100644 docs/images/notifications.png delete mode 100644 docs/images/notifications.svg delete mode 100644 docs/index.md delete mode 100644 docs/insecure.md delete mode 100644 docs/introduction.md delete mode 100644 docs/menu.md delete mode 100644 docs/migration.md delete mode 100644 docs/notifications.md delete mode 100644 docs/recipes/apache.md delete mode 100644 docs/recipes/index.md delete mode 100644 docs/recipes/menu.md delete mode 100644 docs/recipes/mirror.md delete mode 100644 docs/recipes/nginx.md delete mode 100644 docs/recipes/osx-setup-guide.md delete mode 100644 docs/recipes/osx/com.docker.registry.plist delete mode 100644 docs/recipes/osx/config.yml delete mode 100644 docs/storage-drivers/azure.md delete mode 100644 docs/storage-drivers/filesystem.md delete mode 100644 docs/storage-drivers/gcs.md delete mode 100644 docs/storage-drivers/index.md delete mode 100644 docs/storage-drivers/inmemory.md delete mode 100644 docs/storage-drivers/menu.md delete mode 100644 docs/storage-drivers/oss.md delete mode 100644 docs/storage-drivers/s3.md delete mode 100644 docs/storage-drivers/swift.md diff --git a/docs/Dockerfile b/docs/Dockerfile deleted file mode 100644 index fcc63422..00000000 --- a/docs/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM docs/base:oss -MAINTAINER Docker Docs - -ENV PROJECT=registry - -# To get the git info for this repo -COPY . /src -RUN rm -rf /docs/content/$PROJECT/ -COPY . /docs/content/$PROJECT/ diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 585bc871..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -.PHONY: all default docs docs-build docs-shell shell test - -# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs) -DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) - -# to allow `make DOCSPORT=9000 docs` -DOCSPORT := 8000 - -# Get the IP ADDRESS -DOCKER_IP=$(shell python -c "import urlparse ; print urlparse.urlparse('$(DOCKER_HOST)').hostname or ''") -HUGO_BASE_URL=$(shell test -z "$(DOCKER_IP)" && echo localhost || echo "$(DOCKER_IP)") -HUGO_BIND_IP=0.0.0.0 - -GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) -GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g") -DOCKER_DOCS_IMAGE := registry-docs$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN)) - -DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT) -e AWS_S3_BUCKET -e NOCACHE - -# for some docs workarounds (see below in "docs-build" target) -GITCOMMIT := $(shell git rev-parse --short HEAD 2>/dev/null) - -default: docs - -docs: docs-build - $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 -e DOCKERHOST "$(DOCKER_DOCS_IMAGE)" hugo server --port=$(DOCSPORT) --baseUrl=$(HUGO_BASE_URL) --bind=$(HUGO_BIND_IP) - -docs-draft: docs-build - $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 -e DOCKERHOST "$(DOCKER_DOCS_IMAGE)" hugo server --buildDrafts="true" --port=$(DOCSPORT) --baseUrl=$(HUGO_BASE_URL) --bind=$(HUGO_BIND_IP) - -docs-shell: docs-build - $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash - -docs-build: - docker build -t "$(DOCKER_DOCS_IMAGE)" . - -test: docs-build - $(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..b26dc375 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +# The docs have been moved! + +The documentation for Registry has been merged into +[the general documentation repo](https://github.com/docker/docker.github.io). +Commit history has been preserved. + +The docs for Registry are now here: +https://github.com/docker/docker.github.io/tree/master/registry + +> Note: The definitive [./spec directory](spec/) directory and +[configuration.md](configuration.md) file will be maintained in this repository +and be refreshed periodically in +[the general documentation repo](https://github.com/docker/docker.github.io). + +As always, the docs in the general repo remain open-source and we appreciate +your feedback and pull requests! diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 39251760..00000000 --- a/docs/architecture.md +++ /dev/null @@ -1,54 +0,0 @@ - - -# Architecture - -## Design -**TODO(stevvooe):** Discuss the architecture of the registry, internally and externally, in a few different deployment scenarios. - -### Eventual Consistency - -> **NOTE:** This section belongs somewhere, perhaps in a design document. We -> are leaving this here so the information is not lost. - -Running the registry on eventually consistent backends has been part of the -design from the beginning. This section covers some of the approaches to -dealing with this reality. - -There are a few classes of issues that we need to worry about when -implementing something on top of the storage drivers: - -1. Read-After-Write consistency (see this [article on - s3](http://shlomoswidler.com/2009/12/read-after-write-consistency-in-amazon.html)). -2. [Write-Write Conflicts](http://en.wikipedia.org/wiki/Write%E2%80%93write_conflict). - -In reality, the registry must worry about these kinds of errors when doing the -following: - -1. Accepting data into a temporary upload file may not have latest data block - yet (read-after-write). -2. Moving uploaded data into its blob location (write-write race). -3. Modifying the "current" manifest for given tag (write-write race). -4. A whole slew of operations around deletes (read-after-write, delete-write - races, garbage collection, etc.). - -The backend path layout employs a few techniques to avoid these problems: - -1. Large writes are done to private upload directories. This alleviates most - of the corruption potential under multiple writers by avoiding multiple - writers. -2. Constraints in storage driver implementations, such as support for writing - after the end of a file to extend it. -3. Digest verification to avoid data corruption. -4. Manifest files are stored by digest and cannot change. -5. All other non-content files (links, hashes, etc.) are written as an atomic - unit. Anything that requires additions and deletions is broken out into - separate "files". Last writer still wins. - -Unfortunately, one must play this game when trying to build something like -this on top of eventually consistent storage systems. If we run into serious -problems, we can wrap the storagedrivers in a shared consistency layer but -that would increase complexity and hinder registry cluster performance. diff --git a/docs/compatibility.md b/docs/compatibility.md deleted file mode 100644 index cba7e378..00000000 --- a/docs/compatibility.md +++ /dev/null @@ -1,84 +0,0 @@ - - -# Registry Compatibility - -## Synopsis -*If a manifest is pulled by _digest_ from a registry 2.3 with Docker Engine 1.9 -and older, and the manifest was pushed with Docker Engine 1.10, a security check -will cause the Engine to receive a manifest it cannot use and the pull will fail.* - -## Registry Manifest Support - -Historically, the registry has supported a [single manifest type](./spec/manifest-v2-1.md) -known as _Schema 1_. - -With the move toward multiple architecture images the distribution project -introduced two new manifest types: Schema 2 manifests and manifest lists. The -registry 2.3 supports all three manifest types and in order to be compatible -with older Docker engines will, in certain cases, do an on-the-fly -transformation of a manifest before serving the JSON in the response. - -This conversion has some implications for pulling manifests by digest and this -document enumerate these implications. - - -## Content Addressable Storage (CAS) - -Manifests are stored and retrieved in the registry by keying off a digest -representing a hash of the contents. One of the advantages provided by CAS is -security: if the contents are changed, then the digest will no longer match. -This prevents any modification of the manifest by a MITM attack or an untrusted -third party. - -When a manifest is stored by the registry, this digest is returned in the HTTP -response headers and, if events are configured, delivered within the event. The -manifest can either be retrieved by the tag, or this digest. - -For registry versions 2.2.1 and below, the registry will always store and -serve _Schema 1_ manifests. The Docker Engine 1.10 will first -attempt to send a _Schema 2_ manifest, falling back to sending a -Schema 1 type manifest when it detects that the registry does not -support the new version. - - -## Registry v2.3 - -### Manifest Push with Docker 1.9 and Older - -The Docker Engine will construct a _Schema 1_ manifest which the -registry will persist to disk. - -When the manifest is pulled by digest or tag with any docker version, a -_Schema 1_ manifest will be returned. - -### Manifest Push with Docker 1.10 - -The docker engine will construct a _Schema 2_ manifest which the -registry will persist to disk. - -When the manifest is pulled by digest or tag with Docker Engine 1.10, a -_Schema 2_ manifest will be returned. The Docker Engine 1.10 -understands the new manifest format. - -When the manifest is pulled by *tag* with Docker Engine 1.9 and older, the -manifest is converted on-the-fly to _Schema 1_ and sent in the -response. The Docker Engine 1.9 is compatible with this older format. - -*When the manifest is pulled by _digest_ with Docker Engine 1.9 and older, the -same rewriting process will not happen in the registry. If this were to happen -the digest would no longer match the hash of the manifest and would violate the -constraints of CAS.* - -For this reason if a manifest is pulled by _digest_ from a registry 2.3 with Docker -Engine 1.9 and older, and the manifest was pushed with Docker Engine 1.10, a -security check will cause the Engine to receive a manifest it cannot use and the -pull will fail. diff --git a/docs/deploying.md b/docs/deploying.md deleted file mode 100644 index 2e8ce69e..00000000 --- a/docs/deploying.md +++ /dev/null @@ -1,237 +0,0 @@ - - -# Deploying a registry server - -You need to [install Docker version 1.6.0 or newer](/engine/installation/index.md). - -## Running on localhost - -Start your registry: - - docker run -d -p 5000:5000 --restart=always --name registry registry:2 - -You can now use it with docker. - -Get any image from the hub and tag it to point to your registry: - - docker pull ubuntu && docker tag ubuntu localhost:5000/ubuntu - -... then push it to your registry: - - docker push localhost:5000/ubuntu - -... then pull it back from your registry: - - docker pull localhost:5000/ubuntu - -To stop your registry, you would: - - docker stop registry && docker rm -v registry - -## Storage - -By default, your registry data is persisted as a [docker volume](/engine/tutorials/dockervolumes.md) on the host filesystem. Properly understanding volumes is essential if you want to stick with a local filesystem storage. - -Specifically, you might want to point your volume location to a specific place in order to more easily access your registry data. To do so you can: - - docker run -d -p 5000:5000 --restart=always --name registry \ - -v `pwd`/data:/var/lib/registry \ - registry:2 - -### Alternatives - -You should usually consider using [another storage backend](./storage-drivers/index.md) instead of the local filesystem. Use the [storage configuration options](./configuration.md#storage) to configure an alternate storage backend. - -Using one of these will allow you to more easily scale your registry, and leverage your storage redundancy and availability features. - -## Running a domain registry - -While running on `localhost` has its uses, most people want their registry to be more widely available. To do so, the Docker engine requires you to secure it using TLS, which is conceptually very similar to configuring your web server with SSL. - -### Get a certificate - -Assuming that you own the domain `myregistrydomain.com`, and that its DNS record points to the host where you are running your registry, you first need to get a certificate from a CA. - -Create a `certs` directory: - - mkdir -p certs - -Then move and/or rename your crt file to: `certs/domain.crt`, and your key file to: `certs/domain.key`. - -Make sure you stopped your registry from the previous steps, then start your registry again with TLS enabled: - - docker run -d -p 5000:5000 --restart=always --name registry \ - -v `pwd`/certs:/certs \ - -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ - -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ - registry:2 - -You should now be able to access your registry from another docker host: - - docker pull ubuntu - docker tag ubuntu myregistrydomain.com:5000/ubuntu - docker push myregistrydomain.com:5000/ubuntu - docker pull myregistrydomain.com:5000/ubuntu - -#### Gotcha - -A certificate issuer may supply you with an *intermediate* certificate. In this case, you must combine your certificate with the intermediate's to form a *certificate bundle*. You can do this using the `cat` command: - - cat domain.crt intermediate-certificates.pem > certs/domain.crt - -### Let's Encrypt - -The registry supports using Let's Encrypt to automatically obtain a browser-trusted certificate. For more -information on Let's Encrypt, see [https://letsencrypt.org/how-it-works/](https://letsencrypt.org/how-it-works/) and the relevant section of the [registry configuration](configuration.md#letsencrypt). - -### Alternatives - -While rarely advisable, you may want to use self-signed certificates instead, or use your registry in an insecure fashion. You will find instructions [here](insecure.md). - -## Load Balancing Considerations - -One may want to use a load balancer to distribute load, terminate TLS or -provide high availability. While a full load balancing setup is outside the -scope of this document, there are a few considerations that can make the process -smoother. - -The most important aspect is that a load balanced cluster of registries must -share the same resources. For the current version of the registry, this means -the following must be the same: - - - Storage Driver - - HTTP Secret - - Redis Cache (if configured) - -If any of these are different, the registry will have trouble serving requests. -As an example, if you're using the filesystem driver, all registry instances -must have access to the same filesystem root, which means they should be in -the same machine. For other drivers, such as s3 or azure, they should be -accessing the same resource, and will likely share an identical configuration. -The _HTTP Secret_ coordinates uploads, so also must be the same across -instances. Configuring different redis instances will work (at the time -of writing), but will not be optimal if the instances are not shared, causing -more requests to be directed to the backend. - -#### Important/Required HTTP-Headers -Getting the headers correct is very important. For all responses to any -request under the "/v2/" url space, the `Docker-Distribution-API-Version` -header should be set to the value "registry/2.0", even for a 4xx response. -This header allows the docker engine to quickly resolve authentication realms -and fallback to version 1 registries, if necessary. Confirming this is setup -correctly can help avoid problems with fallback. - -In the same train of thought, you must make sure you are properly sending the -`X-Forwarded-Proto`, `X-Forwarded-For` and `Host` headers to their "client-side" -values. Failure to do so usually makes the registry issue redirects to internal -hostnames or downgrading from https to http. - -A properly secured registry should return 401 when the "/v2/" endpoint is hit -without credentials. The response should include a `WWW-Authenticate` -challenge, providing guidance on how to authenticate, such as with basic auth -or a token service. If the load balancer has health checks, it is recommended -to configure it to consider a 401 response as healthy and any other as down. -This will secure your registry by ensuring that configuration problems with -authentication don't accidentally expose an unprotected registry. If you're -using a less sophisticated load balancer, such as Amazon's Elastic Load -Balancer, that doesn't allow one to change the healthy response code, health -checks can be directed at "/", which will always return a `200 OK` response. - -## Restricting access - -Except for registries running on secure local networks, registries should always implement access restrictions. - -### Native basic auth - -The simplest way to achieve access restriction is through basic authentication (this is very similar to other web servers' basic authentication mechanism). - -> **Warning**: You **cannot** use authentication with an insecure registry. You have to [configure TLS first](#running-a-domain-registry) for this to work. - -First create a password file with one entry for the user "testuser", with password "testpassword": - - mkdir auth - docker run --entrypoint htpasswd registry:2 -Bbn testuser testpassword > auth/htpasswd - -Make sure you stopped your registry from the previous step, then start it again: - - docker run -d -p 5000:5000 --restart=always --name registry \ - -v `pwd`/auth:/auth \ - -e "REGISTRY_AUTH=htpasswd" \ - -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ - -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ - -v `pwd`/certs:/certs \ - -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ - -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ - registry:2 - -You should now be able to: - - docker login myregistrydomain.com:5000 - -And then push and pull images as an authenticated user. - -#### Gotcha - -Seeing X509 errors is usually a sign you are trying to use self-signed certificates, and failed to [configure your docker daemon properly](insecure.md). - -### Alternatives - -1. You may want to leverage more advanced basic auth implementations through a proxy design, in front of the registry. You will find examples of such patterns in the [recipes list](recipes/index.md). - -2. Alternatively, the Registry also supports delegated authentication, redirecting users to a specific, trusted token server. That approach requires significantly more investment, and only makes sense if you want to fully configure ACLs and more control over the Registry integration into your global authorization and authentication systems. - -You will find [background information here](spec/auth/token.md), and [configuration information here](configuration.md#auth). - -Beware that you will have to implement your own authentication service for this to work, or leverage a third-party implementation. - -## Managing with Compose - -As your registry configuration grows more complex, dealing with it can quickly become tedious. - -It's highly recommended to use [Docker Compose](/compose/index.md) to facilitate operating your registry. - -Here is a simple `docker-compose.yml` example that condenses everything explained so far: - -``` -registry: - restart: always - image: registry:2 - ports: - - 5000:5000 - environment: - REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt - REGISTRY_HTTP_TLS_KEY: /certs/domain.key - REGISTRY_AUTH: htpasswd - REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd - REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm - volumes: - - /path/data:/var/lib/registry - - /path/certs:/certs - - /path/auth:/auth -``` - -> **Warning**: replace `/path` by whatever directory that holds your `certs` and `auth` folder from above. - -You can then start your registry with a simple - - docker-compose up -d - -## Next - -You will find more specific and advanced informations in the following sections: - - - [Configuration reference](configuration.md) - - [Working with notifications](notifications.md) - - [Advanced "recipes"](recipes/index.md) - - [Registry API](spec/api.md) - - [Storage driver model](storage-drivers/index.md) - - [Token authentication](spec/auth/token.md) diff --git a/docs/deprecated.md b/docs/deprecated.md deleted file mode 100644 index 73bde497..00000000 --- a/docs/deprecated.md +++ /dev/null @@ -1,27 +0,0 @@ - - -# Docker Registry Deprecation - -This document details functionality or components which are deprecated within -the registry. - -### v2.5.0 - -The signature store has been removed from the registry. Since `v2.4.0` it has -been possible to configure the registry to generate manifest signatures rather -than load them from storage. In this version of the registry this becomes -the default behavior. Signatures which are attached to manifests on put are -not stored in the registry. This does not alter the functional behavior of -the registry. - -Old signatures blobs can be removed from the registry storage by running the -garbage-collect subcommand. diff --git a/docs/garbage-collection.md b/docs/garbage-collection.md deleted file mode 100644 index 2d03e787..00000000 --- a/docs/garbage-collection.md +++ /dev/null @@ -1,137 +0,0 @@ - - -# Garbage Collection - -As of v2.4.0 a garbage collector command is included within the registry binary. -This document describes what this command does and how and why it should be used. - -## What is Garbage Collection? - -From [wikipedia](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)): - -"In computer science, garbage collection (GC) is a form of automatic memory management. The -garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by -objects that are no longer in use by the program." - -In the context of the Docker registry, garbage collection is the process of -removing blobs from the filesystem which are no longer referenced by a -manifest. Blobs can include both layers and manifests. - - -## Why Garbage Collection? - -Registry data can occupy considerable amounts of disk space and freeing up -this disk space is an oft-requested feature. Additionally for reasons of security it -can be desirable to ensure that certain layers no longer exist on the filesystem. - - -## Garbage Collection in the Registry - -Filesystem layers are stored by their content address in the Registry. This -has many advantages, one of which is that data is stored once and referred to by manifests. -See [here](compatibility.md#content-addressable-storage-cas) for more details. - -Layers are therefore shared amongst manifests; each manifest maintains a reference -to the layer. As long as a layer is referenced by one manifest, it cannot be garbage -collected. - -Manifests and layers can be 'deleted` with the registry API (refer to the API -documentation [here](spec/api.md#deleting-a-layer) and -[here](spec/api.md#deleting-an-image) for details). This API removes references -to the target and makes them eligible for garbage collection. It also makes them -unable to be read via the API. - -If a layer is deleted it will be removed from the filesystem when garbage collection -is run. If a manifest is deleted the layers to which it refers will be removed from -the filesystem if no other manifests refers to them. - - -### Example - -In this example manifest A references two layers: `a` and `b`. Manifest `B` references -layers `a` and `c`. In this state, nothing is eligible for garbage collection: - -``` -A -----> a <----- B - \--> b | - c <--/ -``` - -Manifest B is deleted via the API: - -``` -A -----> a B - \--> b - c -``` - -In this state layer `c` no longer has a reference and is eligible for garbage -collection. Layer `a` had one reference removed but will not be garbage -collected as it is still referenced by manifest `A`. The blob representing -manifest `B` will also be eligible for garbage collection. - -After garbage collection has been run manifest `A` and its blobs remain. - -``` -A -----> a - \--> b -``` - - -## How Garbage Collection works - -Garbage collection runs in two phases. First, in the 'mark' phase, the process -scans all the manifests in the registry. From these manifests, it constructs a -set of content address digests. This set is the 'mark set' and denotes the set -of blobs to *not* delete. Secondly, in the 'sweep' phase, the process scans all -the blobs and if a blob's content address digest is not in the mark set, the -process will delete it. - - -> **NOTE** You should ensure that the registry is in read-only mode or not running at -> all. If you were to upload an image while garbage collection is running, there is the -> risk that the image's layers will be mistakenly deleted, leading to a corrupted image. - -This type of garbage collection is known as stop-the-world garbage collection. In future -registry versions the intention is that garbage collection will be an automated background -action and this manual process will no longer apply. - - - -# Running garbage collection - -Garbage collection can be run as follows - -`bin/registry garbage-collect [--dry-run] /path/to/config.yml` - -The garbage-collect command accepts a `--dry-run` parameter, which will print the progress -of the mark and sweep phases without removing any data. Running with a log leve of `info` -will give a clear indication of what will and will not be deleted. - -_Sample output from a dry run garbage collection with registry log level set to `info`_ - -``` -hello-world -hello-world: marking manifest sha256:fea8895f450959fa676bcc1df0611ea93823a735a01205fd8622846041d0c7cf -hello-world: marking blob sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb -hello-world: marking blob sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 -hello-world: marking configuration sha256:690ed74de00f99a7d00a98a5ad855ac4febd66412be132438f9b8dbd300a937d -ubuntu - -4 blobs marked, 5 blobs eligible for deletion -blob eligible for deletion: sha256:28e09fddaacbfc8a13f82871d9d66141a6ed9ca526cb9ed295ef545ab4559b81 -blob eligible for deletion: sha256:7e15ce58ccb2181a8fced7709e9893206f0937cc9543bc0c8178ea1cf4d7e7b5 -blob eligible for deletion: sha256:87192bdbe00f8f2a62527f36bb4c7c7f4eaf9307e4b87e8334fb6abec1765bcb -blob eligible for deletion: sha256:b549a9959a664038fc35c155a95742cf12297672ca0ae35735ec027d55bf4e97 -blob eligible for deletion: sha256:f251d679a7c61455f06d793e43c06786d7766c88b8c24edf242b2c08e3c3f599 -``` - diff --git a/docs/glossary.md b/docs/glossary.md deleted file mode 100644 index 8159b520..00000000 --- a/docs/glossary.md +++ /dev/null @@ -1,70 +0,0 @@ - - -# Glossary - -This page contains definitions for distribution related terms. - -
-

Blob

-
-
A blob is any kind of content that is stored by a Registry under a content-addressable identifier (a "digest").
-

- Layers are a good example of "blobs". -

-
- -

Image

-
-
An image is a named set of immutable data from which a Docker container can be created.
-

- An image is represented by a json file called a manifest, and is conceptually a set of layers. - - Image names indicate the location where they can be pulled from and pushed to, as they usually start with a registry domain name and port. - -

-
- -

Layer

-
-
A layer is a tar archive bundling partial content from a filesystem.
-

- Layers from an image are usually extracted in order on top of each other to make up a root filesystem from which containers run out. -

-
- -

Manifest

-
A manifest is the JSON representation of an image.
- -

Namespace

-
A namespace is a collection of repositories with a common name prefix.
-

- The namespace with an empty prefix is considered the Global Namespace. -

-
- -

Registry

-
A registry is a service that let you store and deliver images.
-
- -

Repository

-
-
A repository is a set of data containing all versions of a given image.
-
- -

Scope

-
A scope is the portion of a namespace onto which a given authorization token is granted.
- -

Tag

-
A tag is conceptually a "version" of a named image.
-

- Example: `docker pull myimage:latest` instructs docker to pull the image "myimage" in version "latest". -

- -
- - -
diff --git a/docs/help.md b/docs/help.md deleted file mode 100644 index 77ec378f..00000000 --- a/docs/help.md +++ /dev/null @@ -1,24 +0,0 @@ - - -# Getting help - -If you need help, or just want to chat, you can reach us: - -- on irc: `#docker-distribution` on freenode -- on the [mailing list](https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution) (mail at ) - -If you want to report a bug: - -- be sure to first read about [how to contribute](https://github.com/docker/distribution/blob/master/CONTRIBUTING.md) -- you can then do so on the [GitHub project bugtracker](https://github.com/docker/distribution/issues) - -You can also find out more about the Docker's project [Getting Help resources](/opensource/get-help.md). diff --git a/docs/images/notifications.gliffy b/docs/images/notifications.gliffy deleted file mode 100644 index 5ecf4c3a..00000000 --- a/docs/images/notifications.gliffy +++ /dev/null @@ -1 +0,0 @@ -{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":737,"height":630,"nodeIndex":171,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":290,"y":83},"max":{"x":736.5,"y":630}},"objects":[{"x":699.0,"y":246.0,"rotation":0.0,"id":166,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-30.0,-12.0],[-30.0,59.5],[33.0,59.5],[33.0,131.0]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":632.0,"y":243.0,"rotation":0.0,"id":165,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":28,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-25.0,-11.0],[-25.0,64.5],[-88.0,64.5],[-88.0,140.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":512.0,"y":203.0,"rotation":0.0,"id":161,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-19.0,-3.0],[79.12746812182615,-3.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":589.9999999999999,"y":167.5,"rotation":0.0,"id":143,"width":101.11111111111111,"height":65.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.rectangle","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.722222222222222,"y":0.0,"rotation":0.0,"id":144,"width":99.66666666666663,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Broadcaster

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":290.0,"y":105.0,"rotation":0.0,"id":160,"width":210.0,"height":190.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":26,"lockAspectRatio":false,"lockShape":false,"children":[{"x":12.92581625076238,"y":17.018834253729665,"rotation":0.0,"id":155,"width":189.57418374923762,"height":151.48116574627034,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":25,"lockAspectRatio":false,"lockShape":false,"children":[{"x":97.57418374923762,"y":58.481165746270335,"rotation":90.0,"id":151,"width":149.0,"height":37.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_bottom","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":153,"magnitude":1},{"id":154,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":8.0,"rotation":0.0,"id":152,"width":149.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":151,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":8.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":151,"magnitude":1},{"id":154,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":151,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":153,"width":149.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":151,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":10,"paddingBottom":8,"paddingLeft":10,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Listener

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":67.5,"y":1.0,"rotation":0.0,"id":154,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":152,"px":0.5,"py":0.0,"xOffset":-7.0,"yOffset":-7.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_bottom","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":10.074195639419855,"y":17.481165746270335,"rotation":0.0,"id":150,"width":120.0,"height":119.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":20,"lockAspectRatio":false,"lockShape":false,"children":[{"x":1.0,"y":80.5,"rotation":0.0,"id":133,"width":117.0,"height":38.5,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_bottom","order":16,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":135,"magnitude":1},{"id":136,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":8.0,"rotation":0.0,"id":134,"width":117.0,"height":30.5,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":133,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":8.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":133,"magnitude":1},{"id":136,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":133,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":135,"width":117.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":133,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":10,"paddingBottom":8,"paddingLeft":10,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

handler

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":51.5,"y":1.0,"rotation":0.0,"id":136,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":134,"px":0.5,"py":0.0,"xOffset":-7.0,"yOffset":-7.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_bottom","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":0.0,"y":39.0,"rotation":0.0,"id":129,"width":120.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":12,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":131,"magnitude":1},{"id":132,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":130,"width":120.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":129,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":129,"magnitude":1},{"id":132,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":129,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":131,"width":120.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":129,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":53.0,"y":31.0,"rotation":0.0,"id":132,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":130,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":0.0,"y":0.0,"rotation":0.0,"id":125,"width":120.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":127,"magnitude":1},{"id":128,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":126,"width":120.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":125,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":125,"magnitude":1},{"id":128,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":125,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":127,"width":120.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":125,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

request

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":53.0,"y":31.0,"rotation":0.0,"id":128,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":126,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]}]},{"x":0.5154455517800614,"y":0.5154455517799761,"rotation":90.39513704250749,"id":145,"width":150.0,"height":150.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_bottom","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":147,"magnitude":1},{"id":148,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":8.0,"rotation":0.0,"id":146,"width":150.0,"height":142.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":145,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":8.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":145,"magnitude":1},{"id":148,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":145,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":147,"width":150.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":145,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":10,"paddingBottom":8,"paddingLeft":10,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":68.0,"y":0.9999999999999432,"rotation":0.0,"id":148,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":146,"px":0.5,"py":0.0,"xOffset":-7.0,"yOffset":-7.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_bottom","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]}]},{"x":0.0,"y":0.0,"rotation":0.0,"id":156,"width":210.0,"height":190.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#434343","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":159,"width":206.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Registry instance

","tid":null,"valign":"middle","vposition":"above","hposition":"none"}}}]}]},{"x":473.0,"y":525.0,"rotation":0.0,"id":115,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":69,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":68,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":109,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,4.5],[2.0,11.533649282003012],[2.0,18.567298564006137],[2.0,25.60094784600915]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":665.0,"y":530.0,"rotation":0.0,"id":114,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":68,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":100,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":112,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-2.0,-0.5],[-2.0,6.533649282003012],[-2.0,13.567298564006137],[-2.0,20.60094784600915]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":598.0,"y":550.0,"rotation":0.0,"id":112,"width":120.0,"height":80.0,"uid":"com.gliffy.shape.network.network_v3.home.cloud","order":66,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cloud.network_v3","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#000000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":113,"width":116.00000000000001,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Remote

Endpoint_N

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":420.0,"y":550.0,"rotation":0.0,"id":109,"width":120.0,"height":80.0,"uid":"com.gliffy.shape.network.network_v3.home.cloud","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cloud.network_v3","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#000000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":116.00000000000001,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Remote

Endpoint_1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":540.0,"y":438.5,"rotation":0.0,"id":104,"width":50.0,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":63,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

. . .

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[]},{"x":410.0,"y":379.5,"rotation":0.0,"id":103,"width":130.0,"height":150.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":62,"lockAspectRatio":false,"lockShape":false,"children":[{"x":15.0,"y":20.0,"rotation":0.0,"id":84,"width":100.0,"height":117.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":45,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":78.0,"rotation":0.0,"id":80,"width":100.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":41,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":82,"magnitude":1},{"id":83,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":81,"width":100.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":80,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":80,"magnitude":1},{"id":83,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":80,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":82,"width":100.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":80,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

http

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":43.0,"y":31.0,"rotation":0.0,"id":83,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":81,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":0.0,"y":39.0,"rotation":0.0,"id":76,"width":100.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":37,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":78,"magnitude":1},{"id":79,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":77,"width":100.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":76,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":76,"magnitude":1},{"id":79,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":76,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":78,"width":100.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":76,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

retry

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":43.0,"y":31.0,"rotation":0.0,"id":79,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":77,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":0.0,"y":0.0,"rotation":0.0,"id":72,"width":100.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":74,"magnitude":1},{"id":75,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":73,"width":100.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":72,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":72,"magnitude":1},{"id":75,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":72,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":74,"width":100.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":72,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

queue

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":43.0,"y":31.0,"rotation":0.0,"id":75,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":73,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]}]},{"x":0.0,"y":0.0,"rotation":0.0,"id":68,"width":130.0,"height":150.0,"uid":"com.gliffy.shape.sitemap.sitemap_v1.default.download","order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.download.sitemap_v1","strokeWidth":2.0,"strokeColor":"#666666","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":71,"width":126.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Endpoint_1

","tid":null,"valign":"middle","vposition":"above","hposition":"none"}}}]}]},{"x":598.0,"y":379.5,"rotation":0.0,"id":102,"width":130.0,"height":150.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":61,"lockAspectRatio":false,"lockShape":false,"children":[{"x":15.0,"y":20.0,"rotation":0.0,"id":87,"width":100.0,"height":117.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":60,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":78.0,"rotation":0.0,"id":88,"width":100.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":56,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":90,"magnitude":1},{"id":91,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":89,"width":100.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":88,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":88,"magnitude":1},{"id":91,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":88,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":90,"width":100.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":88,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

http

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":43.0,"y":31.0,"rotation":0.0,"id":91,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":89,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":0.0,"y":39.0,"rotation":0.0,"id":92,"width":100.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":52,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":94,"magnitude":1},{"id":95,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":93,"width":100.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":92,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":92,"magnitude":1},{"id":95,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":92,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":94,"width":100.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":92,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

retry

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":43.0,"y":31.0,"rotation":0.0,"id":95,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":93,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]},{"x":0.0,"y":0.0,"rotation":0.0,"id":96,"width":100.0,"height":40.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.popover_top","order":48,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"id":98,"magnitude":1},{"id":99,"magnitude":1}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":97,"width":100.0,"height":32.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":96,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"id":96,"magnitude":1},{"id":99,"magnitude":-1}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":96,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":100.0,"height":29.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"id":96,"magnitude":1}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

queue

","tid":null,"valign":"top","vposition":"none","hposition":"none"}}}]},{"x":43.0,"y":31.0,"rotation":0.0,"id":99,"width":15.0,"height":8.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":15}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":8}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":97,"px":0.5,"py":1.0,"xOffset":-7.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.popover_top","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[]}]}]},{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":130.0,"height":150.0,"uid":"com.gliffy.shape.sitemap.sitemap_v1.default.download","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.download.sitemap_v1","strokeWidth":2.0,"strokeColor":"#666666","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":101,"width":126.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Endpoint_N

","tid":null,"valign":"middle","vposition":"above","hposition":"none"}}}]}]}],"shapeStyles":{"com.gliffy.shape.sitemap.sitemap_v1.default":{"fill":"#ffffff","stroke":"#666666","strokeWidth":2},"com.gliffy.shape.network.network_v3.home":{"fill":"#000000"},"com.gliffy.shape.network.network_v3.business":{"fill":"#003366"},"com.gliffy.shape.basic.basic_v1.default":{"fill":"#FFFFFF","stroke":"#434343","strokeWidth":2}},"lineStyles":{"global":{"endArrow":1}},"textStyles":{"global":{"size":"14px"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.sitemap.sitemap_v2","com.gliffy.libraries.sitemap.sitemap_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.table.table_v2.default","com.gliffy.libraries.ui.ui_v3.navigation","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.ui.ui_v3.icon_symbols","com.gliffy.libraries.ui.ui_v2.forms_components","com.gliffy.libraries.ui.ui_v2.content","com.gliffy.libraries.ui.ui_v2.miscellaneous","com.gliffy.libraries.network.network_v3.home","com.gliffy.libraries.network.network_v3.business","com.gliffy.libraries.network.network_v3.rack","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.uml.uml_v2.state_machine","com.gliffy.libraries.uml.uml_v2.deployment","com.gliffy.libraries.uml.uml_v2.use_case","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.component","com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.images"]},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/images/notifications.png b/docs/images/notifications.png deleted file mode 100644 index 09de8d2376d6f986374fceeb1e26389d3ab604df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37836 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 E0UWExzyJUM diff --git a/docs/images/notifications.svg b/docs/images/notifications.svg deleted file mode 100644 index 6c3d680b..00000000 --- a/docs/images/notifications.svg +++ /dev/null @@ -1 +0,0 @@ -Registry instanceBroadcaster requestrepositoryhandlerListenerEndpoint_1queueretryhttpEndpoint_Nqueueretryhttp. . .RemoteEndpoint_1RemoteEndpoint_N \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 21ec7a9a..00000000 --- a/docs/index.md +++ /dev/null @@ -1,67 +0,0 @@ - - -# Docker Registry - -## What it is - -The Registry is a stateless, highly scalable server side application that stores and lets you distribute Docker images. -The Registry is open-source, under the permissive [Apache license](http://en.wikipedia.org/wiki/Apache_License). - -## Why use it - -You should use the Registry if you want to: - - * tightly control where your images are being stored - * fully own your images distribution pipeline - * integrate image storage and distribution tightly into your in-house development workflow - -## Alternatives - -Users looking for a zero maintenance, ready-to-go solution are encouraged to head-over to the [Docker Hub](https://hub.docker.com), which provides a free-to-use, hosted Registry, plus additional features (organization accounts, automated builds, and more). - -Users looking for a commercially supported version of the Registry should look into [Docker Trusted Registry](https://docs.docker.com/docker-trusted-registry/overview/). - -## Requirements - -The Registry is compatible with Docker engine **version 1.6.0 or higher**. -If you really need to work with older Docker versions, you should look into the [old python registry](https://github.com/docker/docker-registry). - -## TL;DR - -Start your registry - - docker run -d -p 5000:5000 --name registry registry:2 - -Pull (or build) some image from the hub - - docker pull ubuntu - -Tag the image so that it points to your registry - - docker tag ubuntu localhost:5000/myfirstimage - -Push it - - docker push localhost:5000/myfirstimage - -Pull it back - - docker pull localhost:5000/myfirstimage - -Now stop your registry and remove all data - - docker stop registry && docker rm -v registry - -## Next - -You should now read the [detailed introduction about the registry](introduction.md), or jump directly to [deployment instructions](deploying.md). diff --git a/docs/insecure.md b/docs/insecure.md deleted file mode 100644 index 38b3a355..00000000 --- a/docs/insecure.md +++ /dev/null @@ -1,114 +0,0 @@ - - -# Insecure Registry - -While it's highly recommended to secure your registry using a TLS certificate -issued by a known CA, you may alternatively decide to use self-signed -certificates, or even use your registry over plain http. - -You have to understand the downsides in doing so, and the extra burden in -configuration. - -## Deploying a plain HTTP registry - -> **Warning**: it's not possible to use an insecure registry with basic authentication. - -This basically tells Docker to entirely disregard security for your registry. -While this is relatively easy to configure the daemon in this way, it is -**very** insecure. It does expose your registry to trivial MITM. Only use this -solution for isolated testing or in a tightly controlled, air-gapped -environment. - -1. Open the `/etc/default/docker` file or `/etc/sysconfig/docker` for editing. - - Depending on your operating system, your Engine daemon start options. - -2. Edit (or add) the `DOCKER_OPTS` line and add the `--insecure-registry` flag. - - This flag takes the URL of your registry, for example. - - `DOCKER_OPTS="--insecure-registry myregistrydomain.com:5000"` - -3. Close and save the configuration file. - -4. Restart your Docker daemon - - The command you use to restart the daemon depends on your operating system. - For example, on Ubuntu, this is usually the `service docker stop` and `service - docker start` command. - -5. Repeat this configuration on every Engine host that wants to access your registry. - - -## Using self-signed certificates - -> **Warning**: using this along with basic authentication requires to **also** trust the certificate into the OS cert store for some versions of docker (see below) - -This is more secure than the insecure registry solution. You must configure every docker daemon that wants to access your registry - -1. Generate your own certificate: - - mkdir -p certs && openssl req \ - -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \ - -x509 -days 365 -out certs/domain.crt - -2. Be sure to use the name `myregistrydomain.com` as a CN. - -3. Use the result to [start your registry with TLS enabled](./deploying.md#get-a-certificate) - -4. Instruct every docker daemon to trust that certificate. - - This is done by copying the `domain.crt` file to `/etc/docker/certs.d/myregistrydomain.com:5000/ca.crt`. - -5. Don't forget to restart the Engine daemon. - -## Troubleshooting insecure registry - -This sections lists some common failures and how to recover from them. - -### Failing... - -Failing to configure the Engine daemon and trying to pull from a registry that is not using -TLS will results in the following message: - -``` -FATA[0000] Error response from daemon: v1 ping attempt failed with error: -Get https://myregistrydomain.com:5000/v1/_ping: tls: oversized record received with length 20527. -If this private registry supports only HTTP or HTTPS with an unknown CA certificate,please add -`--insecure-registry myregistrydomain.com:5000` to the daemon's arguments. -In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; -simply place the CA certificate at /etc/docker/certs.d/myregistrydomain.com:5000/ca.crt -``` - -### Docker still complains about the certificate when using authentication? - -When using authentication, some versions of docker also require you to trust the certificate at the OS level. Usually, on Ubuntu this is done with: - -```bash -$ cp certs/domain.crt /usr/local/share/ca-certificates/myregistrydomain.com.crt -update-ca-certificates -``` - -... and on Red Hat (and its derivatives) with: - -```bash -cp certs/domain.crt /etc/pki/ca-trust/source/anchors/myregistrydomain.com.crt -update-ca-trust -``` - -... On some distributions, e.g. Oracle Linux 6, the Shared System Certificates feature needs to be manually enabled: - -```bash -$ update-ca-trust enable -``` - -Now restart docker (`service docker stop && service docker start`, or any other way you use to restart docker). diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index eceb5ffc..00000000 --- a/docs/introduction.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# Understanding the Registry - -A registry is a storage and content delivery system, holding named Docker images, available in different tagged versions. - - > Example: the image `distribution/registry`, with tags `2.0` and `2.1`. - -Users interact with a registry by using docker push and pull commands. - - > Example: `docker pull registry-1.docker.io/distribution/registry:2.1`. - -Storage itself is delegated to drivers. The default storage driver is the local posix filesystem, which is suitable for development or small deployments. Additional cloud-based storage drivers like S3, Microsoft Azure, OpenStack Swift and Aliyun OSS are also supported. People looking into using other storage backends may do so by writing their own driver implementing the [Storage API](storage-drivers/index.md). - -Since securing access to your hosted images is paramount, the Registry natively supports TLS and basic authentication. - -The Registry GitHub repository includes additional information about advanced authentication and authorization methods. Only very large or public deployments are expected to extend the Registry in this way. - -Finally, the Registry ships with a robust [notification system](notifications.md), calling webhooks in response to activity, and both extensive logging and reporting, mostly useful for large installations that want to collect metrics. - -## Understanding image naming - -Image names as used in typical docker commands reflect their origin: - - * `docker pull ubuntu` instructs docker to pull an image named `ubuntu` from the official Docker Hub. This is simply a shortcut for the longer `docker pull docker.io/library/ubuntu` command - * `docker pull myregistrydomain:port/foo/bar` instructs docker to contact the registry located at `myregistrydomain:port` to find the image `foo/bar` - -You can find out more about the various Docker commands dealing with images in the [official Docker engine documentation](/engine/reference/commandline/cli.md). - -## Use cases - -Running your own Registry is a great solution to integrate with and complement your CI/CD system. In a typical workflow, a commit to your source revision control system would trigger a build on your CI system, which would then push a new image to your Registry if the build is successful. A notification from the Registry would then trigger a deployment on a staging environment, or notify other systems that a new image is available. - -It's also an essential component if you want to quickly deploy a new image over a large cluster of machines. - -Finally, it's the best way to distribute images inside an isolated network. - -## Requirements - -You absolutely need to be familiar with Docker, specifically with regard to pushing and pulling images. You must understand the difference between the daemon and the cli, and at least grasp basic concepts about networking. - -Also, while just starting a registry is fairly easy, operating it in a production environment requires operational skills, just like any other service. You are expected to be familiar with systems availability and scalability, logging and log processing, systems monitoring, and security 101. Strong understanding of http and overall network communications, plus familiarity with golang are certainly useful as well for advanced operations or hacking. - -## Next - -Dive into [deploying your registry](deploying.md) diff --git a/docs/menu.md b/docs/menu.md deleted file mode 100644 index 7e24a690..00000000 --- a/docs/menu.md +++ /dev/null @@ -1,23 +0,0 @@ - - -# Overview of Docker Registry Documentation - -The Docker Registry documentation includes the following topics: - -* [Docker Registry Introduction](index.md) -* [Understanding the Registry](introduction.md) -* [Deploying a registry server](deploying.md) -* [Registry Configuration Reference](configuration.md) -* [Notifications](notifications.md) -* [Recipes](recipes/index.md) -* [Getting help](help.md) diff --git a/docs/migration.md b/docs/migration.md deleted file mode 100644 index da0aba91..00000000 --- a/docs/migration.md +++ /dev/null @@ -1,30 +0,0 @@ - - -# Migrating a 1.0 registry to 2.0 - -TODO: This needs to be revised in light of Olivier's work - -A few thoughts here: - -There was no "1.0". There was an implementation of the Registry API V1 but only a version 0.9 of the service was released. -The image formats are not compatible in any way. One must convert v1 images to v2 images using a docker client or other tool. -One can migrate images from one version to the other by pulling images from the old registry and pushing them to the v2 registry. - ------ - -The Docker Registry 2.0 is backward compatible with images created by the earlier specification. If you are migrating a private registry to version 2.0, you should use the following process: - -1. Configure and test a 2.0 registry image in a sandbox environment. - -2. Back up up your production image storage. - - Your production image storage should reside on a volume or storage backend. - Make sure you have a backup of its contents. - -3. Stop your existing registry service. - -4. Restart your registry with your tested 2.0 image. diff --git a/docs/notifications.md b/docs/notifications.md deleted file mode 100644 index c511eb59..00000000 --- a/docs/notifications.md +++ /dev/null @@ -1,350 +0,0 @@ - - -# Notifications - -The Registry supports sending webhook notifications in response to events -happening within the registry. Notifications are sent in response to manifest -pushes and pulls and layer pushes and pulls. These actions are serialized into -events. The events are queued into a registry-internal broadcast system which -queues and dispatches events to [_Endpoints_](#endpoints). - -![](images/notifications.png) - -## Endpoints - -Notifications are sent to _endpoints_ via HTTP requests. Each configured -endpoint has isolated queues, retry configuration and http targets within each -instance of a registry. When an action happens within the registry, it is -converted into an event which is dropped into an inmemory queue. When the -event reaches the end of the queue, an http request is made to the endpoint -until the request succeeds. The events are sent serially to each endpoint but -order is not guaranteed. - -## Configuration - -To setup a registry instance to send notifications to endpoints, one must add -them to the configuration. A simple example follows: - - notifications: - endpoints: - - name: alistener - url: https://mylistener.example.com/event - headers: - Authorization: [Bearer ] - timeout: 500ms - threshold: 5 - backoff: 1s - -The above would configure the registry with an endpoint to send events to -`https://mylistener.example.com/event`, with the header "Authorization: Bearer -". The request would timeout after 500 milliseconds. If -5 failures happen consecutively, the registry will backoff for 1 second before -trying again. - -For details on the fields, please see the [configuration documentation](configuration.md#notifications). - -A properly configured endpoint should lead to a log message from the registry -upon startup: - -``` -INFO[0000] configuring endpoint alistener (https://mylistener.example.com/event), timeout=500ms, headers=map[Authorization:[Bearer ]] app.id=812bfeb2-62d6-43cf-b0c6-152f541618a3 environment=development service=registry -``` - -## Events - -Events have a well-defined JSON structure and are sent as the body of -notification requests. One or more events are sent in a structure called an -envelope. Each event has a unique id that can be used to uniquely identify incoming -requests, if required. Along with that, an _action_ is provided with a -_target_, identifying the object mutated during the event. - -The fields available in an `event` are described below. - -Field | Type | Description ------ | ----- | ------------- -id | string |ID provides a unique identifier for the event. -timestamp | Time | Timestamp is the time at which the event occurred. -action | string | Action indicates what action encompasses the provided event. -target | distribution.Descriptor | Target uniquely describes the target of the event. -length | int | Length in bytes of content. Same as Size field in Descriptor. -repository | string | Repository identifies the named repository. -fromRepository | string | FromRepository identifies the named repository which a blob was mounted from if appropriate. -url | string | URL provides a direct link to the content. -tag | string | Tag identifies a tag name in tag events -request | [RequestRecord](https://godoc.org/github.com/docker/distribution/notifications#RequestRecord) | Request covers the request that generated the event. -actor | [ActorRecord](https://godoc.org/github.com/docker/distribution/notifications#ActorRecord). | Actor specifies the agent that initiated the event. For most situations, this could be from the authorization context of the request. -source | [SourceRecord](https://godoc.org/github.com/docker/distribution/notifications#SourceRecord) | Source identifies the registry node that generated the event. Put differently, while the actor "initiates" the event, the source "generates" it. - - - -The following is an example of a JSON event, sent in response to the push of a -manifest: - -```json -{ - "events": [ - { - "id": "320678d8-ca14-430f-8bb6-4ca139cd83f7", - "timestamp": "2016-03-09T14:44:26.402973972-08:00", - "action": "pull", - "target": { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 708, - "digest": "sha256:fea8895f450959fa676bcc1df0611ea93823a735a01205fd8622846041d0c7cf", - "length": 708, - "repository": "hello-world", - "url": "http://192.168.100.227:5000/v2/hello-world/manifests/sha256:fea8895f450959fa676bcc1df0611ea93823a735a01205fd8622846041d0c7cf", - "tag": "latest" - }, - "request": { - "id": "6df24a34-0959-4923-81ca-14f09767db19", - "addr": "192.168.64.11:42961", - "host": "192.168.100.227:5000", - "method": "GET", - "useragent": "curl/7.38.0" - }, - "actor": {}, - "source": { - "addr": "xtal.local:5000", - "instanceID": "a53db899-3b4b-4a62-a067-8dd013beaca4" - } - } - ] -} -``` - - -The target struct of events which are sent when manifests and blobs are deleted -will contain a subset of the data contained in Get and Put events. Specifically, -only the digest and repository will be sent. - -```json -"target": { - "digest": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845", - "repository": "library/test" -}, -``` - -> __NOTE:__ As of version 2.1, the `length` field for event targets -> is being deprecated for the `size` field, bringing the target in line with -> common nomenclature. Both will continue to be set for the foreseeable -> future. Newer code should favor `size` but accept either. - -## Envelope - -The envelope contains one or more events, with the following json structure: - -```json -{ - "events": [ ... ], -} -``` - -While events may be sent in the same envelope, the set of events within that -envelope have no implied relationship. For example, the registry may choose to -group unrelated events and send them in the same envelope to reduce the total -number of requests. - -The full package has the mediatype -"application/vnd.docker.distribution.events.v1+json", which will be set on the -request coming to an endpoint. - -An example of a full event may look as follows: - -```json -GET /callback -Host: application/vnd.docker.distribution.events.v1+json -Authorization: Bearer -Content-Type: application/vnd.docker.distribution.events.v1+json - -{ - "events": [ - { - "id": "asdf-asdf-asdf-asdf-0", - "timestamp": "2006-01-02T15:04:05Z", - "action": "push", - "target": { - "mediaType": "application/vnd.docker.distribution.manifest.v1+json", - "length": 1, - "digest": "sha256:fea8895f450959fa676bcc1df0611ea93823a735a01205fd8622846041d0c7cf", - "repository": "library/test", - "url": "http://example.com/v2/library/test/manifests/sha256:c3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5" - }, - "request": { - "id": "asdfasdf", - "addr": "client.local", - "host": "registrycluster.local", - "method": "PUT", - "useragent": "test/0.1" - }, - "actor": { - "name": "test-actor" - }, - "source": { - "addr": "hostname.local:port" - } - }, - { - "id": "asdf-asdf-asdf-asdf-1", - "timestamp": "2006-01-02T15:04:05Z", - "action": "push", - "target": { - "mediaType": "application/vnd.docker.container.image.rootfs.diff+x-gtar", - "length": 2, - "digest": "sha256:c3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5", - "repository": "library/test", - "url": "http://example.com/v2/library/test/blobs/sha256:c3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5" - }, - "request": { - "id": "asdfasdf", - "addr": "client.local", - "host": "registrycluster.local", - "method": "PUT", - "useragent": "test/0.1" - }, - "actor": { - "name": "test-actor" - }, - "source": { - "addr": "hostname.local:port" - } - }, - { - "id": "asdf-asdf-asdf-asdf-2", - "timestamp": "2006-01-02T15:04:05Z", - "action": "push", - "target": { - "mediaType": "application/vnd.docker.container.image.rootfs.diff+x-gtar", - "length": 3, - "digest": "sha256:c3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5", - "repository": "library/test", - "url": "http://example.com/v2/library/test/blobs/sha256:c3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5" - }, - "request": { - "id": "asdfasdf", - "addr": "client.local", - "host": "registrycluster.local", - "method": "PUT", - "useragent": "test/0.1" - }, - "actor": { - "name": "test-actor" - }, - "source": { - "addr": "hostname.local:port" - } - } - ] -} -``` - -## Responses - -The registry is fairly accepting of the response codes from endpoints. If an -endpoint responds with any 2xx or 3xx response code (after following -redirects), the message will be considered delivered and discarded. - -In turn, it is recommended that endpoints are accepting of incoming responses, -as well. While the format of event envelopes are standardized by media type, -any "pickyness" about validation may cause the queue to backup on the -registry. - -## Monitoring - -The state of the endpoints are reported via the debug/vars http interface, -usually configured to `http://localhost:5001/debug/vars`. Information such as -configuration and metrics are available by endpoint. - -The following provides an example of a few endpoints that have experienced -several failures and have since recovered: - -```json -"notifications":{ - "endpoints":[ - { - "name":"local-5003", - "url":"http://localhost:5003/callback", - "Headers":{ - "Authorization":[ - "Bearer \u003can example token\u003e" - ] - }, - "Timeout":1000000000, - "Threshold":10, - "Backoff":1000000000, - "Metrics":{ - "Pending":76, - "Events":76, - "Successes":0, - "Failures":0, - "Errors":46, - "Statuses":{ - - } - } - }, - { - "name":"local-8083", - "url":"http://localhost:8083/callback", - "Headers":null, - "Timeout":1000000000, - "Threshold":10, - "Backoff":1000000000, - "Metrics":{ - "Pending":0, - "Events":76, - "Successes":76, - "Failures":0, - "Errors":28, - "Statuses":{ - "202 Accepted":76 - } - } - } - ] -} -``` - -If using notification as part of a larger application, it is _critical_ to -monitor the size ("Pending" above) of the endpoint queues. If failures or -queue sizes are increasing, it can indicate a larger problem. - -The logs are also a valuable resource for monitoring problems. A failing -endpoint will lead to messages similar to the following: - -``` -ERRO[0340] retryingsink: error writing events: httpSink{http://localhost:5003/callback}: error posting: Post http://localhost:5003/callback: dial tcp 127.0.0.1:5003: connection refused, retrying -WARN[0340] httpSink{http://localhost:5003/callback} encountered too many errors, backing off -``` - -The above indicates that several errors have led to a backoff and the registry -will wait before retrying. - -## Considerations - -Currently, the queues are inmemory, so endpoints should be _reasonably -reliable_. They are designed to make a best-effort to send the messages but if -an instance is lost, messages may be dropped. If an endpoint goes down, care -should be taken to ensure that the registry instance is not terminated before -the endpoint comes back up or messages will be lost. - -This can be mitigated by running endpoints in close proximity to the registry -instances. One could run an endpoint that pages to disk and then forwards a -request to provide better durability. - -The notification system is designed around a series of interchangeable _sinks_ -which can be wired up to achieve interesting behavior. If this system doesn't -provide acceptable guarantees, adding a transactional `Sink` to the registry -is a possibility, although it may have an effect on request service time. -Please see the -[godoc](http://godoc.org/github.com/docker/distribution/notifications#Sink) -for more information. diff --git a/docs/recipes/apache.md b/docs/recipes/apache.md deleted file mode 100644 index ac24113b..00000000 --- a/docs/recipes/apache.md +++ /dev/null @@ -1,215 +0,0 @@ - - -# Authenticating proxy with apache - -## Use-case - -People already relying on an apache proxy to authenticate their users to other services might want to leverage it and have Registry communications tunneled through the same pipeline. - -Usually, that includes enterprise setups using LDAP/AD on the backend and a SSO mechanism fronting their internal http portal. - -### Alternatives - -If you just want authentication for your registry, and are happy maintaining users access separately, you should really consider sticking with the native [basic auth registry feature](../deploying.md#native-basic-auth). - -### Solution - -With the method presented here, you implement basic authentication for docker engines in a reverse proxy that sits in front of your registry. - -While we use a simple htpasswd file as an example, any other apache authentication backend should be fairly easy to implement once you are done with the example. - -We also implement push restriction (to a limited user group) for the sake of the example. Again, you should modify this to fit your mileage. - -### Gotchas - -While this model gives you the ability to use whatever authentication backend you want through the secondary authentication mechanism implemented inside your proxy, it also requires that you move TLS termination from the Registry to the proxy itself. - -Furthermore, introducing an extra http layer in your communication pipeline will make it more complex to deploy, maintain, and debug, and will possibly create issues. - -## Setting things up - -Read again [the requirements](index.md#requirements). - -Ready? - -Run the following script: - -``` -mkdir -p auth -mkdir -p data - -# This is the main apache configuration you will use -cat < auth/httpd.conf -LoadModule headers_module modules/mod_headers.so - -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule authz_groupfile_module modules/mod_authz_groupfile.so -LoadModule authz_user_module modules/mod_authz_user.so -LoadModule authz_core_module modules/mod_authz_core.so -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule access_compat_module modules/mod_access_compat.so - -LoadModule log_config_module modules/mod_log_config.so - -LoadModule ssl_module modules/mod_ssl.so - -LoadModule proxy_module modules/mod_proxy.so -LoadModule proxy_http_module modules/mod_proxy_http.so - -LoadModule unixd_module modules/mod_unixd.so - - - SSLRandomSeed startup builtin - SSLRandomSeed connect builtin - - - - User daemon - Group daemon - - -ServerAdmin you@example.com - -ErrorLog /proc/self/fd/2 - -LogLevel warn - - - LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined - LogFormat "%h %l %u %t \"%r\" %>s %b" common - - - LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio - - - CustomLog /proc/self/fd/1 common - - -ServerRoot "/usr/local/apache2" - -Listen 5043 - - - AllowOverride none - Require all denied - - - - - ServerName myregistrydomain.com - - SSLEngine on - SSLCertificateFile /usr/local/apache2/conf/domain.crt - SSLCertificateKeyFile /usr/local/apache2/conf/domain.key - - ## SSL settings recommandation from: https://raymii.org/s/tutorials/Strong_SSL_Security_On_Apache2.html - # Anti CRIME - SSLCompression off - - # POODLE and other stuff - SSLProtocol all -SSLv2 -SSLv3 -TLSv1 - - # Secure cypher suites - SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH - SSLHonorCipherOrder on - - Header always set "Docker-Distribution-Api-Version" "registry/2.0" - Header onsuccess set "Docker-Distribution-Api-Version" "registry/2.0" - RequestHeader set X-Forwarded-Proto "https" - - ProxyRequests off - ProxyPreserveHost on - - # no proxy for /error/ (Apache HTTPd errors messages) - ProxyPass /error/ ! - - ProxyPass /v2 http://registry:5000/v2 - ProxyPassReverse /v2 http://registry:5000/v2 - - - Order deny,allow - Allow from all - AuthName "Registry Authentication" - AuthType basic - AuthUserFile "/usr/local/apache2/conf/httpd.htpasswd" - AuthGroupFile "/usr/local/apache2/conf/httpd.groups" - - # Read access to authentified users - - Require valid-user - - - # Write access to docker-deployer only - - Require group pusher - - - - - -EOF - -# Now, create a password file for "testuser" and "testpassword" -docker run --entrypoint htpasswd httpd:2.4 -Bbn testuser testpassword > auth/httpd.htpasswd -# Create another one for "testuserpush" and "testpasswordpush" -docker run --entrypoint htpasswd httpd:2.4 -Bbn testuserpush testpasswordpush >> auth/httpd.htpasswd - -# Create your group file -echo "pusher: testuserpush" > auth/httpd.groups - -# Copy over your certificate files -cp domain.crt auth -cp domain.key auth - -# Now create your compose file - -cat < docker-compose.yml -apache: - image: "httpd:2.4" - hostname: myregistrydomain.com - ports: - - 5043:5043 - links: - - registry:registry - volumes: - - `pwd`/auth:/usr/local/apache2/conf - -registry: - image: registry:2 - ports: - - 127.0.0.1:5000:5000 - volumes: - - `pwd`/data:/var/lib/registry - -EOF -``` - -## Starting and stopping - -Now, start your stack: - - docker-compose up -d - -Login with a "push" authorized user (using `testuserpush` and `testpasswordpush`), then tag and push your first image: - - docker login myregistrydomain.com:5043 - docker tag ubuntu myregistrydomain.com:5043/test - docker push myregistrydomain.com:5043/test - -Now, login with a "pull-only" user (using `testuser` and `testpassword`), then pull back the image: - - docker login myregistrydomain.com:5043 - docker pull myregistrydomain.com:5043/test - -Verify that the "pull-only" can NOT push: - - docker push myregistrydomain.com:5043/test diff --git a/docs/recipes/index.md b/docs/recipes/index.md deleted file mode 100644 index b4dd6367..00000000 --- a/docs/recipes/index.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# Recipes - -You will find here a list of "recipes", end-to-end scenarios for exotic or otherwise advanced use-cases. - -Most users are not expected to have a use for these. - -## Requirements - -You should have followed entirely the basic [deployment guide](../deploying.md). - -If you have not, please take the time to do so. - -At this point, it's assumed that: - - * you understand Docker security requirements, and how to configure your docker engines properly - * you have installed Docker Compose - * it's HIGHLY recommended that you get a certificate from a known CA instead of self-signed certificates - * inside the current directory, you have a X509 `domain.crt` and `domain.key`, for the CN `myregistrydomain.com` - * be sure you have stopped and removed any previously running registry (typically `docker stop registry && docker rm -v registry`) - -## The List - - * [using Apache as an authenticating proxy](apache.md) - * [using Nginx as an authenticating proxy](nginx.md) - * [running a Registry on OS X](osx-setup-guide.md) - * [mirror the Docker Hub](mirror.md) diff --git a/docs/recipes/menu.md b/docs/recipes/menu.md deleted file mode 100644 index b79c1b30..00000000 --- a/docs/recipes/menu.md +++ /dev/null @@ -1,21 +0,0 @@ - - -# Recipes - -## The List - - * [using Apache as an authenticating proxy](apache.md) - * [using Nginx as an authenticating proxy](nginx.md) - * [running a Registry on OS X](osx-setup-guide.md) - * [mirror the Docker Hub](mirror.md) diff --git a/docs/recipes/mirror.md b/docs/recipes/mirror.md deleted file mode 100644 index ff437ba4..00000000 --- a/docs/recipes/mirror.md +++ /dev/null @@ -1,74 +0,0 @@ - - -# Registry as a pull through cache - -## Use-case - -If you have multiple instances of Docker running in your environment (e.g., multiple physical or virtual machines, all running the Docker daemon), each time one of them requires an image that it doesn’t have it will go out to the internet and fetch it from the public Docker registry. By running a local registry mirror, you can keep most of the redundant image fetch traffic on your local network. - -### Alternatives - -Alternatively, if the set of images you are using is well delimited, you can simply pull them manually and push them to a simple, local, private registry. - -Furthermore, if your images are all built in-house, not using the Hub at all and relying entirely on your local registry is the simplest scenario. - -### Gotcha - -It's currently not possible to mirror another private registry. Only the central Hub can be mirrored. - -### Solution - -The Registry can be configured as a pull through cache. In this mode a Registry responds to all normal docker pull requests but stores all content locally. - -## How does it work? - -The first time you request an image from your local registry mirror, it pulls the image from the public Docker registry and stores it locally before handing it back to you. On subsequent requests, the local registry mirror is able to serve the image from its own storage. - -### What if the content changes on the Hub? - -When a pull is attempted with a tag, the Registry will check the remote to ensure if it has the latest version of the requested content. If it doesn't it will fetch the latest content and cache it. - -### What about my disk? - -In environments with high churn rates, stale data can build up in the cache. When running as a pull through cache the Registry will periodically remove old content to save disk space. Subsequent requests for removed content will cause a remote fetch and local re-caching. - -To ensure best performance and guarantee correctness the Registry cache should be configured to use the `filesystem` driver for storage. - -## Running a Registry as a pull through cache - -The easiest way to run a registry as a pull through cache is to run the official Registry image. - -Multiple registry caches can be deployed over the same back-end. A single registry cache will ensure that concurrent requests do not pull duplicate data, but this property will not hold true for a registry cache cluster. - -### Configuring the cache - -To configure a Registry to run as a pull through cache, the addition of a `proxy` section is required to the config file. - -In order to access private images on the Docker Hub, a username and password can be supplied. - - proxy: - remoteurl: https://registry-1.docker.io - username: [username] - password: [password] - -> :warn: if you specify a username and password, it's very important to understand that private resources that this user has access to on the Hub will be made available on your mirror. It's thus paramount that you secure your mirror by implementing authentication if you expect these resources to stay private! - -### Configuring the Docker daemon - -You will need to pass the `--registry-mirror` option to your Docker daemon on startup: - - docker --registry-mirror=https:// daemon - -For example, if your mirror is serving on http://10.0.0.2:5000, you would run: - - docker --registry-mirror=https://10.0.0.2:5000 daemon - -NOTE: Depending on your local host setup, you may be able to add the `--registry-mirror` option to the `DOCKER_OPTS` variable in `/etc/default/docker`. diff --git a/docs/recipes/nginx.md b/docs/recipes/nginx.md deleted file mode 100644 index f4a67679..00000000 --- a/docs/recipes/nginx.md +++ /dev/null @@ -1,190 +0,0 @@ - - -# Authenticating proxy with nginx - - -## Use-case - -People already relying on a nginx proxy to authenticate their users to other services might want to leverage it and have Registry communications tunneled through the same pipeline. - -Usually, that includes enterprise setups using LDAP/AD on the backend and a SSO mechanism fronting their internal http portal. - -### Alternatives - -If you just want authentication for your registry, and are happy maintaining users access separately, you should really consider sticking with the native [basic auth registry feature](../deploying.md#native-basic-auth). - -### Solution - -With the method presented here, you implement basic authentication for docker engines in a reverse proxy that sits in front of your registry. - -While we use a simple htpasswd file as an example, any other nginx authentication backend should be fairly easy to implement once you are done with the example. - -We also implement push restriction (to a limited user group) for the sake of the example. Again, you should modify this to fit your mileage. - -### Gotchas - -While this model gives you the ability to use whatever authentication backend you want through the secondary authentication mechanism implemented inside your proxy, it also requires that you move TLS termination from the Registry to the proxy itself. - -Furthermore, introducing an extra http layer in your communication pipeline will make it more complex to deploy, maintain, and debug, and will possibly create issues. Make sure the extra complexity is required. - -For instance, Amazon's Elastic Load Balancer (ELB) in HTTPS mode already sets the following client header: - -``` -X-Real-IP -X-Forwarded-For -X-Forwarded-Proto -``` - -So if you have an nginx sitting behind it, should remove these lines from the example config below: - -``` -X-Real-IP $remote_addr; # pass on real client's IP -X-Forwarded-For $proxy_add_x_forwarded_for; -X-Forwarded-Proto $scheme; -``` - -Otherwise nginx will reset the ELB's values, and the requests will not be routed properly. For more information, see [#970](https://github.com/docker/distribution/issues/970). - -## Setting things up - -Read again [the requirements](index.md#requirements). - -Ready? - --- - -Create the required directories - -``` -mkdir -p auth -mkdir -p data -``` - -Create the main nginx configuration you will use. - -``` - -cat < auth/nginx.conf -events { - worker_connections 1024; -} - -http { - - upstream docker-registry { - server registry:5000; - } - - ## Set a variable to help us decide if we need to add the - ## 'Docker-Distribution-Api-Version' header. - ## The registry always sets this header. - ## In the case of nginx performing auth, the header will be unset - ## since nginx is auth-ing before proxying. - map \$upstream_http_docker_distribution_api_version \$docker_distribution_api_version { - 'registry/2.0' ''; - default registry/2.0; - } - - server { - listen 443 ssl; - server_name myregistrydomain.com; - - # SSL - ssl_certificate /etc/nginx/conf.d/domain.crt; - ssl_certificate_key /etc/nginx/conf.d/domain.key; - - # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html - ssl_protocols TLSv1.1 TLSv1.2; - ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - - # disable any limits to avoid HTTP 413 for large image uploads - client_max_body_size 0; - - # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486) - chunked_transfer_encoding on; - - location /v2/ { - # Do not allow connections from docker 1.5 and earlier - # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents - if (\$http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*\$" ) { - return 404; - } - - # To add basic authentication to v2 use auth_basic setting. - auth_basic "Registry realm"; - auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd; - - ## If $docker_distribution_api_version is empty, the header will not be added. - ## See the map directive above where this variable is defined. - add_header 'Docker-Distribution-Api-Version' \$docker_distribution_api_version always; - - proxy_pass http://docker-registry; - proxy_set_header Host \$http_host; # required for docker client's sake - proxy_set_header X-Real-IP \$remote_addr; # pass on real client's IP - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - proxy_read_timeout 900; - } - } -} -EOF -``` - -Now create a password file for "testuser" and "testpassword" - -``` -docker run --rm --entrypoint htpasswd registry:2 -bn testuser testpassword > auth/nginx.htpasswd -``` - -Copy over your certificate files - -``` -cp domain.crt auth -cp domain.key auth -``` - -Now create your compose file - -``` -cat < docker-compose.yml -nginx: - image: "nginx:1.9" - ports: - - 5043:443 - links: - - registry:registry - volumes: - - ./auth:/etc/nginx/conf.d - - ./auth/nginx.conf:/etc/nginx/nginx.conf:ro - -registry: - image: registry:2 - ports: - - 127.0.0.1:5000:5000 - volumes: - - `pwd`./data:/var/lib/registry -EOF -``` - -## Starting and stopping - -Now, start your stack: - - docker-compose up -d - -Login with a "push" authorized user (using `testuser` and `testpassword`), then tag and push your first image: - - docker login -u=testuser -p=testpassword -e=root@example.ch myregistrydomain.com:5043 - docker tag ubuntu myregistrydomain.com:5043/test - docker push myregistrydomain.com:5043/test - docker pull myregistrydomain.com:5043/test diff --git a/docs/recipes/osx-setup-guide.md b/docs/recipes/osx-setup-guide.md deleted file mode 100644 index d47d31c1..00000000 --- a/docs/recipes/osx-setup-guide.md +++ /dev/null @@ -1,81 +0,0 @@ - - -# OS X Setup Guide - -## Use-case - -This is useful if you intend to run a registry server natively on OS X. - -### Alternatives - -You can start a VM on OS X, and deploy your registry normally as a container using Docker inside that VM. - -The simplest road to get there is traditionally to use the [docker Toolbox](https://www.docker.com/toolbox), or [docker-machine](/machine/index.md), which usually relies on the [boot2docker](http://boot2docker.io/) iso inside a VirtualBox VM. - -### Solution - -Using the method described here, you install and compile your own from the git repository and run it as an OS X agent. - -### Gotchas - -Production services operation on OS X is out of scope of this document. Be sure you understand well these aspects before considering going to production with this. - -## Setup golang on your machine - -If you know, safely skip to the next section. - -If you don't, the TLDR is: - - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) - source ~/.gvm/scripts/gvm - gvm install go1.4.2 - gvm use go1.4.2 - -If you want to understand, you should read [How to Write Go Code](https://golang.org/doc/code.html). - -## Checkout the Docker Distribution source tree - - mkdir -p $GOPATH/src/github.com/docker - git clone https://github.com/docker/distribution.git $GOPATH/src/github.com/docker/distribution - cd $GOPATH/src/github.com/docker/distribution - -## Build the binary - - GOPATH=$(PWD)/Godeps/_workspace:$GOPATH make binaries - sudo cp bin/registry /usr/local/libexec/registry - -## Setup - -Copy the registry configuration file in place: - - mkdir /Users/Shared/Registry - cp docs/osx/config.yml /Users/Shared/Registry/config.yml - -## Running the Docker Registry under launchd - -Copy the Docker registry plist into place: - - plutil -lint docs/osx/com.docker.registry.plist - cp docs/osx/com.docker.registry.plist ~/Library/LaunchAgents/ - chmod 644 ~/Library/LaunchAgents/com.docker.registry.plist - -Start the Docker registry: - - launchctl load ~/Library/LaunchAgents/com.docker.registry.plist - -### Restarting the docker registry service - - launchctl stop com.docker.registry - launchctl start com.docker.registry - -### Unloading the docker registry service - - launchctl unload ~/Library/LaunchAgents/com.docker.registry.plist diff --git a/docs/recipes/osx/com.docker.registry.plist b/docs/recipes/osx/com.docker.registry.plist deleted file mode 100644 index 0982349f..00000000 --- a/docs/recipes/osx/com.docker.registry.plist +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Label - com.docker.registry - KeepAlive - - StandardErrorPath - /Users/Shared/Registry/registry.log - StandardOutPath - /Users/Shared/Registry/registry.log - Program - /usr/local/libexec/registry - ProgramArguments - - /usr/local/libexec/registry - /Users/Shared/Registry/config.yml - - Sockets - - http-listen-address - - SockServiceName - 5000 - SockType - dgram - SockFamily - IPv4 - - http-debug-address - - SockServiceName - 5001 - SockType - dgram - SockFamily - IPv4 - - - - diff --git a/docs/recipes/osx/config.yml b/docs/recipes/osx/config.yml deleted file mode 100644 index 63b8f713..00000000 --- a/docs/recipes/osx/config.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: 0.1 -log: - level: info - fields: - service: registry - environment: macbook-air -storage: - cache: - blobdescriptor: inmemory - filesystem: - rootdirectory: /Users/Shared/Registry -http: - addr: 0.0.0.0:5000 - secret: mytokensecret - debug: - addr: localhost:5001 diff --git a/docs/storage-drivers/azure.md b/docs/storage-drivers/azure.md deleted file mode 100644 index a84888de..00000000 --- a/docs/storage-drivers/azure.md +++ /dev/null @@ -1,78 +0,0 @@ - - - -# Microsoft Azure storage driver - -An implementation of the `storagedriver.StorageDriver` interface which uses [Microsoft Azure Blob Storage](http://azure.microsoft.com/en-us/services/storage/) for object storage. - -## Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- accountname - - yes - - Name of the Azure Storage Account. -
- accountkey - - yes - - Primary or Secondary Key for the Storage Account. -
- container - - yes - - Name of the Azure root storage container in which all registry data will be stored. Must comply the storage container name [requirements][create-container-api]. -
- realm - - no - - Domain name suffix for the Storage Service API endpoint. For example realm for "Azure in China" would be `core.chinacloudapi.cn` and realm for "Azure Government" would be `core.usgovcloudapi.net`. By default, this - is core.windows.net. -
- - -## Related Information - -* To get information about -[azure-blob-storage](http://azure.microsoft.com/en-us/services/storage/) visit -the Microsoft website. -* You can use Microsoft's [Blob Service REST API](https://msdn.microsoft.com/en-us/library/azure/dd135733.aspx) to [create a container] (https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx). diff --git a/docs/storage-drivers/filesystem.md b/docs/storage-drivers/filesystem.md deleted file mode 100644 index 8e269cdb..00000000 --- a/docs/storage-drivers/filesystem.md +++ /dev/null @@ -1,24 +0,0 @@ - - - -# Filesystem storage driver - -An implementation of the `storagedriver.StorageDriver` interface which uses the local filesystem. - -## Parameters - -`rootdirectory`: (optional) The absolute path to a root directory tree in which -to store all registry files. The registry stores all its data here so make sure -there is adequate space available. Defaults to `/var/lib/registry`. -`maxthreads`: (optional) The maximum number of simultaneous blocking filesystem -operations permitted within the registry. Each operation spawns a new thread and -may cause thread exhaustion issues if many are done in parallel. Defaults to -`100`, and can be no lower than `25`. diff --git a/docs/storage-drivers/gcs.md b/docs/storage-drivers/gcs.md deleted file mode 100644 index 1bc67f9e..00000000 --- a/docs/storage-drivers/gcs.md +++ /dev/null @@ -1,78 +0,0 @@ - - - -# Google Cloud Storage driver - -An implementation of the `storagedriver.StorageDriver` interface which uses Google Cloud for object storage. - -## Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- bucket - - yes - - Storage bucket name. -
- keyfile - - no - - A private service account key file in JSON format. Instead of a key file Google Application Default Credentials can be used. -
- rootdirectory - - no - - This is a prefix that will be applied to all Google Cloud Storage keys to allow you to segment data in your bucket if necessary. -
- chunksize - - no (default 5242880) - - This is the chunk size used for uploading large blobs, must be a multiple of 256*1024. -
- - -`bucket`: The name of your Google Cloud Storage bucket where you wish to store objects (needs to already be created prior to driver initialization). - -`keyfile`: (optional) A private key file in JSON format, used for [Service Account Authentication](https://cloud.google.com/storage/docs/authentication#service_accounts). - -**Note** Instead of a key file you can use [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials). - -`rootdirectory`: (optional) The root directory tree in which all registry files will be stored. Defaults to the empty string (bucket root). diff --git a/docs/storage-drivers/index.md b/docs/storage-drivers/index.md deleted file mode 100644 index 89635bd3..00000000 --- a/docs/storage-drivers/index.md +++ /dev/null @@ -1,66 +0,0 @@ - - - -# Docker Registry Storage Driver - -This document describes the registry storage driver model, implementation, and explains how to contribute new storage drivers. - -## Provided Drivers - -This storage driver package comes bundled with several drivers: - -- [inmemory](inmemory.md): A temporary storage driver using a local inmemory map. This exists solely for reference and testing. -- [filesystem](filesystem.md): A local storage driver configured to use a directory tree in the local filesystem. -- [s3](s3.md): A driver storing objects in an Amazon Simple Storage Solution (S3) bucket. -- [azure](azure.md): A driver storing objects in [Microsoft Azure Blob Storage](http://azure.microsoft.com/en-us/services/storage/). -- [swift](swift.md): A driver storing objects in [Openstack Swift](http://docs.openstack.org/developer/swift/). -- [oss](oss.md): A driver storing objects in [Aliyun OSS](http://www.aliyun.com/product/oss). -- [gcs](gcs.md): A driver storing objects in a [Google Cloud Storage](https://cloud.google.com/storage/) bucket. - -## Storage Driver API - -The storage driver API is designed to model a filesystem-like key/value storage in a manner abstract enough to support a range of drivers from the local filesystem to Amazon S3 or other distributed object storage systems. - -Storage drivers are required to implement the `storagedriver.StorageDriver` interface provided in `storagedriver.go`, which includes methods for reading, writing, and deleting content, as well as listing child objects of a specified prefix key. - -Storage drivers are intended to be written in Go, providing compile-time -validation of the `storagedriver.StorageDriver` interface. - -## Driver Selection and Configuration - -The preferred method of selecting a storage driver is using the `StorageDriverFactory` interface in the `storagedriver/factory` package. These factories provide a common interface for constructing storage drivers with a parameters map. The factory model is based off of the [Register](http://golang.org/pkg/database/sql/#Register) and [Open](http://golang.org/pkg/database/sql/#Open) methods in the builtin [database/sql](http://golang.org/pkg/database/sql) package. - -Storage driver factories may be registered by name using the -`factory.Register` method, and then later invoked by calling `factory.Create` -with a driver name and parameters map. If no such storage driver can be found, -`factory.Create` will return an `InvalidStorageDriverError`. - -## Driver Contribution - -### Writing new storage drivers - -To create a valid storage driver, one must implement the -`storagedriver.StorageDriver` interface and make sure to expose this driver -via the factory system. - -#### Registering - -Storage drivers should call `factory.Register` with their driver name in an `init` method, allowing callers of `factory.New` to construct instances of this driver without requiring modification of imports throughout the codebase. - -## Testing - -Storage driver test suites are provided in -`storagedriver/testsuites/testsuites.go` and may be used for any storage -driver written in Go. Tests can be registered using the `RegisterSuite` -function, which run the same set of tests for any registered drivers. diff --git a/docs/storage-drivers/inmemory.md b/docs/storage-drivers/inmemory.md deleted file mode 100644 index 1a14e77a..00000000 --- a/docs/storage-drivers/inmemory.md +++ /dev/null @@ -1,23 +0,0 @@ - - - -# In-memory storage driver (Testing Only) - -For purely tests purposes, you can use the `inmemory` storage driver. This -driver is an implementation of the `storagedriver.StorageDriver` interface which -uses local memory for object storage. If you would like to run a registry from -volatile memory, use the [`filesystem` driver](filesystem.md) on a ramdisk. - -**IMPORTANT**: This storage driver *does not* persist data across runs. This is why it is only suitable for testing. *Never* use this driver in production. - -## Parameters - -None diff --git a/docs/storage-drivers/menu.md b/docs/storage-drivers/menu.md deleted file mode 100644 index 3638649f..00000000 --- a/docs/storage-drivers/menu.md +++ /dev/null @@ -1,13 +0,0 @@ - - diff --git a/docs/storage-drivers/oss.md b/docs/storage-drivers/oss.md deleted file mode 100644 index a85e315e..00000000 --- a/docs/storage-drivers/oss.md +++ /dev/null @@ -1,126 +0,0 @@ - - -# Aliyun OSS storage driver - -An implementation of the `storagedriver.StorageDriver` interface which uses [Aliyun OSS](http://www.aliyun.com/product/oss) for object storage. - -## Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- accesskeyid - -yes - -Your access key ID. -
- accesskeysecret - -yes - -Your access key secret. -
- region - -yes - The name of the OSS region in which you would like to store objects (for example `oss-cn-beijing`). For a list of regions, you can look at -
- endpoint - -no - -An endpoint which defaults to `..aliyuncs.com` or `.-internal.aliyuncs.com` (when `internal=true`). You can change the default endpoint by changing this value. -
- internal - -no - An internal endpoint or the public endpoint for OSS access. The default is false. For a list of regions, you can look at -
- bucket - -yes - The name of your OSS bucket where you wish to store objects (needs to already be created prior to driver initialization). -
- encrypt - -no - Specifies whether you would like your data encrypted on the server side. Defaults to false if not specified. -
- secure - -no - Specifies whether to transfer data to the bucket over ssl or not. If you omit this value, `true` is used. -
- chunksize - -no - The default part size for multipart uploads (performed by WriteStream) to OSS. The default is 10 MB. Keep in mind that the minimum part size for OSS is 5MB. You might experience better performance for larger chunk sizes depending on the speed of your connection to OSS. -
- rootdirectory - -no - The root directory tree in which to store all registry files. Defaults to an empty string (bucket root). -
diff --git a/docs/storage-drivers/s3.md b/docs/storage-drivers/s3.md deleted file mode 100644 index 97cfbfc1..00000000 --- a/docs/storage-drivers/s3.md +++ /dev/null @@ -1,268 +0,0 @@ - - - -# S3 storage driver - -An implementation of the `storagedriver.StorageDriver` interface which uses Amazon S3 or S3 compatible services for object storage. - -## Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- accesskey - - yes - - Your AWS Access Key. -
- secretkey - - yes - - Your AWS Secret Key. -
- region - - yes - - The AWS region in which your bucket exists. For the moment, the Go AWS - library in use does not use the newer DNS based bucket routing. -
- regionendpoint - - no - - Endpoint for S3 compatible storage services (Minio, etc) -
- bucket - - yes - - The bucket name in which you want to store the registry's data. -
- encrypt - - no - - Specifies whether the registry stores the image in encrypted format or - not. A boolean value. The default is false. -
- keyid - - no - - Optional KMS key ID to use for encryption (encrypt must be true, or this - parameter will be ignored). The default is none. -
- secure - - no - - Indicates whether to use HTTPS instead of HTTP. A boolean value. The - default is true. -
- v4auth - - no - - Indicates whether the registry uses Version 4 of AWS's authentication. - Generally, you should set this to true. By default, this is - false. -
- chunksize - - no - - The S3 API requires multipart upload chunks to be at least 5MB. This value - should be a number that is larger than 5*1024*1024. -
- rootdirectory - - no - - This is a prefix that will be applied to all S3 keys to allow you to segment data in your bucket if necessary. -
- storageclass - - no - - The S3 storage class applied to each registry file. The default value is STANDARD. -
- - -`accesskey`: Your aws access key. - -`secretkey`: Your aws secret key. - -**Note** You can provide empty strings for your access and secret keys if you plan on running the driver on an ec2 instance and will handle authentication with the instance's credentials. - -`region`: The name of the aws region in which you would like to store objects (for example `us-east-1`). For a list of regions, you can look at http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html - -`regionendpoint`: (optional) Endpoint URL for S3 compatible APIs. This should not be provided when using Amazon S3. - -`bucket`: The name of your S3 bucket where you wish to store objects. The bucket must exist prior to the driver initialization. - -`encrypt`: (optional) Whether you would like your data encrypted on the server side (defaults to false if not specified). - -`keyid`: (optional) Whether you would like your data encrypted with this KMS key ID (defaults to none if not specified, will be ignored if encrypt is not true). - -`secure`: (optional) Whether you would like to transfer data to the bucket over ssl or not. Defaults to true (meaning transferring over ssl) if not specified. Note that while setting this to false will improve performance, it is not recommended due to security concerns. - -`v4auth`: (optional) Whether you would like to use aws signature version 4 with your requests. This defaults to false if not specified (note that the eu-central-1 region does not work with version 2 signatures, so the driver will error out if initialized with this region and v4auth set to false) - -`chunksize`: (optional) The default part size for multipart uploads (performed by WriteStream) to S3. The default is 10 MB. Keep in mind that the minimum part size for S3 is 5MB. Depending on the speed of your connection to S3, a larger chunk size may result in better performance; faster connections will benefit from larger chunk sizes. - -`rootdirectory`: (optional) The root directory tree in which all registry files will be stored. Defaults to the empty string (bucket root). - -`storageclass`: (optional) The storage class applied to each registry file. Defaults to STANDARD. Valid options are STANDARD and REDUCED_REDUNDANCY. - -## S3 permission scopes - -The following IAM permissions are required by the registry for push and pull. See [the S3 policy documentation](http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html) for more details. - -``` - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:ListBucket", - "s3:GetBucketLocation", - "s3:ListBucketMultipartUploads" - ], - "Resource": "arn:aws:s3:::mybucket" - }, - { - "Effect": "Allow", - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject", - "s3:ListMultipartUploadParts", - "s3:AbortMultipartUpload" - ], - "Resource": "arn:aws:s3:::mybucket/*" - } -] -``` - -# CloudFront as Middleware with S3 backend - -## Use Case - -Adding CloudFront as a middleware for your S3 backed registry can dramatically improve pull times. Your registry will have the ability to retrieve your images from edge servers, rather than the geographically limited location of your S3 bucket. The farther your registry is from your bucket, the more improvements you will see. See [Amazon CloudFront](https://aws.amazon.com/cloudfront/details/). - -## Configuring CloudFront for Distribution - -If you are unfamiliar with creating a CloudFront distribution, see [Getting Started with Cloudfront](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.html). - -Defaults can be kept in most areas except: - -### Origin: - -The CloudFront distribution must be created such that the `Origin Path` is set to the directory level of the root "docker" key in S3. If your registry exists on the root of the bucket, this path should be left blank. - -### Behaviors: - - - Viewer Protocol Policy: HTTPS Only - - Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE - - Cached HTTP Methods: OPTIONS (checked) - - Restrict Viewer Access (Use Signed URLs or Signed Cookies): Yes - - Trusted Signers: Self (Can add other accounts as long as you have access to CloudFront Key Pairs for those additional accounts) - -## Registry configuration - -Here the `middleware` option is used. It is still important to keep the `storage` option as CloudFront will only handle `pull` actions; `push` actions are still directly written to S3. - -The following example shows what you will need at minimum: -``` -... -storage: - s3: - region: us-east-1 - bucket: docker.myregistry.com -middleware: - storage: - - name: cloudfront - options: - baseurl: https://abcdefghijklmn.cloudfront.net/ - privatekey: /etc/docker/cloudfront/pk-ABCEDFGHIJKLMNOPQRST.pem - keypairid: ABCEDFGHIJKLMNOPQRST -... -``` - -## CloudFront Key-Pair - -A CloudFront key-pair is required for all AWS accounts needing access to your CloudFront distribution. For information, please see [Creating CloudFront Key Pairs](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html#private-content-creating-cloudfront-key-pairs). diff --git a/docs/storage-drivers/swift.md b/docs/storage-drivers/swift.md deleted file mode 100644 index b1a0c932..00000000 --- a/docs/storage-drivers/swift.md +++ /dev/null @@ -1,246 +0,0 @@ - - - -# OpenStack Swift storage driver - -An implementation of the `storagedriver.StorageDriver` interface that uses [OpenStack Swift](http://docs.openstack.org/developer/swift/) for object storage. - -## Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- authurl - - yes - - URL for obtaining an auth token. https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth -
- username - - yes - - Your Openstack user name. -
- password - - yes - - Your Openstack password. -
- region - - no - - The Openstack region in which your container exists. -
- container - - yes - - The name of your Swift container where you wish to store the registry's data. The driver creates the named container during its initialization. -
- tenant - - no - - Your Openstack tenant name. You can either use tenant or tenantid. -
- tenantid - - no - - Your Openstack tenant id. You can either use tenant or tenantid. -
- domain - - no - - Your Openstack domain name for Identity v3 API. You can either use domain or domainid. -
- domainid - - no - - Your Openstack domain id for Identity v3 API. You can either use domain or domainid. -
- trustid - - no - - Your Openstack trust id for Identity v3 API. -
- insecureskipverify - - no - - true to skip TLS verification, false by default. -
- chunksize - - no - - Size of the data segments for the Swift Dynamic Large Objects. This value should be a number (defaults to 5M). -
- prefix - - no - - This is a prefix that will be applied to all Swift keys to allow you to segment data in your container if necessary. Defaults to the empty string which is the container's root. -
- secretkey - - no - - The secret key used to generate temporary URLs. -
- accesskey - - no - - The access key to generate temporary URLs. It is used by HP Cloud Object Storage in addition to the `secretkey` parameter. -
- authversion - - no - - Specify the OpenStack Auth's version,for example 3. By default the driver will autodetect the auth's version from the AuthURL. -
- endpointtype - - no - - The endpoint type used when connecting to swift. Possible values are `public`, `internal` and `admin`. Default is `public`. -
- -The features supported by the Swift server are queried by requesting the `/info` URL on the server. In case the administrator -disabled that feature, the configuration file can specify the following optional parameters : - - - - - - - - - - -
- tempurlcontainerkey - -

- Specify whether to use container secret key to generate temporary URL when set to true, or the account secret key otherwise.

-

-
- tempurlmethods - -

- Array of HTTP methods that are supported by the TempURL middleware of the Swift server. Example:

- - - tempurlmethods: - - GET - - PUT - - HEAD - - POST - - DELETE - -

-
From 6b3ccf964053c6130a4dbfc53f03501d608f4b0e Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Fri, 14 Oct 2016 15:40:45 -0700 Subject: [PATCH 12/19] Convert Markdown frontmatter to YAML Some frontmatter such as the weights, menu stuff, etc is no longer used 'draft=true' becomes 'published: false' Signed-off-by: Misty Stanley-Jones (cherry picked from commit f180e9a9349040229a5a72b5867e098600cf9b55) Signed-off-by: Misty Stanley-Jones (cherry picked from commit c5a8e74c562cd62db83df69ec71d9cee3e346317) Signed-off-by: Misty Stanley-Jones --- docs/configuration.md | 15 +++++---------- docs/spec/api.md | 14 +++++--------- docs/spec/api.md.tmpl | 14 +++++--------- docs/spec/auth/index.md | 15 +++++---------- docs/spec/auth/jwt.md | 15 +++++---------- docs/spec/auth/oauth.md | 16 +++++----------- docs/spec/auth/scope.md | 16 +++++----------- docs/spec/auth/token.md | 19 +++++++------------ docs/spec/implementations.md | 8 +++----- docs/spec/index.md | 15 +++++---------- docs/spec/json.md | 16 ++++++---------- docs/spec/manifest-v2-1.md | 14 +++++--------- docs/spec/manifest-v2-2.md | 14 +++++--------- docs/spec/menu.md | 20 +++++++------------- 14 files changed, 73 insertions(+), 138 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1ef680f5..e2821d4d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,13 +1,8 @@ - +--- +title: "Configuring a registry" +description: "Explains how to configure a registry" +keywords: ["registry, on-prem, images, tags, repository, distribution, configuration"] +--- # Registry Configuration Reference diff --git a/docs/spec/api.md b/docs/spec/api.md index 39041146..5b61a29e 100644 --- a/docs/spec/api.md +++ b/docs/spec/api.md @@ -1,12 +1,8 @@ - +--- +title: "HTTP API V2" +description: "Specification for the Registry API." +keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced"] +--- # Docker Registry HTTP API V2 diff --git a/docs/spec/api.md.tmpl b/docs/spec/api.md.tmpl index 13a1fc2e..a429642e 100644 --- a/docs/spec/api.md.tmpl +++ b/docs/spec/api.md.tmpl @@ -1,12 +1,8 @@ - +--- +title: "HTTP API V2" +description: "Specification for the Registry API." +keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced"] +--- # Docker Registry HTTP API V2 diff --git a/docs/spec/auth/index.md b/docs/spec/auth/index.md index f6ee8e1f..d1aa9422 100644 --- a/docs/spec/auth/index.md +++ b/docs/spec/auth/index.md @@ -1,13 +1,8 @@ - +--- +title: "Docker Registry Token Authentication" +description: "Docker Registry v2 authentication schema" +keywords: ["registry, on-prem, images, tags, repository, distribution, authentication, advanced"] +--- # Docker Registry v2 authentication diff --git a/docs/spec/auth/jwt.md b/docs/spec/auth/jwt.md index c90bd6e8..aa9941b0 100644 --- a/docs/spec/auth/jwt.md +++ b/docs/spec/auth/jwt.md @@ -1,13 +1,8 @@ - +--- +title: "Token Authentication Implementation" +description: "Describe the reference implementation of the Docker Registry v2 authentication schema" +keywords: ["registry, on-prem, images, tags, repository, distribution, JWT authentication, advanced"] +--- # Docker Registry v2 Bearer token specification diff --git a/docs/spec/auth/oauth.md b/docs/spec/auth/oauth.md index 3d1ae0aa..8b157179 100644 --- a/docs/spec/auth/oauth.md +++ b/docs/spec/auth/oauth.md @@ -1,13 +1,8 @@ - +--- +title: "Oauth2 Token Authentication" +description: "Specifies the Docker Registry v2 authentication" +keywords: ["registry, on-prem, images, tags, repository, distribution, oauth2, advanced"] +--- # Docker Registry v2 authentication using OAuth2 @@ -188,4 +183,3 @@ Content-Type: application/json {"refresh_token":"kas9Da81Dfa8","access_token":"eyJhbGciOiJFUzI1NiIsInR5":"expires_in":900,"scope":"repository:samalba/my-app:pull,repository:samalba/my-app:push"} ``` - diff --git a/docs/spec/auth/scope.md b/docs/spec/auth/scope.md index a8f6c062..eecb8f6f 100644 --- a/docs/spec/auth/scope.md +++ b/docs/spec/auth/scope.md @@ -1,13 +1,8 @@ - +--- +title: "Token Scope Documentation" +description: "Describes the scope and access fields used for registry authorization tokens" +keywords: ["registry, on-prem, images, tags, repository, distribution, advanced, access, scope"] +--- # Docker Registry Token Scope and Access @@ -140,4 +135,3 @@ done by fetching an access token using the refresh token. Since the refresh token is not scoped to specific resources for an audience, extra care should be taken to only use the refresh token to negotiate new access tokens directly with the authorization server, and never with a resource provider. - diff --git a/docs/spec/auth/token.md b/docs/spec/auth/token.md index 12dfc685..41bcb00d 100644 --- a/docs/spec/auth/token.md +++ b/docs/spec/auth/token.md @@ -1,13 +1,8 @@ - +--- +title: "Token Authentication Specification" +description: "Specifies the Docker Registry v2 authentication" +keywords: ["registry, on-prem, images, tags, repository, distribution, Bearer authentication, advanced"] +--- # Docker Registry v2 authentication via central service @@ -25,7 +20,7 @@ This document outlines the v2 Docker registry authentication scheme: 5. The client retries the original request with the Bearer token embedded in the request's Authorization header. 6. The Registry authorizes the client by validating the Bearer token and the - claim set embedded within it and begins the push/pull session as usual. + claim set embedded within it and begins the push/pull session as usual. ## Requirements @@ -161,7 +156,7 @@ Defines getting a bearer and refresh token using the token endpoint. expires_in
- (Optional) The duration in seconds since the token was issued that it + (Optional) The duration in seconds since the token was issued that it will remain valid. When omitted, this defaults to 60 seconds. For compatibility with older clients, a token should never be returned with less than 60 seconds to live. diff --git a/docs/spec/implementations.md b/docs/spec/implementations.md index ec937b64..34746535 100644 --- a/docs/spec/implementations.md +++ b/docs/spec/implementations.md @@ -1,8 +1,6 @@ - +--- +published: false +--- # Distribution API Implementations diff --git a/docs/spec/index.md b/docs/spec/index.md index 474bd455..952ebabd 100644 --- a/docs/spec/index.md +++ b/docs/spec/index.md @@ -1,13 +1,8 @@ - +--- +title: "Reference Overview" +description: "Explains registry JSON objects" +keywords: ["registry, service, images, repository, json"] +--- # Docker Registry Reference diff --git a/docs/spec/json.md b/docs/spec/json.md index a8916dcc..825b17ac 100644 --- a/docs/spec/json.md +++ b/docs/spec/json.md @@ -1,13 +1,9 @@ - +--- +published: false +title: "Docker Distribution JSON Canonicalization" +description: "Explains registry JSON objects" +keywords: ["registry, service, images, repository, json"] +--- diff --git a/docs/spec/manifest-v2-1.md b/docs/spec/manifest-v2-1.md index 056f4bc6..b06c2f2a 100644 --- a/docs/spec/manifest-v2-1.md +++ b/docs/spec/manifest-v2-1.md @@ -1,12 +1,8 @@ - +--- +title: "Image Manifest V 2, Schema 1 " +description: "image manifest for the Registry." +keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced, manifest"] +--- # Image Manifest Version 2, Schema 1 diff --git a/docs/spec/manifest-v2-2.md b/docs/spec/manifest-v2-2.md index fc705639..1db8b33b 100644 --- a/docs/spec/manifest-v2-2.md +++ b/docs/spec/manifest-v2-2.md @@ -1,12 +1,8 @@ - +--- +title: "Image Manifest V 2, Schema 2 " +description: "image manifest for the Registry." +keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced, manifest"] +--- # Image Manifest Version 2, Schema 2 diff --git a/docs/spec/menu.md b/docs/spec/menu.md index ebc52327..9237e3ce 100644 --- a/docs/spec/menu.md +++ b/docs/spec/menu.md @@ -1,13 +1,7 @@ - - +--- +title: "Reference" +description: "Explains registry JSON objects" +keywords: ["registry, service, images, repository, json"] +type: "menu" +identifier: "smn_registry_ref" +--- From abd2d765ac754ae8e17c07e2b13380f06b539218 Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Thu, 10 Nov 2016 15:37:21 -0800 Subject: [PATCH 13/19] Metadata and formatting fixes needed for Jekyll build Signed-off-by: Misty Stanley-Jones (cherry picked from commit 49d6706ce9d952718725350d82d9ea7deb4f7326) Signed-off-by: Misty Stanley-Jones --- docs/configuration.md | 6 ++---- docs/spec/api.md | 11 ++--------- docs/spec/api.md.tmpl | 6 ++---- docs/spec/auth/index.md | 6 ++---- docs/spec/auth/jwt.md | 6 ++---- docs/spec/auth/oauth.md | 6 ++---- docs/spec/auth/scope.md | 6 ++---- docs/spec/auth/token.md | 6 ++---- docs/spec/implementations.md | 4 +--- docs/spec/index.md | 6 ++---- docs/spec/json.md | 6 +----- docs/spec/manifest-v2-1.md | 4 +--- docs/spec/manifest-v2-2.md | 4 +--- docs/spec/menu.md | 7 ------- 14 files changed, 22 insertions(+), 62 deletions(-) delete mode 100644 docs/spec/menu.md diff --git a/docs/configuration.md b/docs/configuration.md index e2821d4d..f8d05f02 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,11 +1,9 @@ --- -title: "Configuring a registry" +title: "Registry Configuration Reference" description: "Explains how to configure a registry" -keywords: ["registry, on-prem, images, tags, repository, distribution, configuration"] +keywords: "registry, on-prem, images, tags, repository, distribution, configuration" --- -# Registry Configuration Reference - The Registry configuration is based on a YAML file, detailed below. While it comes with sane default values out of the box, you are heavily encouraged to review it exhaustively before moving your systems to production. ## Override specific configuration options diff --git a/docs/spec/api.md b/docs/spec/api.md index 5b61a29e..70d8d0fb 100644 --- a/docs/spec/api.md +++ b/docs/spec/api.md @@ -1,11 +1,9 @@ --- -title: "HTTP API V2" +title: "Docker Registry HTTP API V2" description: "Specification for the Registry API." -keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced"] +keywords: "registry, on-prem, images, tags, repository, distribution, api, advanced" --- -# Docker Registry HTTP API V2 - ## Introduction The _Docker Registry HTTP API_ is the protocol to facilitate distribution of @@ -4842,8 +4840,3 @@ The following headers will be returned with the response: |----|-----------| |`Content-Length`|Length of the JSON response body.| |`Link`|RFC5988 compliant rel='next' with URL to next result set, if available| - - - - - diff --git a/docs/spec/api.md.tmpl b/docs/spec/api.md.tmpl index a429642e..28edc37f 100644 --- a/docs/spec/api.md.tmpl +++ b/docs/spec/api.md.tmpl @@ -1,11 +1,9 @@ --- -title: "HTTP API V2" +title: "Docker Registry HTTP API V2" description: "Specification for the Registry API." -keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced"] +keywords: "registry, on-prem, images, tags, repository, distribution, api, advanced" --- -# Docker Registry HTTP API V2 - ## Introduction The _Docker Registry HTTP API_ is the protocol to facilitate distribution of diff --git a/docs/spec/auth/index.md b/docs/spec/auth/index.md index d1aa9422..bb749060 100644 --- a/docs/spec/auth/index.md +++ b/docs/spec/auth/index.md @@ -1,11 +1,9 @@ --- -title: "Docker Registry Token Authentication" +title: "Docker Registry v2 authentication" description: "Docker Registry v2 authentication schema" -keywords: ["registry, on-prem, images, tags, repository, distribution, authentication, advanced"] +keywords: "registry, on-prem, images, tags, repository, distribution, authentication, advanced" --- -# Docker Registry v2 authentication - See the [Token Authentication Specification](token.md), [Token Authentication Implementation](jwt.md), [Token Scope Documentation](scope.md), diff --git a/docs/spec/auth/jwt.md b/docs/spec/auth/jwt.md index aa9941b0..85608282 100644 --- a/docs/spec/auth/jwt.md +++ b/docs/spec/auth/jwt.md @@ -1,11 +1,9 @@ --- -title: "Token Authentication Implementation" +title: "Docker Registry v2 Bearer token specification" description: "Describe the reference implementation of the Docker Registry v2 authentication schema" -keywords: ["registry, on-prem, images, tags, repository, distribution, JWT authentication, advanced"] +keywords: "registry, on-prem, images, tags, repository, distribution, JWT authentication, advanced" --- -# Docker Registry v2 Bearer token specification - This specification covers the `docker/distribution` implementation of the v2 Registry's authentication schema. Specifically, it describes the JSON Web Token schema that `docker/distribution` has adopted to implement the diff --git a/docs/spec/auth/oauth.md b/docs/spec/auth/oauth.md index 8b157179..f023a30f 100644 --- a/docs/spec/auth/oauth.md +++ b/docs/spec/auth/oauth.md @@ -1,11 +1,9 @@ --- -title: "Oauth2 Token Authentication" +title: "Docker Registry v2 authentication using OAuth2" description: "Specifies the Docker Registry v2 authentication" -keywords: ["registry, on-prem, images, tags, repository, distribution, oauth2, advanced"] +keywords: "registry, on-prem, images, tags, repository, distribution, oauth2, advanced" --- -# Docker Registry v2 authentication using OAuth2 - This document describes support for the OAuth2 protocol within the authorization server. [RFC6749](https://tools.ietf.org/html/rfc6749) should be used as a reference for the protocol and HTTP endpoints described here. diff --git a/docs/spec/auth/scope.md b/docs/spec/auth/scope.md index eecb8f6f..3e58212e 100644 --- a/docs/spec/auth/scope.md +++ b/docs/spec/auth/scope.md @@ -1,11 +1,9 @@ --- -title: "Token Scope Documentation" +title: "Docker Registry Token Scope and Access" description: "Describes the scope and access fields used for registry authorization tokens" -keywords: ["registry, on-prem, images, tags, repository, distribution, advanced, access, scope"] +keywords: "registry, on-prem, images, tags, repository, distribution, advanced, access, scope" --- -# Docker Registry Token Scope and Access - Tokens used by the registry are always restricted what resources they may be used to access, where those resources may be accessed, and what actions may be done on those resources. Tokens always have the context of a user which diff --git a/docs/spec/auth/token.md b/docs/spec/auth/token.md index 41bcb00d..9c89ae07 100644 --- a/docs/spec/auth/token.md +++ b/docs/spec/auth/token.md @@ -1,11 +1,9 @@ --- -title: "Token Authentication Specification" +title: "Docker Registry v2 authentication via central service" description: "Specifies the Docker Registry v2 authentication" -keywords: ["registry, on-prem, images, tags, repository, distribution, Bearer authentication, advanced"] +keywords: "registry, on-prem, images, tags, repository, distribution, Bearer authentication, advanced" --- -# Docker Registry v2 authentication via central service - This document outlines the v2 Docker registry authentication scheme: ![v2 registry auth](https://docs.google.com/drawings/d/1EHZU9uBLmcH0kytDClBv6jv6WR4xZjE8RKEUw1mARJA/pub?w=480&h=360) diff --git a/docs/spec/implementations.md b/docs/spec/implementations.md index 34746535..b78807a1 100644 --- a/docs/spec/implementations.md +++ b/docs/spec/implementations.md @@ -1,9 +1,8 @@ --- published: false +title: Distribution API Implementations --- -# Distribution API Implementations - This is a list of known implementations of the Distribution API spec. ## [Docker Distribution Registry](https://github.com/docker/distribution) @@ -27,4 +26,3 @@ _Known Issues_ - No resumable push support - No PATCH implementation for blob upload - Content ranges ignored - diff --git a/docs/spec/index.md b/docs/spec/index.md index 952ebabd..853debc9 100644 --- a/docs/spec/index.md +++ b/docs/spec/index.md @@ -1,11 +1,9 @@ --- -title: "Reference Overview" +title: "Docker Registry Reference" description: "Explains registry JSON objects" -keywords: ["registry, service, images, repository, json"] +keywords: "registry, service, images, repository, json" --- -# Docker Registry Reference - * [HTTP API V2](api.md) * [Storage Driver](../storage-drivers/index.md) * [Token Authentication Specification](auth/token.md) diff --git a/docs/spec/json.md b/docs/spec/json.md index 825b17ac..b646a4e7 100644 --- a/docs/spec/json.md +++ b/docs/spec/json.md @@ -2,13 +2,9 @@ published: false title: "Docker Distribution JSON Canonicalization" description: "Explains registry JSON objects" -keywords: ["registry, service, images, repository, json"] +keywords: "registry, service, images, repository, json" --- - - -# Docker Distribution JSON Canonicalization - To provide consistent content hashing of JSON objects throughout Docker Distribution APIs, the specification defines a canonical JSON format. Adopting such a canonicalization also aids in caching JSON responses. diff --git a/docs/spec/manifest-v2-1.md b/docs/spec/manifest-v2-1.md index b06c2f2a..925251aa 100644 --- a/docs/spec/manifest-v2-1.md +++ b/docs/spec/manifest-v2-1.md @@ -1,11 +1,9 @@ --- title: "Image Manifest V 2, Schema 1 " description: "image manifest for the Registry." -keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced, manifest"] +keywords: "registry, on-prem, images, tags, repository, distribution, api, advanced, manifest" --- -# Image Manifest Version 2, Schema 1 - This document outlines the format of of the V2 image manifest. The image manifest described herein was introduced in the Docker daemon in the [v1.3.0 release](https://github.com/docker/docker/commit/9f482a66ab37ec396ac61ed0c00d59122ac07453). diff --git a/docs/spec/manifest-v2-2.md b/docs/spec/manifest-v2-2.md index 1db8b33b..f2a2ce73 100644 --- a/docs/spec/manifest-v2-2.md +++ b/docs/spec/manifest-v2-2.md @@ -1,11 +1,9 @@ --- title: "Image Manifest V 2, Schema 2 " description: "image manifest for the Registry." -keywords: ["registry, on-prem, images, tags, repository, distribution, api, advanced, manifest"] +keywords: "registry, on-prem, images, tags, repository, distribution, api, advanced, manifest" --- -# Image Manifest Version 2, Schema 2 - This document outlines the format of of the V2 image manifest, schema version 2. The original (and provisional) image manifest for V2 (schema 1), was introduced in the Docker daemon in the [v1.3.0 diff --git a/docs/spec/menu.md b/docs/spec/menu.md deleted file mode 100644 index 9237e3ce..00000000 --- a/docs/spec/menu.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: "Reference" -description: "Explains registry JSON objects" -keywords: ["registry, service, images, repository, json"] -type: "menu" -identifier: "smn_registry_ref" ---- From cfe707930001a8082a9ae756229f3ca23b378900 Mon Sep 17 00:00:00 2001 From: Richard Scothern Date: Fri, 14 Oct 2016 17:03:08 -0700 Subject: [PATCH 14/19] Satisfy the latest go lint rules Signed-off-by: Richard Scothern --- registry/proxy/proxymanifeststore_test.go | 4 ++-- registry/storage/cache/cachecheck/suite.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/registry/proxy/proxymanifeststore_test.go b/registry/proxy/proxymanifeststore_test.go index 0d6b7171..26a26443 100644 --- a/registry/proxy/proxymanifeststore_test.go +++ b/registry/proxy/proxymanifeststore_test.go @@ -111,7 +111,7 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE stats: make(map[string]int), } - manifestDigest, err := populateRepo(t, ctx, truthRepo, name, tag) + manifestDigest, err := populateRepo(ctx, t, truthRepo, name, tag) if err != nil { t.Fatalf(err.Error()) } @@ -148,7 +148,7 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE } } -func populateRepo(t *testing.T, ctx context.Context, repository distribution.Repository, name, tag string) (digest.Digest, error) { +func populateRepo(ctx context.Context, t *testing.T, repository distribution.Repository, name, tag string) (digest.Digest, error) { m := schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, diff --git a/registry/storage/cache/cachecheck/suite.go b/registry/storage/cache/cachecheck/suite.go index cba5addd..a563c02a 100644 --- a/registry/storage/cache/cachecheck/suite.go +++ b/registry/storage/cache/cachecheck/suite.go @@ -16,12 +16,12 @@ import ( func CheckBlobDescriptorCache(t *testing.T, provider cache.BlobDescriptorCacheProvider) { ctx := context.Background() - checkBlobDescriptorCacheEmptyRepository(t, ctx, provider) - checkBlobDescriptorCacheSetAndRead(t, ctx, provider) - checkBlobDescriptorCacheClear(t, ctx, provider) + checkBlobDescriptorCacheEmptyRepository(ctx, t, provider) + checkBlobDescriptorCacheSetAndRead(ctx, t, provider) + checkBlobDescriptorCacheClear(ctx, t, provider) } -func checkBlobDescriptorCacheEmptyRepository(t *testing.T, ctx context.Context, provider cache.BlobDescriptorCacheProvider) { +func checkBlobDescriptorCacheEmptyRepository(ctx context.Context, t *testing.T, provider cache.BlobDescriptorCacheProvider) { if _, err := provider.Stat(ctx, "sha384:abc111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); err != distribution.ErrBlobUnknown { t.Fatalf("expected unknown blob error with empty store: %v", err) } @@ -59,7 +59,7 @@ func checkBlobDescriptorCacheEmptyRepository(t *testing.T, ctx context.Context, } } -func checkBlobDescriptorCacheSetAndRead(t *testing.T, ctx context.Context, provider cache.BlobDescriptorCacheProvider) { +func checkBlobDescriptorCacheSetAndRead(ctx context.Context, t *testing.T, provider cache.BlobDescriptorCacheProvider) { localDigest := digest.Digest("sha384:abc111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") expected := distribution.Descriptor{ Digest: "sha256:abc1111111111111111111111111111111111111111111111111111111111111", @@ -143,7 +143,7 @@ func checkBlobDescriptorCacheSetAndRead(t *testing.T, ctx context.Context, provi } } -func checkBlobDescriptorCacheClear(t *testing.T, ctx context.Context, provider cache.BlobDescriptorCacheProvider) { +func checkBlobDescriptorCacheClear(ctx context.Context, t *testing.T, provider cache.BlobDescriptorCacheProvider) { localDigest := digest.Digest("sha384:def111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") expected := distribution.Descriptor{ Digest: "sha256:def1111111111111111111111111111111111111111111111111111111111111", From 5adfbe34dbb7e3181c417998043cc9ba9f2a5702 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Mon, 14 Nov 2016 14:03:01 -0800 Subject: [PATCH 15/19] Remove newlines from end of error strings Golint now checks for new lines at the end of go error strings, remove these unneeded new lines. Signed-off-by: Derek McGowan (github: dmcgowan) --- registry/storage/garbagecollect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/storage/garbagecollect.go b/registry/storage/garbagecollect.go index bc340416..e8e8ecce 100644 --- a/registry/storage/garbagecollect.go +++ b/registry/storage/garbagecollect.go @@ -97,7 +97,7 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis }) if err != nil { - return fmt.Errorf("failed to mark: %v\n", err) + return fmt.Errorf("failed to mark: %v", err) } // sweep @@ -125,7 +125,7 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis } err = vacuum.RemoveBlob(string(dgst)) if err != nil { - return fmt.Errorf("failed to delete blob %s: %v\n", dgst, err) + return fmt.Errorf("failed to delete blob %s: %v", dgst, err) } } From 6c985f7f63ea1c7c89b7b390b1fd4a501da1817d Mon Sep 17 00:00:00 2001 From: Eric Yang Date: Thu, 24 Nov 2016 20:25:52 +0800 Subject: [PATCH 16/19] Update main.go Signed-off-by: Eric Yang --- cmd/registry/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/registry/main.go b/cmd/registry/main.go index 686af12b..c077a0c1 100644 --- a/cmd/registry/main.go +++ b/cmd/registry/main.go @@ -13,6 +13,7 @@ import ( _ "github.com/docker/distribution/registry/storage/driver/gcs" _ "github.com/docker/distribution/registry/storage/driver/inmemory" _ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront" + _ "github.com/docker/distribution/registry/storage/driver/middleware/redirect" _ "github.com/docker/distribution/registry/storage/driver/oss" _ "github.com/docker/distribution/registry/storage/driver/s3-aws" _ "github.com/docker/distribution/registry/storage/driver/s3-goamz" From fcbea606cb94e599dcc6d54102cb63429a2907a5 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 3 Jan 2017 10:10:11 -0800 Subject: [PATCH 17/19] Improve formatting of configuration.md Backported from master to release/2.5 Signed-off-by: Misty Stanley-Jones --- docs/configuration.md | 2495 ++++++++++++++--------------------------- 1 file changed, 832 insertions(+), 1663 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f8d05f02..67c07114 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,457 +1,453 @@ --- -title: "Registry Configuration Reference" +title: "Configuring a registry" description: "Explains how to configure a registry" -keywords: "registry, on-prem, images, tags, repository, distribution, configuration" +keywords: registry, on-prem, images, tags, repository, distribution, configuration --- -The Registry configuration is based on a YAML file, detailed below. While it comes with sane default values out of the box, you are heavily encouraged to review it exhaustively before moving your systems to production. +The Registry configuration is based on a YAML file, detailed below. While it +comes with sane default values out of the box, you should review it exhaustively +before moving your systems to production. ## Override specific configuration options -In a typical setup where you run your Registry from the official image, you can specify a configuration variable from the environment by passing `-e` arguments to your `docker run` stanza, or from within a Dockerfile using the `ENV` instruction. +In a typical setup where you run your Registry from the official image, you can +specify a configuration variable from the environment by passing `-e` arguments +to your `docker run` stanza or from within a Dockerfile using the `ENV` +instruction. To override a configuration option, create an environment variable named -`REGISTRY_variable` where *`variable`* is the name of the configuration option +`REGISTRY_variable` where `variable` is the name of the configuration option and the `_` (underscore) represents indention levels. For example, you can configure the `rootdirectory` of the `filesystem` storage backend: - storage: - filesystem: - rootdirectory: /var/lib/registry +```none +storage: + filesystem: + rootdirectory: /var/lib/registry +``` To override this value, set an environment variable like this: - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere +```none +REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere +``` This variable overrides the `/var/lib/registry` value to the `/somewhere` directory. ->**NOTE**: It is highly recommended to create a base configuration file with which environment variables can be used to tweak individual values. Overriding configuration sections with environment variables is not recommended. +> **Note**: Create a base configuration file with environment variables that can +> be configured to tweak individual values. Overriding configuration sections +> with environment variables is not recommended. ## Overriding the entire configuration file -If the default configuration is not a sound basis for your usage, or if you are having issues overriding keys from the environment, you can specify an alternate YAML configuration file by mounting it as a volume in the container. +If the default configuration is not a sound basis for your usage, or if you are +having issues overriding keys from the environment, you can specify an alternate +YAML configuration file by mounting it as a volume in the container. -Typically, create a new configuration file from scratch, and call it `config.yml`, then: +Typically, create a new configuration file from scratch,named `config.yml`, then +specify it in the `docker run` command: - docker run -d -p 5000:5000 --restart=always --name registry \ - -v `pwd`/config.yml:/etc/docker/registry/config.yml \ - registry:2 +```bash +$ docker run -d -p 5000:5000 --restart=always --name registry \ + -v `pwd`/config.yml:/etc/docker/registry/config.yml \ + registry:2 +``` -You can (and probably should) use [this as a starting point](https://github.com/docker/distribution/blob/master/cmd/registry/config-example.yml). +Use this +[example YAML file](https://github.com/docker/distribution/blob/master/cmd/registry/config-example.yml) +as a starting point. ## List of configuration options -This section lists all the registry configuration options. Some options in -the list are mutually exclusive. So, make sure to read the detailed reference -information about each option that appears later in this page. +These are all configuration options for the registry. Some options in the list +are mutually exclusive. Read the detailed reference information about each +option before finalizing your configuration. - version: 0.1 - log: - level: debug - formatter: text - fields: - service: registry - environment: staging - hooks: - - type: mail - disabled: true - levels: - - panic - options: - smtp: - addr: mail.example.com:25 - username: mailuser - password: password - insecure: true - from: sender@example.com - to: - - errors@example.com - loglevel: debug # deprecated: use "log" - storage: - filesystem: - rootdirectory: /var/lib/registry - maxthreads: 100 - azure: - accountname: accountname - accountkey: base64encodedaccountkey - container: containername - gcs: - bucket: bucketname - keyfile: /path/to/keyfile - rootdirectory: /gcs/object/name/prefix - chunksize: 5242880 - s3: - accesskey: awsaccesskey - secretkey: awssecretkey - region: us-west-1 - regionendpoint: http://myobjects.local - bucket: bucketname - encrypt: true - keyid: mykeyid - secure: true - v4auth: true - chunksize: 5242880 - rootdirectory: /s3/object/name/prefix - swift: - username: username - password: password - authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth - tenant: tenantname - tenantid: tenantid - domain: domain name for Openstack Identity v3 API - domainid: domain id for Openstack Identity v3 API - insecureskipverify: true - region: fr - container: containername - rootdirectory: /swift/object/name/prefix - oss: - accesskeyid: accesskeyid - accesskeysecret: accesskeysecret - region: OSS region name - endpoint: optional endpoints - internal: optional internal endpoint - bucket: OSS bucket - encrypt: optional data encryption setting - secure: optional ssl setting - chunksize: optional size valye - rootdirectory: optional root directory - inmemory: # This driver takes no parameters - delete: - enabled: false - redirect: - disable: false - cache: - blobdescriptor: redis - maintenance: - uploadpurging: - enabled: true - age: 168h - interval: 24h - dryrun: false - readonly: - enabled: false - auth: - silly: - realm: silly-realm - service: silly-service - token: - realm: token-realm - service: token-service - issuer: registry-token-issuer - rootcertbundle: /root/certs/bundle - htpasswd: - realm: basic-realm - path: /path/to/htpasswd - middleware: - registry: - - name: ARegistryMiddleware - options: - foo: bar - repository: - - name: ARepositoryMiddleware - options: - foo: bar - storage: - - name: cloudfront - options: - baseurl: https://my.cloudfronted.domain.com/ - privatekey: /path/to/pem - keypairid: cloudfrontkeypairid - duration: 3000s - storage: - - name: redirect - options: - baseurl: https://example.com/ - reporting: - bugsnag: - apikey: bugsnagapikey - releasestage: bugsnagreleasestage - endpoint: bugsnagendpoint - newrelic: - licensekey: newreliclicensekey - name: newrelicname - verbose: true - http: - addr: localhost:5000 - prefix: /my/nested/registry/ - host: https://myregistryaddress.org:5000 - secret: asecretforlocaldevelopment - relativeurls: false - tls: - certificate: /path/to/x509/public - key: /path/to/x509/private - clientcas: - - /path/to/ca.pem - - /path/to/another/ca.pem - letsencrypt: - cachefile: /path/to/cache-file - email: emailused@letsencrypt.com - debug: - addr: localhost:5001 +```none +version: 0.1 +log: + level: debug + formatter: text + fields: + service: registry + environment: staging + hooks: + - type: mail + disabled: true + levels: + - panic + options: + smtp: + addr: mail.example.com:25 + username: mailuser + password: password + insecure: true + from: sender@example.com + to: + - errors@example.com +loglevel: debug # deprecated: use "log" +storage: + filesystem: + rootdirectory: /var/lib/registry + maxthreads: 100 + azure: + accountname: accountname + accountkey: base64encodedaccountkey + container: containername + gcs: + bucket: bucketname + keyfile: /path/to/keyfile + rootdirectory: /gcs/object/name/prefix + chunksize: 5242880 + s3: + accesskey: awsaccesskey + secretkey: awssecretkey + region: us-west-1 + regionendpoint: http://myobjects.local + bucket: bucketname + encrypt: true + keyid: mykeyid + secure: true + v4auth: true + chunksize: 5242880 + rootdirectory: /s3/object/name/prefix + swift: + username: username + password: password + authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth + tenant: tenantname + tenantid: tenantid + domain: domain name for Openstack Identity v3 API + domainid: domain id for Openstack Identity v3 API + insecureskipverify: true + region: fr + container: containername + rootdirectory: /swift/object/name/prefix + oss: + accesskeyid: accesskeyid + accesskeysecret: accesskeysecret + region: OSS region name + endpoint: optional endpoints + internal: optional internal endpoint + bucket: OSS bucket + encrypt: optional data encryption setting + secure: optional ssl setting + chunksize: optional size valye + rootdirectory: optional root directory + inmemory: # This driver takes no parameters + delete: + enabled: false + redirect: + disable: false + cache: + blobdescriptor: redis + maintenance: + uploadpurging: + enabled: true + age: 168h + interval: 24h + dryrun: false + readonly: + enabled: false +auth: + silly: + realm: silly-realm + service: silly-service + token: + realm: token-realm + service: token-service + issuer: registry-token-issuer + rootcertbundle: /root/certs/bundle + htpasswd: + realm: basic-realm + path: /path/to/htpasswd +middleware: + registry: + - name: ARegistryMiddleware + options: + foo: bar + repository: + - name: ARepositoryMiddleware + options: + foo: bar + storage: + - name: cloudfront + options: + baseurl: https://my.cloudfronted.domain.com/ + privatekey: /path/to/pem + keypairid: cloudfrontkeypairid + duration: 3000s + storage: + - name: redirect + options: + baseurl: https://example.com/ +reporting: + bugsnag: + apikey: bugsnagapikey + releasestage: bugsnagreleasestage + endpoint: bugsnagendpoint + newrelic: + licensekey: newreliclicensekey + name: newrelicname + verbose: true +http: + addr: localhost:5000 + prefix: /my/nested/registry/ + host: https://myregistryaddress.org:5000 + secret: asecretforlocaldevelopment + relativeurls: false + tls: + certificate: /path/to/x509/public + key: /path/to/x509/private + clientcas: + - /path/to/ca.pem + - /path/to/another/ca.pem + letsencrypt: + cachefile: /path/to/cache-file + email: emailused@letsencrypt.com + debug: + addr: localhost:5001 + headers: + X-Content-Type-Options: [nosniff] +notifications: + endpoints: + - name: alistener + disabled: false + url: https://my.listener.com/event + headers: + timeout: 500 + threshold: 5 + backoff: 1000 +redis: + addr: localhost:6379 + password: asecret + db: 0 + dialtimeout: 10ms + readtimeout: 10ms + writetimeout: 10ms + pool: + maxidle: 16 + maxactive: 64 + idletimeout: 300s +health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 + file: + - file: /path/to/checked/file + interval: 10s + http: + - uri: http://server.to.check/must/return/200 headers: - X-Content-Type-Options: [nosniff] - notifications: - endpoints: - - name: alistener - disabled: false - url: https://my.listener.com/event - headers: - timeout: 500 - threshold: 5 - backoff: 1000 - redis: - addr: localhost:6379 - password: asecret - db: 0 - dialtimeout: 10ms - readtimeout: 10ms - writetimeout: 10ms - pool: - maxidle: 16 - maxactive: 64 - idletimeout: 300s - health: - storagedriver: - enabled: true - interval: 10s - threshold: 3 - file: - - file: /path/to/checked/file - interval: 10s - http: - - uri: http://server.to.check/must/return/200 - headers: - Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] - statuscode: 200 - timeout: 3s - interval: 10s - threshold: 3 - tcp: - - addr: redis-server.domain.com:6379 - timeout: 3s - interval: 10s - threshold: 3 - proxy: - remoteurl: https://registry-1.docker.io - username: [username] - password: [password] - compatibility: - schema1: - signingkeyfile: /etc/registry/key.json + Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] + statuscode: 200 + timeout: 3s + interval: 10s + threshold: 3 + tcp: + - addr: redis-server.domain.com:6379 + timeout: 3s + interval: 10s + threshold: 3 +proxy: + remoteurl: https://registry-1.docker.io + username: [username] + password: [password] +compatibility: + schema1: + signingkeyfile: /etc/registry/key.json +``` In some instances a configuration option is **optional** but it contains child -options marked as **required**. This indicates that you can omit the parent with +options marked as **required**. In these cases, you can omit the parent with all its children. However, if the parent is included, you must also include all the children marked **required**. -## version +## `version` - version: 0.1 +```none +version: 0.1 +``` The `version` option is **required**. It specifies the configuration's version. It is expected to remain a top-level field, to allow for a consistent version check before parsing the remainder of the configuration file. -## log +## `log` The `log` subsection configures the behavior of the logging system. The logging system outputs everything to stdout. You can adjust the granularity and format with this configuration section. - log: - level: debug - formatter: text - fields: - service: registry - environment: staging +```none +log: + level: debug + formatter: text + fields: + service: registry + environment: staging +``` - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- level - - no - - Sets the sensitivity of logging output. Permitted values are - error, warn, info and - debug. The default is info. -
- formatter - - no - - This selects the format of logging output. The format primarily affects how keyed - attributes for a log line are encoded. Options are text, json or - logstash. The default is text. -
- fields - - no - - A map of field names to values. These are added to every log line for - the context. This is useful for identifying log messages source after - being mixed in other systems. -
+| Parameter | Required | Description | +|-------------|----------|-------------| +| `level` | no | Sets the sensitivity of logging output. Permitted values are `error`, `warn`, `info`, and `debug`. The default is `info`. | +| `formatter` | no | This selects the format of logging output. The format primarily affects how keyed attributes for a log line are encoded. Options are `text`, `json`, and `logstash`. The default is `text`. | +| `fields` | no | A map of field names to values. These are added to every log line for the context. This is useful for identifying log messages source after being mixed in other systems. | -## hooks +## `hooks` - hooks: - - type: mail - levels: - - panic - options: - smtp: - addr: smtp.sendhost.com:25 - username: sendername - password: password - insecure: true - from: name@sendhost.com - to: - - name@receivehost.com +```none +hooks: + - type: mail + levels: + - panic + options: + smtp: + addr: smtp.sendhost.com:25 + username: sendername + password: password + insecure: true + from: name@sendhost.com + to: + - name@receivehost.com +``` The `hooks` subsection configures the logging hooks' behavior. This subsection includes a sequence handler which you can use for sending mail, for example. Refer to `loglevel` to configure the level of messages printed. -## loglevel +## `loglevel` > **DEPRECATED:** Please use [log](#log) instead. - loglevel: debug +```none +loglevel: debug +``` Permitted values are `error`, `warn`, `info` and `debug`. The default is `info`. -## storage +## `storage` - storage: - filesystem: - rootdirectory: /var/lib/registry - azure: - accountname: accountname - accountkey: base64encodedaccountkey - container: containername - gcs: - bucket: bucketname - keyfile: /path/to/keyfile - rootdirectory: /gcs/object/name/prefix - s3: - accesskey: awsaccesskey - secretkey: awssecretkey - region: us-west-1 - regionendpoint: http://myobjects.local - bucket: bucketname - encrypt: true - keyid: mykeyid - secure: true - v4auth: true - chunksize: 5242880 - rootdirectory: /s3/object/name/prefix - swift: - username: username - password: password - authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth - tenant: tenantname - tenantid: tenantid - domain: domain name for Openstack Identity v3 API - domainid: domain id for Openstack Identity v3 API - insecureskipverify: true - region: fr - container: containername - rootdirectory: /swift/object/name/prefix - oss: - accesskeyid: accesskeyid - accesskeysecret: accesskeysecret - region: OSS region name - endpoint: optional endpoints - internal: optional internal endpoint - bucket: OSS bucket - encrypt: optional data encryption setting - secure: optional ssl setting - chunksize: optional size valye - rootdirectory: optional root directory - inmemory: - delete: - enabled: false - cache: - blobdescriptor: inmemory - maintenance: - uploadpurging: - enabled: true - age: 168h - interval: 24h - dryrun: false - redirect: - disable: false +```none +storage: + filesystem: + rootdirectory: /var/lib/registry + azure: + accountname: accountname + accountkey: base64encodedaccountkey + container: containername + gcs: + bucket: bucketname + keyfile: /path/to/keyfile + rootdirectory: /gcs/object/name/prefix + s3: + accesskey: awsaccesskey + secretkey: awssecretkey + region: us-west-1 + regionendpoint: http://myobjects.local + bucket: bucketname + encrypt: true + keyid: mykeyid + secure: true + v4auth: true + chunksize: 5242880 + rootdirectory: /s3/object/name/prefix + swift: + username: username + password: password + authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth + tenant: tenantname + tenantid: tenantid + domain: domain name for Openstack Identity v3 API + domainid: domain id for Openstack Identity v3 API + insecureskipverify: true + region: fr + container: containername + rootdirectory: /swift/object/name/prefix + oss: + accesskeyid: accesskeyid + accesskeysecret: accesskeysecret + region: OSS region name + endpoint: optional endpoints + internal: optional internal endpoint + bucket: OSS bucket + encrypt: optional data encryption setting + secure: optional ssl setting + chunksize: optional size valye + rootdirectory: optional root directory + inmemory: + delete: + enabled: false + cache: + blobdescriptor: inmemory + maintenance: + uploadpurging: + enabled: true + age: 168h + interval: 24h + dryrun: false + readonly: + enabled: false + redirect: + disable: false +``` -The storage option is **required** and defines which storage backend is in use. -You must configure one backend; if you configure more, the registry returns an error. You can choose any of these backend storage drivers: +The `storage` option is **required** and defines which storage backend is in +use. You must configure exactly one backend. If you configure more, the registry +returns an error. You can choose any of these backend storage drivers: -| Storage driver | Description -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `filesystem` | Uses the local disk to store registry files. It is ideal for development and may be appropriate for some small-scale production applications. See the [driver's reference documentation](storage-drivers/filesystem.md). | -| `azure` | Uses Microsoft's Azure Blob Storage. See the [driver's reference documentation](storage-drivers/azure.md). | -| `gcs` | Uses Google Cloud Storage. See the [driver's reference documentation](storage-drivers/gcs.md). | -| `s3` | Uses Amazon's Simple Storage Service (S3) and compatible Storage Services. See the [driver's reference documentation](storage-drivers/s3.md). | -| `swift` | Uses Openstack Swift object storage. See the [driver's reference documentation](storage-drivers/swift.md). | -| `oss` | Uses Aliyun OSS for object storage. See the [driver's reference documentation](storage-drivers/oss.md). | +| Storage driver | Description | +|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `filesystem` | Uses the local disk to store registry files. It is ideal for development and may be appropriate for some small-scale production applications. See the [driver's reference documentation](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/filesystem.md). | +| `azure` | Uses Microsoft Azure Blob Storage. See the [driver's reference documentation](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/azure.md). | +| `gcs` | Uses Google Cloud Storage. See the [driver's reference documentation](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/gcs.md). | +| `s3` | Uses Amazon Simple Storage Service (S3) and compatible Storage Services. See the [driver's reference documentation](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/s3.md). | +| `swift` | Uses Openstack Swift object storage. See the [driver's reference documentation](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/swift.md). | +| `oss` | Uses Aliyun OSS for object storage. See the [driver's reference documentation](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/oss.md). | -For purely tests purposes, you can use the [`inmemory` storage -driver](storage-drivers/inmemory.md). If you would like to run a registry from -volatile memory, use the [`filesystem` driver](storage-drivers/filesystem.md) on -a ramdisk. +For testing only, you can use the [`inmemory` storage +driver](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/inmemory.md). +If you would like to run a registry from volatile memory, use the +[`filesystem` driver](https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers/filesystem.md) +on a ramdisk. -If you are deploying a registry on Windows, be aware that a Windows volume -mounted from the host is not recommended. Instead, you can use a S3, or Azure, -backing data-store. If you do use a Windows volume, you must ensure that the -`PATH` to the mount point is within Windows' `MAX_PATH` limits (typically 255 -characters). Failure to do so can result in the following error message: +If you are deploying a registry on Windows, a Windows volume mounted from the +host is not recommended. Instead, you can use a S3 or Azure backing +data-store. If you do use a Windows volume, the length of the `PATH` to +the mount point must be within the `MAX_PATH` limits (typically 255 characters), +or this error will occur: - mkdir /XXX protocol error and your registry will not function properly. +```none +mkdir /XXX protocol error and your registry will not function properly. +``` -### Maintenance +### `maintenance` -Currently upload purging and read-only mode are the only maintenance functions available. -These and future maintenance functions which are related to storage can be configured under -the maintenance section. +Currently, upload purging and read-only mode are the only `maintenance` +functions available. -### Upload Purging +### `uploadpurging` -Upload purging is a background process that periodically removes orphaned files from the upload -directories of the registry. Upload purging is enabled by default. To -configure upload directory purging, the following parameters -must be set. +Upload purging is a background process that periodically removes orphaned files +from the upload directories of the registry. Upload purging is enabled by +default. To configure upload directory purging, the following parameters must +be set. -| Parameter | Required | Description - --------- | -------- | ----------- -`enabled` | yes | Set to true to enable upload purging. Default=true. | -`age` | yes | Upload directories which are older than this age will be deleted. Default=168h (1 week) -`interval` | yes | The interval between upload directory purging. Default=24h. -`dryrun` | yes | dryrun can be set to true to obtain a summary of what directories will be deleted. Default=false. +| Parameter | Required | Description | +|------------|----------|----------------------------------------------------------------------------------------------------| +| `enabled` | yes | Set to `true` to enable upload purging. Defaults to `true`. | +| `age` | yes | Upload directories which are older than this age will be deleted.Defaults to `168h` (1 week). | +| `interval` | yes | The interval between upload directory purging. Defaults to `24h`. | +| `dryrun` | yes | Set `dryrun` to `true` to obtain a summary of what directories will be deleted. Defaults to `false`.| -Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week). +> **Note**: `age` and `interval` are strings containing a number with optional +fraction and a unit suffix. Some examples: `45m`, `2h10m`, `168h`. -### Read-only mode +### `readonly` If the `readonly` section under `maintenance` has `enabled` set to `true`, clients will not be allowed to write to the registry. This mode is useful to @@ -461,239 +457,153 @@ restarted with readonly's `enabled` set to true. After the garbage collection pass finishes, the registry may be restarted again, this time with `readonly` removed from the configuration (or set to false). -### delete +### `delete` -Use the `delete` subsection to enable the deletion of image blobs and manifests +Use the `delete` structure to enable the deletion of image blobs and manifests by digest. It defaults to false, but it can be enabled by writing the following on the configuration file: - delete: - enabled: true +```none +delete: + enabled: true +``` -### cache +### `cache` -Use the `cache` subsection to enable caching of data accessed in the storage +Use the `cache` structure to enable caching of data accessed in the storage backend. Currently, the only available cache provides fast access to layer -metadata. This, if configured, uses the `blobdescriptor` field. +metadata, which uses the `blobdescriptor` field if configured. -You can set `blobdescriptor` field to `redis` or `inmemory`. The `redis` value uses -a Redis pool to cache layer metadata. The `inmemory` value uses an in memory -map. +You can set `blobdescriptor` field to `redis` or `inmemory`. If set to `redis`,a +Redis pool caches layer metadata. If set to `inmemory`, an in-memory map caches +layer metadata. ->**NOTE**: Formerly, `blobdescriptor` was known as `layerinfo`. While these ->are equivalent, `layerinfo` has been deprecated, in favor or ->`blobdescriptor`. +> **NOTE**: Formerly, `blobdescriptor` was known as `layerinfo`. While these +> are equivalent, `layerinfo` has been deprecated. -### redirect +### `redirect` The `redirect` subsection provides configuration for managing redirects from content backends. For backends that support it, redirecting is enabled by -default. Certain deployment scenarios may prefer to route all data through the -Registry, rather than redirecting to the backend. This may be more efficient -when using a backend that is not co-located or when a registry instance is -doing aggressive caching. +default. In certain deployment scenarios, you may decide to route all data +through the Registry, rather than redirecting to the backend. This may be more +efficient when using a backend that is not co-located or when a registry +instance is aggressively caching. -Redirects can be disabled by adding a single flag `disable`, set to `true` +To disable redirects, add a single flag `disable`, set to `true` under the `redirect` section: - redirect: - disable: true +```none +redirect: + disable: true +``` +## `auth` -## auth +```none +auth: + silly: + realm: silly-realm + service: silly-service + token: + realm: token-realm + service: token-service + issuer: registry-token-issuer + rootcertbundle: /root/certs/bundle + htpasswd: + realm: basic-realm + path: /path/to/htpasswd +``` - auth: - silly: - realm: silly-realm - service: silly-service - token: - realm: token-realm - service: token-service - issuer: registry-token-issuer - rootcertbundle: /root/certs/bundle - htpasswd: - realm: basic-realm - path: /path/to/htpasswd +The `auth` option is **optional**. Possible auth providers include: -The `auth` option is **optional**. There are -currently 3 possible auth providers, `silly`, `token` and `htpasswd`. You can configure only -one `auth` provider. +- [`silly`](#silly) +- [`token`](#token) +- [`htpasswd`](#token) -### silly +You can configure only one authentication provider. -The `silly` auth is only for development purposes. It simply checks for the -existence of the `Authorization` header in the HTTP request. It has no regard for -the header's value. If the header does not exist, the `silly` auth responds with a -challenge response, echoing back the realm, service, and scope that access was -denied for. +### `silly` + +The `silly` authentication provider is only appropriate for development. It simply checks +for the existence of the `Authorization` header in the HTTP request. It does not +check the header's value. If the header does not exist, the `silly` auth +responds with a challenge response, echoing back the realm, service, and scope +for which access was denied. The following values are used to configure the response: - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- realm - - yes - - The realm in which the registry server authenticates. -
- service - - yes - - The service being authenticated. -
+| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `realm` | yes | The realm in which the registry server authenticates. | +| `service` | yes | The service being authenticated. | + +### `token` + +Token-based authentication allows you to decouple the authentication system from +the registry. It is an established authentication paradigm with a high degree of +security. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `realm` | yes | The realm in which the registry server authenticates. | +| `service` | yes | The service being authenticated. | +| `issuer` | yes | The name of the token issuer. The issuer inserts this into the token so it must match the value configured for the issuer. | +| `rootcertbundle` | yes | The absolute path to the root certificate bundle. This bundle contains the public part of the certificates used to sign authentication tokens. | +For more information about Token based authentication configuration, see the +[specification](spec/auth/token.md). -### token +### `htpasswd` -Token based authentication allows the authentication system to be decoupled from -the registry. It is a well established authentication paradigm with a high -degree of security. +The _htpasswd_ authentication backed allows you to configure basic +authentication using an +[Apache htpasswd file](https://httpd.apache.org/docs/2.4/programs/htpasswd.html). +The only supported password format is +[`bcrypt`](http://en.wikipedia.org/wiki/Bcrypt). Entries with other hash types +are ignored. The `htpasswd` file is loaded once, at startup. If the file is +invalid, the registry will display an error and will not start. - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- realm - - yes - - The realm in which the registry server authenticates. -
- service - - yes - - The service being authenticated. -
- issuer - - yes - -The name of the token issuer. The issuer inserts this into -the token so it must match the value configured for the issuer. -
- rootcertbundle - - yes - -The absolute path to the root certificate bundle. This bundle contains the -public part of the certificates that is used to sign authentication tokens. -
- -For more information about Token based authentication configuration, see the [specification](spec/auth/token.md). - -### htpasswd - -The _htpasswd_ authentication backed allows one to configure basic auth using an -[Apache htpasswd -file](https://httpd.apache.org/docs/2.4/programs/htpasswd.html). Only -[`bcrypt`](http://en.wikipedia.org/wiki/Bcrypt) format passwords are supported. -Entries with other hash types will be ignored. The htpasswd file is loaded once, -at startup. If the file is invalid, the registry will display an error and will -not start. - -> __WARNING:__ This authentication scheme should only be used with TLS -> configured, since basic authentication sends passwords as part of the http +> **Warning**: Only use the `htpasswd` authentication scheme with TLS +> configured, since basic authentication sends passwords as part of the HTTP > header. - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- realm - - yes - - The realm in which the registry server authenticates. -
- path - - yes - - Path to htpasswd file to load at startup. -
+| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `realm` | yes | The realm in which the registry server authenticates. | +| `path` | yes | The path to the `htpasswd` file to load at startup. | -## middleware +## `middleware` -The `middleware` option is **optional**. Use this option to inject middleware at -named hook points. All middleware must implement the same interface as the -object they're wrapping. This means a registry middleware must implement the -`distribution.Namespace` interface, repository middleware must implement -`distribution.Repository`, and storage middleware must implement +The `middleware` structure is **optional**. Use this option to inject middleware at +named hook points. Each middleware must implement the same interface as the +object it is wrapping. For instance, a registry middleware must implement the +`distribution.Namespace` interface, while a repository middleware must implement +`distribution.Repository`, and a storage middleware must implement `driver.StorageDriver`. -An example configuration of the `cloudfront` middleware, a storage middleware: +This is an example configuration of the `cloudfront` middleware, a storage +middleware: - middleware: - registry: - - name: ARegistryMiddleware - options: - foo: bar - repository: - - name: ARepositoryMiddleware - options: - foo: bar - storage: - - name: cloudfront - options: - baseurl: https://my.cloudfronted.domain.com/ - privatekey: /path/to/pem - keypairid: cloudfrontkeypairid - duration: 3000s +```none +middleware: + registry: + - name: ARegistryMiddleware + options: + foo: bar + repository: + - name: ARepositoryMiddleware + options: + foo: bar + storage: + - name: cloudfront + options: + baseurl: https://my.cloudfronted.domain.com/ + privatekey: /path/to/pem + keypairid: cloudfrontkeypairid + duration: 3000s +``` Each middleware entry has `name` and `options` entries. The `name` must correspond to the name under which the middleware registers itself. The @@ -703,375 +613,132 @@ it supports any interesting structures desired, leaving it up to the middleware initialization function to best determine how to handle the specific interpretation of the options. -### cloudfront +### `cloudfront` - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- baseurl - - yes - - SCHEME://HOST[/PATH] at which Cloudfront is served. -
- privatekey - - yes - - Private Key for Cloudfront provided by AWS. -
- keypairid - - yes - - Key pair ID provided by AWS. -
- duration - - no - - Specify a `duration` by providing an integer and a unit. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `3000s` is a valid duration; there should be no space between the integer and unit. If you do not specify a `duration` or specify an integer without a time unit, this defaults to 20 minutes. -
-### redirect +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `baseurl` | yes | The `SCHEME://HOST[/PATH]` at which Cloudfront is served. | +| `privatekey` | yes | The private key for Cloudfront, provided by AWS. | +| `keypairid` | yes | The key pair ID provided by AWS. | +| `duration` | no | An integer and unit for the duration of the Cloudfront session. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, or `h`. For example, `3000s` is valid, but `3000 s` is not. If you do not specify a `duration` or you specify an integer without a time unit, the duration defaults to `20m` (20 minutes).| -In place of the `cloudfront` storage middleware, the `redirect` -storage middleware can be used to specify a custom URL to a location -of a proxy for the layer stored by the S3 storage driver. +### `redirect` + +You can use the `redirect` storage middleware to specify a custom URL to a +location of a proxy for the layer stored by the S3 storage driver. | Parameter | Required | Description | -| --- | --- | --- | -| baseurl | yes | `SCHEME://HOST` at which layers are served. Can also contain port. For example, `https://example.com:5443`. | +|-----------|----------|-------------------------------------------------------------------------------------------------------------| +| `baseurl` | yes | `SCHEME://HOST` at which layers are served. Can also contain port. For example, `https://example.com:5443`. | -## reporting +## `reporting` - reporting: - bugsnag: - apikey: bugsnagapikey - releasestage: bugsnagreleasestage - endpoint: bugsnagendpoint - newrelic: - licensekey: newreliclicensekey - name: newrelicname - verbose: true +```none +reporting: + bugsnag: + apikey: bugsnagapikey + releasestage: bugsnagreleasestage + endpoint: bugsnagendpoint + newrelic: + licensekey: newreliclicensekey + name: newrelicname + verbose: true +``` The `reporting` option is **optional** and configures error and metrics -reporting tools. At the moment only two services are supported, [New -Relic](http://newrelic.com/) and [Bugsnag](http://bugsnag.com), a valid -configuration may contain both. +reporting tools. At the moment only two services are supported: -### bugsnag +- [Bugsnag](#bugsnag) +- [New Relic](#new-relic) - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- apikey - - yes - - API Key provided by Bugsnag -
- releasestage - - no - - Tracks where the registry is deployed, for example, - production,staging, or - development. -
- endpoint - - no - - Specify the enterprise Bugsnag endpoint. -
+A valid configuration may contain both. + +### `bugsnag` + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `apikey` | yes | The API Key provided by Bugsnag. | +| `releasestage` | no | Tracks where the registry is deployed, using a string like `production`, `staging`, or `development`.| +| `endpoint`| no | The enterprise Bugsnag endpoint. | + +### `newrelic` + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `licensekey` | yes | License key provided by New Relic. | +| `name` | no | New Relic application name. | +| `verbose`| no | Set to `true` to enable New Relic debugging output on `stdout`. | + +## `http` + +```none +http: + addr: localhost:5000 + net: tcp + prefix: /my/nested/registry/ + host: https://myregistryaddress.org:5000 + secret: asecretforlocaldevelopment + relativeurls: false + tls: + certificate: /path/to/x509/public + key: /path/to/x509/private + clientcas: + - /path/to/ca.pem + - /path/to/another/ca.pem + letsencrypt: + cachefile: /path/to/cache-file + email: emailused@letsencrypt.com + debug: + addr: localhost:5001 + headers: + X-Content-Type-Options: [nosniff] +``` + +The `http` option details the configuration for the HTTP server that hosts the +registry. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `addr` | yes | The address for which the server should accept connections. The form depends on a network type (see the `net` option). Use `HOST:PORT` for TCP and `FILE` for a UNIX socket. | +| `net` | no | The network used to create a listening socket. Known networks are `unix` and `tcp`. | +| `prefix` | no | If the server does not run at the root path, set this to the value of the prefix. The root path is the section before `v2`. It requires both preceding and trailing slashes, such as in the example `/path/`. | +| `host` | no | A fully-qualified URL for an externally-reachable address for the registry. If present, it is used when creating generated URLs. Otherwise, these URLs are derived from client requests. | +| `secret` | no | A random piece of data used to sign state that may be stored with the client to protect against tampering. For production environments you should generate a random piece of data using a cryptographically secure random generator. If you omit the secret, the registry will automatically generate a secret when it starts. **If you are building a cluster of registries behind a load balancer, you MUST ensure the secret is the same for all registries.**| +| `relativeurls`| no | If `true`, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL. **This option is not compatible with Docker 1.7 and earlier.**| -### newrelic +### `tls` - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- licensekey - - yes - - License key provided by New Relic. -
- name - - no - - New Relic application name. -
- verbose - - no - - Enable New Relic debugging output on stdout. -
- -## http - - http: - addr: localhost:5000 - net: tcp - prefix: /my/nested/registry/ - host: https://myregistryaddress.org:5000 - secret: asecretforlocaldevelopment - relativeurls: false - tls: - certificate: /path/to/x509/public - key: /path/to/x509/private - clientcas: - - /path/to/ca.pem - - /path/to/another/ca.pem - letsencrypt: - cachefile: /path/to/cache-file - email: emailused@letsencrypt.com - debug: - addr: localhost:5001 - headers: - X-Content-Type-Options: [nosniff] - -The `http` option details the configuration for the HTTP server that hosts the registry. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- addr - - yes - - The address for which the server should accept connections. The form depends on a network type (see net option): - HOST:PORT for tcp and FILE for a unix socket. -
- net - - no - - The network which is used to create a listening socket. Known networks are unix and tcp. - The default empty value means tcp. -
- prefix - - no - -If the server does not run at the root path use this value to specify the -prefix. The root path is the section before v2. It -should have both preceding and trailing slashes, for example /path/. -
- host - - no - -This parameter specifies an externally-reachable address for the registry, as a -fully qualified URL. If present, it is used when creating generated URLs. -Otherwise, these URLs are derived from client requests. -
- secret - - yes - -A random piece of data. This is used to sign state that may be stored with the -client to protect against tampering. For production environments you should generate a -random piece of data using a cryptographically secure random generator. This -configuration parameter may be omitted, in which case the registry will automatically -generate a secret at launch. -

-WARNING: If you are building a cluster of registries behind a load balancer, you MUST -ensure the secret is the same for all registries. -

- relativeurls - - no - - Specifies that the registry should return relative URLs in Location headers. - The client is responsible for resolving the correct URL. This option is not - compatible with Docker 1.7 and earlier. -
- - -### tls - -The `tls` struct within `http` is **optional**. Use this to configure TLS -for the server. If you already have a server such as Nginx or Apache running on -the same host as the registry, you may prefer to configure TLS termination there +The `tls` structure within `http` is **optional**. Use this to configure TLS +for the server. If you already have a web server running on +the same host as the registry, you may prefer to configure TLS on that web server and proxy connections to the registry server. - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- certificate - - yes - - Absolute path to x509 cert file -
- key - - yes - - Absolute path to x509 private key file. -
- clientcas - - no - - An array of absolute paths to an x509 CA file -
+| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `certificate` | yes | Absolute path to the x509 certificate file. | +| `key` | yes | Absolute path to the x509 private key file. | +| `clientcas` | no | An array of absolute paths to x509 CA files. | -### letsencrypt +### `letsencrypt` -The `letsencrypt` struct within `tls` is **optional**. Use this to configure TLS -certificates provided by [Let's Encrypt](https://letsencrypt.org/how-it-works/). +The `letsencrypt` structure within `tls` is **optional**. Use this to configure +TLS certificates provided by +[Let's Encrypt](https://letsencrypt.org/how-it-works/). - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- cachefile - - yes - - Absolute path to a file for the Let's Encrypt agent to cache data -
- email - - yes - - Email used to register with Let's Encrypt. -
+>**NOTE**: When using Let's Encrypt, ensure that the outward-facing address is +> accessible on port `443`. The registry defaults to listening on port `5000`. +> If you run the registry as a container, consider adding the flag `-p 443:5000` +> to the `docker run` command or using a similar setting in a cloud +> configuration. -### debug +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `cachefile` | yes | Absolute path to a file where the Let's Encrypt agent can cache data. | +| `email` | yes | The email address used to register with Let's Encrypt. | + +### `debug` The `debug` option is **optional** . Use it to configure a debug server that can be helpful in diagnosing problems. The debug endpoint can be used for @@ -1079,11 +746,10 @@ monitoring registry metrics and health, as well as profiling. Sensitive information may be available via the debug endpoint. Please be certain that access to the debug endpoint is locked down in a production environment. -The `debug` section takes a single, required `addr` parameter. This parameter -specifies the `HOST:PORT` on which the debug server should accept connections. +The `debug` section takes a single required `addr` parameter, which specifies +the `HOST:PORT` on which the debug server should accept connections. - -### headers +### `headers` The `headers` option is **optional** . Use it to specify headers that the HTTP server should include in responses. This can be used for security headers such @@ -1095,776 +761,279 @@ header's payload values. Including `X-Content-Type-Options: [nosniff]` is recommended, so that browsers will not interpret content as HTML if they are directed to load a page from the -registry. This header is included in the example configuration files. +registry. This header is included in the example configuration file. -## notifications +## `notifications` - notifications: - endpoints: - - name: alistener - disabled: false - url: https://my.listener.com/event - headers: - timeout: 500 - threshold: 5 - backoff: 1000 +```none +notifications: + endpoints: + - name: alistener + disabled: false + url: https://my.listener.com/event + headers: + timeout: 500 + threshold: 5 + backoff: 1000 + ignoredmediatypes: + - application/octet-stream +``` The notifications option is **optional** and currently may contain a single option, `endpoints`. -### endpoints +### `endpoints` -Endpoints is a list of named services (URLs) that can accept event notifications. +The `endpoints` structure contains a list of named services (URLs) that can +accept event notifications. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- name - - yes - -A human readable name for the service. -
- disabled - - no - -A boolean to enable/disable notifications for a service. -
- url - - yes - -The URL to which events should be published. -
- headers - - yes - - Static headers to add to each request. Each header's name should be a key - underneath headers, and each value is a list of payloads for that - header name. Note that values must always be lists. -
- timeout - - yes - - An HTTP timeout value. This field takes a positive integer and an optional - suffix indicating the unit of time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. -
- threshold - - yes - - An integer specifying how long to wait before backing off a failure. -
- backoff - - yes - - How long the system backs off before retrying. This field takes a positive - integer and an optional suffix indicating the unit of time. Possible units - are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. -
+| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `name` | yes | A human-readable name for the service. | +| `disabled` | no | If `true`, notifications are disabled for the service.| +| `url` | yes | The URL to which events should be published. | +| `headers` | yes | A list of static headers to add to each request. Each header's name is a key beneath `headers`, and each value is a list of payloads for that header name. Values must always be lists. | +| `timeout` | yes | A value for the HTTP timeout. A positive integer and an optional suffix indicating the unit of time, which may be `ns`, `us`, `ms`, `s`, `m`, or `h`. If you omit the unit of time, `ns` is used. | +| `threshold` | yes | An integer specifying how long to wait before backing off a failure. | +| `backoff` | yes | How long the system backs off before retrying after a failure. A positive integer and an optional suffix indicating the unit of time, which may be `ns`, `us`, `ms`, `s`, `m`, or `h`. If you omit the unit of time, `ns` is used. | + +## `redis` + +```none +redis: + addr: localhost:6379 + password: asecret + db: 0 + dialtimeout: 10ms + readtimeout: 10ms + writetimeout: 10ms + pool: + maxidle: 16 + maxactive: 64 + idletimeout: 300s +``` + +Declare parameters for constructing the `redis` connections. Registry instances +may use the Redis instance for several applications. Currently, it caches +information about immutable blobs. Most of the `redis` options control +how the registry connects to the `redis` instance. You can control the pool's +behavior with the [pool](#pool) subsection. + +You should configure Redis with the **allkeys-lru** eviction policy, because the +registry does not set an expiration value on keys. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `addr` | yes | The address (host and port) of the Redis instance. | +| `password`| no | A password used to authenticate to the Redis instance.| +| `db` | no | The name of the database to use for each connection. | +| `dialtimeout` | no | The timeout for connecting to the Redis instance. | +| `readtimeout` | no | The timeout for reading from the Redis instance. | +| `writetimeout` | no | The timeout for writing to the Redis instance. | + +### `pool` + +```none +pool: + maxidle: 16 + maxactive: 64 + idletimeout: 300s +``` + +Use these settings to configure the behavior of the Redis connection pool. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `maxidle` | no | The maximum number of idle connections in the pool. | +| `maxactive`| no | The maximum number of connections which can be open before blocking a connection request. | +| `idletimeout`| no | How long to wait before closing inactive connections. | + +## `health` + +```none +health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 + file: + - file: /path/to/checked/file + interval: 10s + http: + - uri: http://server.to.check/must/return/200 + headers: + Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] + statuscode: 200 + timeout: 3s + interval: 10s + threshold: 3 + tcp: + - addr: redis-server.domain.com:6379 + timeout: 3s + interval: 10s + threshold: 3 +``` + +The health option is **optional**, and contains preferences for a periodic +health check on the storage driver's backend storage, as well as optional +periodic checks on local files, HTTP URIs, and/or TCP servers. The results of +the health checks are available at the `/debug/health` endpoint on the debug +HTTP server if the debug HTTP server is enabled (see http section). + +### `storagedriver` + +The `storagedriver` structure contains options for a health check on the +configured storage driver's backend storage. The health check is only active +when `enabled` is set to `true`. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `enabled` | yes | Set to `true` to enable storage driver health checks or `false` to disable them. | +| `interval`| no | How long to wait between repetitions of the storage driver health check. A positive integer and an optional suffix indicating the unit of time. The suffix is one of `ns`, `us`, `ms`, `s`, `m`, or `h`. Defaults to `10s` if the value is omitted. If you specify a value but omit the suffix, the value is interpreted as a number of nanoseconds. | +| `threshold`| no | A positive integer which represents the number of times the check must fail before the state is marked as unhealthy. If not specified, a single failure marks the state as unhealthy. | + +### `file` + +The `file` structure includes a list of paths to be periodically checked for the\ +existence of a file. If a file exists at the given path, the health check will +fail. You can use this mechanism to bring a registry out of rotation by creating +a file. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `file` | yes | The path to check for existence of a file. | +| `interval`| no | How long to wait before repeating the check. A positive integer and an optional suffix indicating the unit of time. The suffix is one of `ns`, `us`, `ms`, `s`, `m`, or `h`. Defaults to `10s` if the value is omitted. | + +### `http` + +The `http` structure includes a list of HTTP URIs to periodically check with +`HEAD` requests. If a `HEAD` request does not complete or returns an unexpected +status code, the health check will fail. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `uri` | yes | The URI to check. | +| `headers` | no | Static headers to add to each request. Each header's name is a key beneath `headers`, and each value is a list of payloads for that header name. Values must always be lists. | +| `statuscode` | no | The expected status code from the HTTP URI. Defaults to `200`. | +| `timeout` | no | How long to wait before timing out the HTTP request. A positive integer and an optional suffix indicating the unit of time. The suffix is one of `ns`, `us`, `ms`, `s`, `m`, or `h`. If you specify a value but omit the suffix, the value is interpreted as a number of nanoseconds. | +| `interval`| no | How long to wait before repeating the check. A positive integer and an optional suffix indicating the unit of time. The suffix is one of `ns`, `us`, `ms`, `s`, `m`, or `h`. Defaults to `10s` if the value is omitted. If you specify a value but omit the suffix, the value is interpreted as a number of nanoseconds. | +| `threshold`| no | The number of times the check must fail before the state is marked as unhealthy. If this field is not specified, a single failure marks the state as unhealthy. | + +### `tcp` + +The `tcp` structure includes a list of TCP addresses to periodically check using +TCP connection attempts. Addresses must include port numbers. If a connection +attempt fails, the health check will fail. + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `addr` | yes | The TCP address and port to connect to. | +| `timeout` | no | How long to wait before timing out the TCP connection. A positive integer and an optional suffix indicating the unit of time. The suffix is one of `ns`, `us`, `ms`, `s`, `m`, or `h`. If you specify a value but omit the suffix, the value is interpreted as a number of nanoseconds. | +| `interval`| no | How long to wait between repetitions of the check. A positive integer and an optional suffix indicating the unit of time. The suffix is one of `ns`, `us`, `ms`, `s`, `m`, or `h`. Defaults to `10s` if the value is omitted. If you specify a value but omit the suffix, the value is interpreted as a number of nanoseconds. | +| `threshold`| no | The number of times the check must fail before the state is marked as unhealthy. If this field is not specified, a single failure marks the state as unhealthy. | -## redis +## `proxy` - redis: - addr: localhost:6379 - password: asecret - db: 0 - dialtimeout: 10ms - readtimeout: 10ms - writetimeout: 10ms - pool: - maxidle: 16 - maxactive: 64 - idletimeout: 300s +```none +proxy: + remoteurl: https://registry-1.docker.io + username: [username] + password: [password] +``` -Declare parameters for constructing the redis connections. Registry instances -may use the Redis instance for several applications. The current purpose is -caching information about immutable blobs. Most of the options below control -how the registry connects to redis. You can control the pool's behavior -with the [pool](#pool) subsection. +The `proxy` structure allows a registry to be configured as a pull-through cache +to Docker Hub. See +[mirror](https://github.com/docker/docker.github.io/tree/master/registry/recipes/mirror.md) +for more information. Pushing to a registry configured as a pull-through cache +is unsupported. -It's advisable to configure Redis itself with the **allkeys-lru** eviction policy -as the registry does not set an expire value on keys. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- addr - - yes - - Address (host and port) of redis instance. -
- password - - no - - A password used to authenticate to the redis instance. -
- db - - no - - Selects the db for each connection. -
- dialtimeout - - no - - Timeout for connecting to a redis instance. -
- readtimeout - - no - - Timeout for reading from redis connections. -
- writetimeout - - no - - Timeout for writing to redis connections. -
+| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `remoteurl`| yes | The URL for the repository on Docker Hub. | +| `username` | no | The username registered with Docker Hub which has access to the repository. | +| `password` | no | The password used to authenticate to Docker Hub using the username specified in `username`. | -### pool +To enable pulling private repositories (e.g. `batman/robin`) specify the +username (such as `batman`) and the password for that username. - pool: - maxidle: 16 - maxactive: 64 - idletimeout: 300s +> **Note**: These private repositories are stored in the proxy cache's storage. +> Take appropriate measures to protect access to the proxy cache. -Configure the behavior of the Redis connection pool. +## `compatibility` - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- maxidle - - no - - Sets the maximum number of idle connections. -
- maxactive - - no - - sets the maximum number of connections that should - be opened before blocking a connection request. -
- idletimeout - - no - - sets the amount time to wait before closing - inactive connections. -
+```none +compatibility: + schema1: + signingkeyfile: /etc/registry/key.json +``` -## health +Use the `compatibility` structure to configure handling of older and deprecated +features. Each subsection defines such a feature with configurable behavior. - health: - storagedriver: - enabled: true - interval: 10s - threshold: 3 - file: - - file: /path/to/checked/file - interval: 10s - http: - - uri: http://server.to.check/must/return/200 - headers: - Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] - statuscode: 200 - timeout: 3s - interval: 10s - threshold: 3 - tcp: - - addr: redis-server.domain.com:6379 - timeout: 3s - interval: 10s - threshold: 3 +### `schema1` -The health option is **optional**. It may contain preferences for a periodic -health check on the storage driver's backend storage, and optional periodic -checks on local files, HTTP URIs, and/or TCP servers. The results of the health -checks are available at /debug/health on the debug HTTP server if the debug -HTTP server is enabled (see http section). - -### storagedriver - -storagedriver contains options for a health check on the configured storage -driver's backend storage. enabled must be set to true for this health check to -be active. - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- enabled - - yes - -"true" to enable the storage driver health check or "false" to disable it. -
- interval - - no - - The length of time to wait between repetitions of the check. This field - takes a positive integer and an optional suffix indicating the unit of - time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. - The default value is 10 seconds if this field is omitted. -
- threshold - - no - - An integer specifying the number of times the check must fail before the - check triggers an unhealthy state. If this filed is not specified, a - single failure will trigger an unhealthy state. -
- -### file - -file is a list of paths to be periodically checked for the existence of a file. -If a file exists at the given path, the health check will fail. This can be -used as a way of bringing a registry out of rotation by creating a file. - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- file - - yes - -The path to check for the existence of a file. -
- interval - - no - - The length of time to wait between repetitions of the check. This field - takes a positive integer and an optional suffix indicating the unit of - time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. - The default value is 10 seconds if this field is omitted. -
- -### http - -http is a list of HTTP URIs to be periodically checked with HEAD requests. If -a HEAD request doesn't complete or returns an unexpected status code, the -health check will fail. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- uri - - yes - -The URI to check. -
- headers - - no - - Static headers to add to each request. Each header's name should be a key - underneath headers, and each value is a list of payloads for that - header name. Note that values must always be lists. -
- statuscode - - no - -Expected status code from the HTTP URI. Defaults to 200. -
- timeout - - no - - The length of time to wait before timing out the HTTP request. This field - takes a positive integer and an optional suffix indicating the unit of - time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. -
- interval - - no - - The length of time to wait between repetitions of the check. This field - takes a positive integer and an optional suffix indicating the unit of - time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. - The default value is 10 seconds if this field is omitted. -
- threshold - - no - - An integer specifying the number of times the check must fail before the - check triggers an unhealthy state. If this filed is not specified, a - single failure will trigger an unhealthy state. -
- -### tcp - -tcp is a list of TCP addresses to be periodically checked with connection -attempts. The addresses must include port numbers. If a connection attempt -fails, the health check will fail. - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- addr - - yes - -The TCP address to connect to, including a port number. -
- timeout - - no - - The length of time to wait before timing out the TCP connection. This - field takes a positive integer and an optional suffix indicating the unit - of time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. -
- interval - - no - - The length of time to wait between repetitions of the check. This field - takes a positive integer and an optional suffix indicating the unit of - time. Possible units are: -
    -
  • ns (nanoseconds)
  • -
  • us (microseconds)
  • -
  • ms (milliseconds)
  • -
  • s (seconds)
  • -
  • m (minutes)
  • -
  • h (hours)
  • -
- If you omit the suffix, the system interprets the value as nanoseconds. - The default value is 10 seconds if this field is omitted. -
- threshold - - no - - An integer specifying the number of times the check must fail before the - check triggers an unhealthy state. If this filed is not specified, a - single failure will trigger an unhealthy state. -
- -## Proxy - - proxy: - remoteurl: https://registry-1.docker.io - username: [username] - password: [password] - -Proxy enables a registry to be configured as a pull through cache to the official Docker Hub. See [mirror](recipes/mirror.md) for more information. Pushing to a registry configured as a pull through cache is currently unsupported. - - - - - - - - - - - - - - - - - - - - - - -
ParameterRequiredDescription
- remoteurl - - yes - - The URL of the official Docker Hub -
- username - - no - - The username of the Docker Hub account -
- password - - no - - The password for the official Docker Hub account -
- -To enable pulling private repositories (e.g. `batman/robin`) a username and password for user `batman` must be specified. Note: These private repositories will be stored in the proxy cache's storage and relevant measures should be taken to protect access to this. - -## Compatibility - - compatibility: - schema1: - signingkeyfile: /etc/registry/key.json - -Configure handling of older and deprecated features. Each subsection -defines a such a feature with configurable behavior. - -### Schema1 - - - - - - - - - - - - -
ParameterRequiredDescription
- signingkeyfile - - no - - The signing private key used for adding signatures to schema1 manifests. - If no signing key is provided, a new ECDSA key will be generated on - startup. -
+| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------| +| `signingkeyfile` | no | The signing private key used to add signatures to `schema1` manifests. If no signing key is provided, a new ECDSA key is generated when the registry starts. | ## Example: Development configuration -The following is a simple example you can use for local development: +You can use this simple example for local development: - version: 0.1 - log: - level: debug - storage: - filesystem: - rootdirectory: /var/lib/registry - http: - addr: localhost:5000 - secret: asecretforlocaldevelopment - debug: - addr: localhost:5001 +```none +version: 0.1 +log: + level: debug +storage: + filesystem: + rootdirectory: /var/lib/registry +http: + addr: localhost:5000 + secret: asecretforlocaldevelopment + debug: + addr: localhost:5001 +``` -The above configures the registry instance to run on port `5000`, binding to -`localhost`, with the `debug` server enabled. Registry data storage is in the -`/var/lib/registry` directory. Logging is in `debug` mode, which is the most +This example configures the registry instance to run on port `5000`, binding to +`localhost`, with the `debug` server enabled. Registry data is stored in the +`/var/lib/registry` directory. Logging is set to `debug` mode, which is the most verbose. -A similar simple configuration is available at -[config-example.yml](https://github.com/docker/distribution/blob/master/cmd/registry/config-example.yml). -Both are generally useful for local development. +See +[config-example.yml](https://github.com/docker/distribution/blob/master/cmd/registry/config-example.yml) +for another simple configuration. Both examples are generally useful for local +development. ## Example: Middleware configuration -This example illustrates how to configure storage middleware in a registry. -Middleware allows the registry to serve layers via a content delivery network -(CDN). This is useful for reducing requests to the storage layer. +This example configures [Amazon Cloudfront](http://aws.amazon.com/cloudfront/) +as the storage middleware in a registry. Middleware allows the registry to serve +layers via a content delivery network (CDN). This reduces requests to the +storage layer. -The registry supports [Amazon -Cloudfront](http://aws.amazon.com/cloudfront/). You can only use Cloudfront in -conjunction with the S3 storage driver. +Cloudfront requires the S3 storage driver. - - - - - - - - - - - - - - - - - -
ParameterDescription
nameThe storage middleware name. Currently cloudfront is an accepted value.
disabledSet to false to easily disable the middleware.
options: - A set of key/value options to configure the middleware. -
    -
  • baseurl: The Cloudfront base URL.
  • -
  • privatekey: The location of your AWS private key on the filesystem.
  • -
  • keypairid: The ID of your Cloudfront keypair.
  • -
  • duration: The duration in minutes for which the URL is valid. Default is 20.
  • -
-
+This is the configuration expressed in YAML: -The following example illustrates these values: +```none +middleware: + storage: + - name: cloudfront + disabled: false + options: + baseurl: http://d111111abcdef8.cloudfront.net + privatekey: /path/to/asecret.pem + keypairid: asecret + duration: 60 +``` - middleware: - storage: - - name: cloudfront - disabled: false - options: - baseurl: http://d111111abcdef8.cloudfront.net - privatekey: /path/to/asecret.pem - keypairid: asecret - duration: 60 +See the configuration reference for [Cloudfront](#cloudfront) for more +information about configuration options. - ->**Note**: Cloudfront keys exist separately to other AWS keys. See ->[the documentation on AWS credentials](http://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html) ->for more information. +> **Note**: Cloudfront keys exist separately from other AWS keys. See +> [the documentation on AWS credentials](http://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html) +> for more information. From 58d239d723efbc2b2935ddc8816b51d355525989 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 6 Jul 2017 16:01:26 -0700 Subject: [PATCH 18/19] registry/{storage,handlers}: limit content sizes Under certain circumstances, the use of `StorageDriver.GetContent` can result in unbounded memory allocations. In particualr, this happens when accessing a layer through the manifests endpoint. This problem is mitigated by setting a 4MB limit when using to access content that may have been accepted from a user. In practice, this means setting the limit with the use of `BlobProvider.Get` by wrapping `StorageDriver.GetContent` in a helper that uses `StorageDriver.Reader` with a `limitReader` that returns an error. When mitigating this security issue, we also noticed that the size of manifests uploaded to the registry is also unlimited. We apply similar logic to the request body of payloads that are full buffered. Signed-off-by: Stephen J Day (cherry picked from commit 55ea4404280f1ca15a982a9421b9713fdc145be8) Signed-off-by: Stephen J Day --- registry/handlers/blobupload.go | 8 ++-- registry/handlers/helpers.go | 13 ++++-- registry/handlers/images.go | 8 ++-- registry/storage/blobstore.go | 4 +- registry/storage/io.go | 71 +++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 registry/storage/io.go diff --git a/registry/handlers/blobupload.go b/registry/handlers/blobupload.go index e4133ce8..f63c1cb7 100644 --- a/registry/handlers/blobupload.go +++ b/registry/handlers/blobupload.go @@ -179,8 +179,8 @@ func (buh *blobUploadHandler) PatchBlobData(w http.ResponseWriter, r *http.Reque // TODO(dmcgowan): support Content-Range header to seek and write range - if err := copyFullPayload(w, r, buh.Upload, buh, "blob PATCH", &buh.Errors); err != nil { - // copyFullPayload reports the error if necessary + if err := copyFullPayload(w, r, buh.Upload, -1, buh, "blob PATCH"); err != nil { + buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err.Error())) return } @@ -218,8 +218,8 @@ func (buh *blobUploadHandler) PutBlobUploadComplete(w http.ResponseWriter, r *ht return } - if err := copyFullPayload(w, r, buh.Upload, buh, "blob PUT", &buh.Errors); err != nil { - // copyFullPayload reports the error if necessary + if err := copyFullPayload(w, r, buh.Upload, -1, buh, "blob PUT"); err != nil { + buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err.Error())) return } diff --git a/registry/handlers/helpers.go b/registry/handlers/helpers.go index dac4f7a8..dc3091a9 100644 --- a/registry/handlers/helpers.go +++ b/registry/handlers/helpers.go @@ -6,7 +6,6 @@ import ( "net/http" ctxu "github.com/docker/distribution/context" - "github.com/docker/distribution/registry/api/errcode" ) // closeResources closes all the provided resources after running the target @@ -23,7 +22,9 @@ func closeResources(handler http.Handler, closers ...io.Closer) http.Handler { // copyFullPayload copies the payload of an HTTP request to destWriter. If it // receives less content than expected, and the client disconnected during the // upload, it avoids sending a 400 error to keep the logs cleaner. -func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWriter io.Writer, context ctxu.Context, action string, errSlice *errcode.Errors) error { +// +// The copy will be limited to `limit` bytes, if limit is greater than zero. +func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWriter io.Writer, limit int64, context ctxu.Context, action string) error { // Get a channel that tells us if the client disconnects var clientClosed <-chan bool if notifier, ok := responseWriter.(http.CloseNotifier); ok { @@ -32,8 +33,13 @@ func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWr ctxu.GetLogger(context).Warnf("the ResponseWriter does not implement CloseNotifier (type: %T)", responseWriter) } + var body = r.Body + if limit > 0 { + body = http.MaxBytesReader(responseWriter, body, limit) + } + // Read in the data, if any. - copied, err := io.Copy(destWriter, r.Body) + copied, err := io.Copy(destWriter, body) if clientClosed != nil && (err != nil || (r.ContentLength > 0 && copied < r.ContentLength)) { // Didn't receive as much content as expected. Did the client // disconnect during the request? If so, avoid returning a 400 @@ -58,7 +64,6 @@ func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWr if err != nil { ctxu.GetLogger(context).Errorf("unknown error reading request payload: %v", err) - *errSlice = append(*errSlice, errcode.ErrorCodeUnknown.WithDetail(err)) return err } diff --git a/registry/handlers/images.go b/registry/handlers/images.go index df7f869b..27132e69 100644 --- a/registry/handlers/images.go +++ b/registry/handlers/images.go @@ -21,8 +21,9 @@ import ( // These constants determine which architecture and OS to choose from a // manifest list when downconverting it to a schema1 manifest. const ( - defaultArch = "amd64" - defaultOS = "linux" + defaultArch = "amd64" + defaultOS = "linux" + maxManifestBodySize = 4 << 20 ) // imageManifestDispatcher takes the request context and builds the @@ -240,8 +241,9 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http } var jsonBuf bytes.Buffer - if err := copyFullPayload(w, r, &jsonBuf, imh, "image manifest PUT", &imh.Errors); err != nil { + if err := copyFullPayload(w, r, &jsonBuf, maxManifestBodySize, imh, "image manifest PUT"); err != nil { // copyFullPayload reports the error if necessary + imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err.Error())) return } diff --git a/registry/storage/blobstore.go b/registry/storage/blobstore.go index 84f6660f..57e05fd8 100644 --- a/registry/storage/blobstore.go +++ b/registry/storage/blobstore.go @@ -27,7 +27,7 @@ func (bs *blobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error return nil, err } - p, err := bs.driver.GetContent(ctx, bp) + p, err := getContent(ctx, bs.driver, bp) if err != nil { switch err.(type) { case driver.PathNotFoundError: @@ -37,7 +37,7 @@ func (bs *blobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error return nil, err } - return p, err + return p, nil } func (bs *blobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { diff --git a/registry/storage/io.go b/registry/storage/io.go new file mode 100644 index 00000000..c1be3b77 --- /dev/null +++ b/registry/storage/io.go @@ -0,0 +1,71 @@ +package storage + +import ( + "errors" + "io" + "io/ioutil" + + "github.com/docker/distribution/context" + "github.com/docker/distribution/registry/storage/driver" +) + +const ( + maxBlobGetSize = 4 << 20 +) + +func getContent(ctx context.Context, driver driver.StorageDriver, p string) ([]byte, error) { + r, err := driver.Reader(ctx, p, 0) + if err != nil { + return nil, err + } + + return readAllLimited(r, maxBlobGetSize) +} + +func readAllLimited(r io.Reader, limit int64) ([]byte, error) { + r = limitReader(r, limit) + return ioutil.ReadAll(r) +} + +// limitReader returns a new reader limited to n bytes. Unlike io.LimitReader, +// this returns an error when the limit reached. +func limitReader(r io.Reader, n int64) io.Reader { + return &limitedReader{r: r, n: n} +} + +// limitedReader implements a reader that errors when the limit is reached. +// +// Partially cribbed from net/http.MaxBytesReader. +type limitedReader struct { + r io.Reader // underlying reader + n int64 // max bytes remaining + err error // sticky error +} + +func (l *limitedReader) Read(p []byte) (n int, err error) { + if l.err != nil { + return 0, l.err + } + if len(p) == 0 { + return 0, nil + } + // If they asked for a 32KB byte read but only 5 bytes are + // remaining, no need to read 32KB. 6 bytes will answer the + // question of the whether we hit the limit or go past it. + if int64(len(p)) > l.n+1 { + p = p[:l.n+1] + } + n, err = l.r.Read(p) + + if int64(n) <= l.n { + l.n -= int64(n) + l.err = err + return n, err + } + + n = int(l.n) + l.n = 0 + + l.err = errors.New("storage: read exceeds limit") + return n, l.err +} From 48cb60af7c1d73451e85f87483109aee02f03b35 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 20 Jul 2017 13:53:11 -0700 Subject: [PATCH 19/19] release: prepare for 2.5.2 release Signed-off-by: Stephen J Day --- .mailmap | 1 + AUTHORS | 22 ++++++++++++++-------- version/version.go | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.mailmap b/.mailmap index d9910601..a6fa4c8e 100644 --- a/.mailmap +++ b/.mailmap @@ -16,3 +16,4 @@ davidli davidli Omer Cohen Omer Cohen Eric Yang Eric Yang Nikita Tarasov Nikita +Misty Stanley-Jones Misty Stanley-Jones diff --git a/AUTHORS b/AUTHORS index 9e80e062..1e349a7e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,9 +16,9 @@ Andrew T Nguyen Andrey Kostov Andy Goldstein Anis Elleuch -Anton Tiurin Antonio Mercado Antonio Murdaca +Anton Tiurin Arien Holthuizen Arnaud Porterie Arthur Baars @@ -31,6 +31,8 @@ bin liu Brian Bland burnettk Carson A +Cezar Sa Espinola +Charles Smith Chris Dillon cyli Daisuke Fujita @@ -39,15 +41,16 @@ Darren Shepherd Dave Trombley Dave Tucker David Lawrence +davidli David Verhasselt David Xia -davidli Dejan Golja Derek McGowan Diogo Mónica DJ Enriquez Donald Huang Doug Davis +Edgar Lee Eric Yang Fabio Huser farmerworking @@ -58,8 +61,8 @@ gabriell nascimento Gleb Schukin harche Henri Gomez -Hu Keping Hua Wang +Hu Keping HuKeping Ian Babrou igayoso @@ -70,20 +73,20 @@ Jessie Frazelle jhaohai Jianqing Wang John Starks +Jonathan Boulle Jon Johnson Jon Poler -Jonathan Boulle Jordan Liggitt Josh Hawn Julien Fernandez -Ke Xu Keerthan Mala Kelsey Hightower Kenneth Lim Kenny Leung -Li Yi -Liu Hua +Ke Xu liuchang0812 +Liu Hua +Li Yi Louis Kottmann Luke Carpenter Mary Anthony @@ -94,6 +97,7 @@ Matt Robenolt Michael Prokop Michal Minar Miquel Sabaté +Misty Stanley-Jones Morgan Bauer moxiegirl Nathan Sullivan @@ -113,6 +117,7 @@ Rodolfo Carvalho Rusty Conover Sean Boran Sebastiaan van Stijn +Sebastien Coavoux Serge Dubrouski Sharif Nassar Shawn Falkner-Horine @@ -134,11 +139,12 @@ Tonis Tiigi Tony Holdstock-Brown Trevor Pounds Troels Thomsen +Victoria Bialas Vincent Batts Vincent Demeester Vincent Giersch -W. Trevor King weiyuan.yl +W. Trevor King xg.song xiekeyang Yann ROBERT diff --git a/version/version.go b/version/version.go index cafe2336..4ce4dc75 100644 --- a/version/version.go +++ b/version/version.go @@ -8,4 +8,4 @@ var Package = "github.com/docker/distribution" // the latest release tag by hand, always suffixed by "+unknown". During // build, it will be replaced by the actual version. The value here will be // used if the registry is run after a go get based install. -var Version = "v2.4.1+unknown" +var Version = "v2.5.2+unknown"