Go’s pgx package goes above being a postgres driver and actually implements its own interface that boasts better performance than the standard database/sql
package. That, along with it being a well-maintained and actively developed project, make it an attractive option for using postgres in your next Go project. This guide will hopefully help you get up and running with pgx
and docker-compose.
1. Go Project
Create a new Go project according to pgx
’s Getting Started guide . Once you have a working Go program with a local install of postgres, make sure to stop your local postgres instance, to avoid any possible conflicts later. With postgres installed via brew on a mac, I ran
brew services stop postgresql
1a. Create Postgres volume
In your terminal, create a directory that the docker postgres service will use as a data volume.
mkdir -p /path/to/postgres/database-data
2. Go Dockerfile
At the root of your new Go project, Add a Dockerfile
file with the below contents:
FROM golang:1.14-alpine AS build
WORKDIR /src/
COPY main.go go.* /src/
RUN CGO_ENABLED=0 go build -o /bin/hello-world
FROM scratch
COPY --from=build /bin/hello-world /bin/hello-world
ENTRYPOINT ["/bin/hello-world"]
This is a pretty generic dockerfile that you might see variants of in other Go projects that use Docker. It pulls the golang docker image, version 1.14-alpine
, builds your app, and moves the built binary into a bin/
directory, readying it for us to run.
2a. Verify Dockerfile
With your dockerfile in place, verify it works by building your app with:
docker build
in your terminal.
3. docker-compose
At the root of your project, create a file called docker-compose.yml
and paste in the following:
version: '3'
services:
app:
build: .
depends_on:
- db
environment:
DATABASE_URL: "postgres://postgres:postgres@db:5432/postgres"
db:
image: "postgres"
volumes:
- /path/to/postgres/database-data:/var/lib/postgresql/data/
volumes:
database-data:
We are declaring that we have two services: app
and db
.
db
service
db:
image: "postgres"
volumes:
- ~/Documents/database-data:/var/lib/postgresql/data/
The db
service pulls the latest postgres docker image, and declares a single volume to store data in.
app
service
app:
build: .
depends_on:
- db
environment:
DATABASE_URL: "postgres://postgres:postgres@db:5432/postgres"
The app
service defined uses our dockerfile that we added in step 2 (build .
defines where docker-compose should look for a dockerfile). We are also saying that this service dependes_on
the db
service.
4. First run
Run:
docker-compose build
in your terminal and watch docker-compose pull images and build your services.
With your services built, run:
docker-compose up
You should hopefully see logs being outputted in your terminal, with different prefixes for app
and db
, depending on which service the log is from. Unfortunately, you will probably see a database connection error coming from app
. One caveat of docker-compose is that while the app
service may depend on the db
service, this dependency is at a container level. So there is a good chance that postgres may not be running when your go program tries connecting to it, since the idea of postgres being ‘ready’ at an application level isn’t clear. Read on to see how to fix this.
5. Waiting for Postgres
Docker compose’s own documentation encourages developers to add logic for waiting for services at the application level, instead of relying on scripts like wait-for-it.sh
(which is great btw). Below, I’ve adapted a wait-for-it.sh
port for go to wait for postgres to be running and accepting connections on the default port of 5432.
func WaitForPostgres(service string, timeOut time.Duration) error {
var pgChan = make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
go func(s string) {
defer wg.Done()
for {
_, err := net.Dial("tcp", service)
if err == nil {
return
}
time.Sleep(1 * time.Second)
}
}(service)
wg.Wait()
close(pgChan)
}()
select {
case <-pgChan:
return nil
case <-time.After(timeOut):
return fmt.Errorf("postgres isn't ready in %s", timeOut)
}
}
Using this function before connecting to postgres with pgx
makes our go program wait for postgres. Try running
docker-compose up
again and watch your app
service log “Hello world”!
Thanks for reading.