DEV Community


GCP Cloud Run: containers without Dockerfile

alvardev profile image Alvaro David Updated on ・4 min read

Containers allow us to run our code wherever we want, Docker is a popular option to build our containers. However, sometimes you love Dockerfile and sometimes (or more) you hate Dockerfile.

"44% of the containers in the wild had know vulnerabilities for which patches existed. This just shows you that writing your own Dockerfile can actually be quite fraught with errors unless you're an expert or maybe you have a platform team that provides Dockerfiles for you." Src: Buildpacks in Google Cloud

Writing a Dockerfile could be complicated, considering image size, security, multi-stage, versions and so on, there are too many ways to do that!

Sure, we can handle all that things to get the perfect image but if you are using containers, sooner or later you will have to deploy your container in a Kubernetes cluster (like Cloud Run or GKE on GCP) and Kubernetes has deprecated Docker.

We are Devs, we should focus on the code...

So... Here comes Buildpacks to save the day!

Our main objective for today is to deploy a simple API on Cloud Run without a Dockerfile using Buildpacks.


The Code

We're going to deploy a simple API using Go

package main

import (

// Response definition for response API
type Response struct {
    Message string `json:"message"`

func handler(w http.ResponseWriter, r *http.Request) {

    response := Response{}
    response.Message = "Hello world!"
    responseJSON, err := json.Marshal(response)

    if err != nil {
        log.Fatalf("Failed to parse json: %v", err)

    w.Header().Set("Content-Type", "application/json")

func main() {
    log.Print("starting server...")
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
Enter fullscreen mode Exit fullscreen mode

Using Dockerfile

Let's begin with the 'popular' way, I mean using a Dockerfile

# Source:

FROM golang:1.16-buster as builder

# Create and change to the app directory.

# Retrieve application dependencies.
# This allows the container build to reuse cached dependencies.
# Expecting to copy go.mod and if present go.sum.
COPY go.* ./
RUN go mod download

# Copy local code to the container image.
COPY . ./

# Build the binary.
RUN go build -mod=readonly -v -o server

# Use the official Debian slim image for a lean production container.
FROM debian:buster-slim
RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/server /app/server

# Run the web service on container startup.
CMD ["/app/server"]
Enter fullscreen mode Exit fullscreen mode

Don't forget the .dockerignore!

# The .dockerignore file excludes files from the container build process.

# Exclude locally vendored dependencies.

# Exclude "build-time" ignore files.

# Exclude git history and configuration.
Enter fullscreen mode Exit fullscreen mode

With no doubt somebody will say:

-"Hey! your Dockerfile is incomplete! with multi-stage your image will be smaller"

And... yes, you're right! with this Dockerfile the image is 82.6 mb, I found somewhere a long time ago a Dockerfile with multi-stage for Go, it's here just for the record, and the image size was reduced to 13.7 mb.

Great right? Not at all, First I don't remember where I found the multi-stage Dockerfile or if the post even exists today, second for a maintainer it could be difficult to understand the process if an update (security patch) is required.

Using Buildpacks

Buildpacks is an open-source technology that makes it fast and easy for you to create secure, production-ready container images from source code and without a Dockerfile, following the best practices. Src: Announcing Google Cloud buildpacks—container images made easy

The first thing you should do for using Buildpacks is to remove the Dockerfile and the .dockerignore.

That's it! you keep what it matters: your code.

To install buildpacks go to:

To build the image use this:

pack build my-go-api:v0.1 --builder
Enter fullscreen mode Exit fullscreen mode

To test the image use this:

docker run --rm -p 8080:8080 my-go-api:v0.1

# In another terminal
curl --request GET --url http://localhost:8080/

# Response expected
# {"message": "Hello world!"}
Enter fullscreen mode Exit fullscreen mode

Awesome! we have our image running locally, now let's deploy to Cloud Run

# You can push the image that you built
# but I want to show you that buildpacks can be
# used with gcloud too.

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')

gcloud alpha builds submit --pack$PROJECT_ID/my-go-api:v0.1

# Deploy to Cloud Run
gcloud run deploy my-go-api-service \ 
  --image$PROJECT_ID/my-go-api:v0.1 \
  --region southamerica-east1 \
  --platform managed 

curl https://my-go-api-service-[your-hash]
# {"message": "Hello world!"}
Enter fullscreen mode Exit fullscreen mode

Here is the repo with all the code in this post, check the branchs.

Hope this post helps you!

Discussion (2)

Editor guide
patarapolw profile image
Pacharapol Withayasakpunt

If you use just buildpacks, why not Google App Engine?

alvardev profile image
Alvaro David Author

Hi Pacharapol, excellent question!

For this example App Engine and even Cloud Functions are perfect serverless resources to deploy the API too.

Actually, what I want to show in this post are two things:

1.- Buildpacks are relative new on GCP and I know a lot of devs that are lock in on Docker, so I wanted to show them that there are other options to build container images.

2.- Kubernetes has deprecated Docker so the next question was "What are we going to do with our containers in Cloud Run (Knative)?" .

Cloud Run is the cheapest way to run a container on GCP and migrate to GKE or Anthos is easier. Maybe I simplified too much the code lol.

By the way, for those who are new to GCP, you can check the docs for more information and use cases of serverless on GCP