By Paul Jolly, Go contributor, and co-creator of play-with-go.dev
.
The go
command defaults to downloading modules from the public Go module mirror at
proxy.golang.org. It also defaults to validating downloaded modules, regardless of source,
against the public Go checksum database at sum.golang.org. These defaults work well for
publicly available source code.
But what happens if you and your fellow developers need to work with private modules?
This guide explains how to work with private modules. In the guide you will create three modules:
{{{.PUBLIC}}}
, a publicly accessible module that provides aMessage()
function{{{.PRIVATE}}}
, a private module that provides aSecret()
functiongopher
, a local-only module that usespublic
andprivate
modules
Prerequisites
You should already have completed:
This guide is running using:
$ go version
go version go1.19.1 linux/amd64
The public
and private
modules
Start by initialising your public
module:
$ mkdir /home/gopher/public
$ cd /home/gopher/public
$ go mod init {{{.PUBLIC}}}
go: creating new go.mod: module {{{.PUBLIC}}}
$ git init -q
$ git remote add origin https://{{{.PUBLIC}}}.git
Create an initial version of the Message()
in public.go
:
package public
func Message() string {
return "This is a public safety announcement!"
}
Commit and push this initial version:
$ git add public.go go.mod
$ git commit -q -m 'Initial commit of public module'
$ git push -q origin main
remote: . Processing 1 references
remote: Processed 1 references in total
Now do the same for the private
module:
$ mkdir /home/gopher/private
$ cd /home/gopher/private
$ go mod init {{{.PRIVATE}}}
go: creating new go.mod: module {{{.PRIVATE}}}
$ git init -q
$ git remote add origin https://{{{.PRIVATE}}}.git
Note: the private
source code repository at https://{{{.PRIVATE}}}.git was automatically created for you when this
guide loaded, much like https://{{{.PUBLIC}}}.git was created for the public
module. However, the
private
module repository was marked as Private: true
, hence authenticated access is required to access
https://{{{.PRIVATE}}}.git.
Create an initial version of the Secret()
in private.go
:
package private
func Secret() string {
return "This is a top secret message... for your eyes only"
}
Commit and push this initial version:
$ git add private.go go.mod
$ git commit -q -m 'Initial commit of private module'
$ git push -q origin main
remote: . Processing 1 references
remote: Processed 1 references in total
The gopher
module
Now create a gopher
module to try out the public
and private
modules. Unlike
the public
and private
modules, you will not publish the gopher
module; it
will be local only:
$ mkdir /home/gopher/gopher
$ cd /home/gopher/gopher
$ go mod init gopher
go: creating new go.mod: module gopher
Create an initial version of a main
package that uses the two modules, in gopher.go
:
package main
import (
"fmt"
"{{{.PUBLIC}}}"
"{{{.PRIVATE}}}"
)
func main() {
fmt.Printf("public.Message(): %v\n", public.Message())
fmt.Printf("private.Secret(): %v\n", private.Secret())
}
At this point, let’s take a small diversion to talk about proxies.
Module proxies
The go
command can fetch modules from a proxy or connect to source control
servers directly, according to the setting of the GOPROXY
environment
variable (see go help env
). You can see the default setting for GOPROXY
by inspecting
the output of go env
:
$ go env GOPROXY
https://proxy.golang.org,direct
This means it will try the Go module mirror run by Google and fall back to a direct connection if the proxy reports that it does not have the module (HTTP error 404 or 410).
The go
command also defaults to validating downloaded modules, regardless of source,
against the public Go checksum database at sum.golang.org, something that is controlled by the
GOSUMDB
environment variable. You can see the default for GOSUMDB
by checking the
output of go env
:
$ go env GOSUMDB
sum.golang.org
Because your session is already configured with authentication credentials for the source control system that hosts
{{{.PRIVATE}}}
, attempting to go get
that module will succeed because the go
command will
fall back to the direct
mode.
Let’s simulate getting our module dependencies with no credentials by setting GOPROXY
to only use the
public proxy, using the go env
command:
$ go env -w GOPROXY=https://proxy.golang.org
Add a dependency on the public
module:
$ go get {{{.PUBLIC}}}
go: downloading {{{.PUBLIC}}} v0.0.0-20060102150405-abcedf12345
go: added {{{.PUBLIC}}} v0.0.0-20060102150405-abcedf12345
As expected, that succeeded.
Try to add a dependency on the private
module:
$ go get {{{.PRIVATE}}}
go: module {{{.PRIVATE}}}: reading https://proxy.golang.org/{{{.PRIVATE}}}/@v/list: 404 Not Found
server response:
not found: module {{{.PRIVATE}}}: git ls-remote -q origin in /tmp/gopath/pkg/mod/cache/vcs/6f24e6c3f0f418f23971c3f3f96e9d70729d1ef5d14781310442dcb8d214d2f6: exit status 128:
fatal: could not read Username for 'https://gopher.live': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
Thankfully, this failed.
Let’s return GOPROXY
to its default value:
$ go env -w GOPROXY=
And try once again to add a dependency on the private
module:
$ go get {{{.PRIVATE}}}
go: downloading {{{.PRIVATE}}} v0.0.0-20060102150405-abcedf12345
go: {{{.PRIVATE}}}@v0.0.0-20060102150405-abcedf12345: verifying module: {{{.PRIVATE}}}@v0.0.0-20060102150405-abcedf12345: reading https://sum.golang.org/lookup/{{{.PRIVATE}}}@v0.0.0-20060102150405-abcedf12345: 404 Not Found
server response:
not found: {{{.PRIVATE}}}@v0.0.0-20060102150405-abcedf12345: invalid version: git ls-remote -q origin in /tmp/gopath/pkg/mod/cache/vcs/6f24e6c3f0f418f23971c3f3f96e9d70729d1ef5d14781310442dcb8d214d2f6: exit status 128:
fatal: could not read Username for 'https://gopher.live': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
This fails because the checksum database is not able to access your private
module. But it’s worse than
that, because the go
command “leaked” a request for {{{.PRIVATE}}}
to the public proxy. This might well be
fine for a trusted proxy like the Google proxy, but it isn’t always the case.
The GOPRIVATE
environment variable
The GOPRIVATE
environment variable controls which modules the go
command
considers to be private (not available publicly) and should therefore not use the
proxy or checksum database. The variable is a comma-separated list of
glob patterns (in the syntax of Go’s path.Match
) of module path prefixes.
Let’s tell the go
command that {{{.PRIVATE}}}
by setting the GOPRIVATE
environment
variable:
$ go env -w GOPRIVATE={{{.PRIVATE}}}
Try to get the latest version of the private
module again (remember, the public
module
succeeded):
$ go get {{{.PRIVATE}}}
go: downloading {{{.PRIVATE}}} v0.0.0-20060102150405-abcedf12345
go: added {{{.PRIVATE}}} v0.0.0-20060102150405-abcedf12345
Success! As a final check, run the gopher
module main
package:
$ go run .
public.Message(): This is a public safety announcement!
private.Secret(): This is a top secret message... for your eyes only
For more details on the GOPRIVATE
environment variable and the values it can take, see
go help module-auth
, which also includes examples of how to use the *
glob to match multiple sub domains
or modules.
The GONOPROXY
and GONOSUMDB
environment variables can be used for more fine
grained control. Again, see go help module-auth
for more information.
Conclusion
This guide has provided you with a brief introduction to handling private modules.
As a next step you might like to consider: