Obligatory first post of 2025…yay!
For the longest time I’ve been lazy with my docker builds happy to have a single stage. This has worked for me because a) I don’t use docker outside of my laptop much and b) I don’t have to worry too much about hard drive space. But then I decided to start using the Google Cloud Platform a bit more….
The Google Cloud Platform, like many online hosting providers, offers certain things for free provided you stay under set boundaries. One of these things is their artifact repository, which you will no doubt run into if you want to build and run something via Cloud Run. I have such an application and in my mind I thought everything I was doing would be free. But it wasn’t. And now, like a lot of people who happily throw things at cloud platforms without due care and attention I’m now seriously out of pocket. I could contact Google but I’ve resigned myself to that fact I’m just never going to get my £0.02p back. At least I’m super glad that I set up budget alerts!
My problem here is that something was going over the free quota. I’ve not build or deployed much so far but a quick budget breakdown showed that the whopping costs were coming from my Artifact Repository. I’ve not delved much there so far, thanks to Google doing a lot of heavy lifting when it comes to building things, but I soon learned I was creating Docker images roughly in the region of 700MB per image, and the free tier for Artifact storage stops at 0.5GB. Whoops.
This leads me here, my public reminder to use multi-stage Docker builds. I’m creating a Go application and running up.
For reference, here’s the Dockerfile for the 700MB version:
FROM golang:1.23-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o cargo .
EXPOSE 8080
CMD ["./cargo"]
and here’s the Dockerfile for the same application, but results in an 11MB build:
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o cargo .
FROM alpine:latest
COPY --from=builder /app/cargo /app/cargo
EXPOSE 8080
CMD ["/app/cargo"]
It boils down to two extra lines. A new FROM
and a COPY
. I won’t go into details on what these do, that’s why Docker has their own documentation. Instead I’ll just be using this as at least one lesson why multi-stage builds are good.
Now, time to go see if there’s 2p down the back of the sofa.