Deploy an app in a container image to a GKE cluster


This page shows you how to do the following:

  1. Create a Hello World app.
  2. Package the app into a container image using Cloud Build.
  3. Create a cluster in Google Kubernetes Engine (GKE).
  4. Deploy the container image to your cluster.

The sample is shown in several languages, but you can use other languages in addition to the ones shown.


To follow step-by-step guidance for this task directly in the Cloud Shell Editor, click Guide me:

Guide me


Before you begin

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Artifact Registry, Cloud Build, and Google Kubernetes Engine APIs.

    Enable the APIs

  5. Install the Google Cloud CLI.
  6. To initialize the gcloud CLI, run the following command:

    gcloud init
  7. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  8. Make sure that billing is enabled for your Google Cloud project.

  9. Enable the Artifact Registry, Cloud Build, and Google Kubernetes Engine APIs.

    Enable the APIs

  10. Install the Google Cloud CLI.
  11. To initialize the gcloud CLI, run the following command:

    gcloud init
  12. kubectl is used to manage Kubernetes, the cluster orchestration system used by GKE. You can install kubectl by using gcloud:
    gcloud components install kubectl

Writing the sample app

For instructions on creating a Hello World app that runs on GKE, click your language:

Go

  1. Create a new directory named helloworld-gke and change directory into it:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a new module named example.com/helloworld:

    go mod init example.com/helloworld
    
  3. Create a new file named helloworld.go and paste the following code into it:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	http.HandleFunc("/", handler)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    
    	log.Printf("Listening on localhost:%s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	log.Print("Hello world received a request.")
    	target := os.Getenv("TARGET")
    	if target == "" {
    		target = "World"
    	}
    	fmt.Fprintf(w, "Hello %s!\n", target)
    }
    

    This code creates a web server that listens on the port defined by the PORT environment variable.

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Artifact Registry.

Node.js

  1. Create a new directory named helloworld-gke and change into this directory:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a package.json file with the following contents:

    {
      "name": "gke-helloworld",
      "version": "1.0.0",
      "description": "GKE hello world sample in Node",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "author": "",
      "license": "Apache-2.0",
      "dependencies": {
        "express": "^4.16.4"
      }
    }
    
  3. In the same directory, create a index.js file, and copy the following lines into this file:

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      console.log('Hello world received a request.');
    
      const target = process.env.TARGET || 'World';
      res.send(`Hello ${target}!`);
    });
    
    const port = process.env.PORT || 8080;
    app.listen(port, () => {
      console.log('Hello world listening on port', port);
    });

    This code creates a web server that listens on the port defined by the PORT environment variable.

Your app is finished and ready to be packaged in a Docker container and uploaded to Artifact Registry.

Python

  1. Create a new directory named helloworld-gke and change into this directory:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a file named app.py and paste the following code into this file:

    import os
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        target = os.environ.get('TARGET', 'World')
        return 'Hello {}!\n'.format(target)
    
    if __name__ == "__main__":
        app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))

Java

Create a Spring Boot app.

  1. Install Java SE 8 or higher JDK and cURL. Java SE and cURL are only required to create the new web project in the next step. The Dockerfile, which is described later, loads all dependencies into the container.

  2. From your terminal, create a new empty web project:

    curl https://start.spring.io/starter.zip \
        -d dependencies=web \
        -d javaVersion=1.8 \
        -d type=maven-project \
        -d bootVersion=2.6.6 \
        -d name=helloworld \
        -d artifactId=helloworld \
        -d baseDir=helloworld-gke \
        -o helloworld-gke.zip
    unzip helloworld-gke.zip
    cd helloworld-gke
    

    You now have a new Spring Boot project in helloworld-gke.

  3. In the src/main/java/com/example/helloworld/HelloworldApplication.java file, Update the HelloworldApplication class by adding a @RestController to handle the / mapping.

    package com.example.helloworld;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    public class HelloworldApplication {
    
    	@Value("${TARGET:World}")
    	String target;
    
    	@RestController
    	class HelloworldController {
    		@GetMapping("/")
    		String hello() {
    			return "Hello " + target + "!";
    		}
    	}
    
    	public static void main(String[] args) {
    		SpringApplication.run(HelloworldApplication.class, args);
    	}
    }

    This code creates a web server that listens on the port defined by the PORT environment variable.

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Artifact Registry.

C#

  1. Install the .NET SDK. The .NET SDK is only required to create the new web project in the next step. The Dockerfile, which is described later, loads all dependencies into the container.

  2. From your terminal, create a new empty web project:

    dotnet new web -o helloworld-gke
    
  3. Change directory to helloworld-gke.

    cd helloworld-gke
    
  4. Update Program.cs to listen on port 8080:

    var builder = WebApplication.CreateBuilder(args);
    
    // Google Cloud Run sets the PORT environment variable to tell this
    // process which port to listen to.
    var port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
    var url = $"http://0.0.0.0:{port}";
    var target = Environment.GetEnvironmentVariable("TARGET") ?? "World";
    
    var app = builder.Build();
    
    app.MapGet("/", () => $"Hello {target}!");
    
    app.Run(url);

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Artifact Registry.

PHP

  1. Create a new directory named helloworld-gke and change into this directory:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a file named index.php and paste the following code into this file:

    <?php
    $target = getenv('TARGET', true) ?: 'World';
    echo sprintf("Hello %s!", $target);
    ?>

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Artifact Registry.

Containerizing an app with Cloud Build

  1. To containerize the sample app, create a new file named Dockerfile in the same directory as the source files, and copy the following content:

    Go

    # Use the offical Go image to create a build artifact.
    # This is based on Debian and sets the GOPATH to /go.
    # https://hub.docker.com/_/golang
    FROM golang:1.21.0 as builder
    WORKDIR /app
    
    # Initialize a new Go module.
    RUN go mod init quickstart-go
    
    # Copy local code to the container image.
    COPY *.go ./
    
    # Build the command inside the container.
    RUN CGO_ENABLED=0 GOOS=linux go build -o /quickstart-go
    
    # Use a Docker multi-stage build to create a lean production image.
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM gcr.io/distroless/base-debian11
    
    # Change the working directory.
    WORKDIR /
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /quickstart-go /quickstart-go
    
    # Run the web service on container startup.
    USER nonroot:nonroot
    ENTRYPOINT ["/quickstart-go"]

    Node.js

    # Use the official lightweight Node.js 16 image.
    # https://hub.docker.com/_/node
    FROM node:17-slim
    
    # Create and change to the app directory.
    WORKDIR /usr/src/app
    
    # Copy application dependency manifests to the container image.
    # A wildcard is used to ensure both package.json AND package-lock.json are copied.
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install production dependencies.
    RUN npm install --omit=dev
    
    # Copy local code to the container image.
    COPY . ./
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]

    Add a further .dockerignore file to ensure that local files do not affect the container build process:

    Dockerfile
    README.md
    node_modules
    npm-debug.log
    

    Python

    # Use the official lightweight Python image.
    # https://hub.docker.com/_/python
    FROM python:3.7-slim
    
    # Copy local code to the container image.
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . ./
    
    # Install production dependencies.
    RUN pip install Flask gunicorn
    
    # Run the web service on container startup. Here we use the gunicorn
    # webserver, with one worker process and 8 threads.
    # For environments with multiple CPU cores, increase the number of workers
    # to be equal to the cores available.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 app:app

    Add a .dockerignore file to ensure that local files don't affect the container build process:

    Dockerfile
    README.md
    *.pyc
    *.pyo
    *.pyd
    __pycache__
    

    Java

    # Use the official maven/Java 8 image to create a build artifact.
    # https://hub.docker.com/_/maven
    FROM maven:3.5-jdk-8-alpine as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY pom.xml ./
    COPY src ./src/
    
    # Build a release artifact.
    RUN mvn package -DskipTests
    
    # Use AdoptOpenJDK for base image.
    # It's important to use OpenJDK 8u191 or above that has container support enabled.
    # https://hub.docker.com/r/adoptopenjdk/openjdk8
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM adoptopenjdk/openjdk8:jdk8u202-b08-alpine-slim
    
    # Copy the jar to the production image from the builder stage.
    COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar
    
    # Run the web service on container startup.
    CMD ["java","-Djava.security.egd=file:/dev/./urandom","-Dserver.port=${PORT}","-jar","/helloworld.jar"]

    C#

    # Use Microsoft's official lightweight .NET images.
    FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
    WORKDIR /app
    
    # Install production dependencies.
    # Copy csproj and restore as distinct layers.
    COPY *.csproj ./
    RUN dotnet restore
    
    # Copy local code to the container image.
    COPY . ./
    
    # Build a release artifact.
    RUN dotnet publish -c Release -o out
    
    # Run the web service on container startup in a lean production image.
    FROM mcr.microsoft.com/dotnet/aspnet:6.0
    WORKDIR /app
    COPY --from=build /app/out .
    
    # Start the .dll (will have the same name as your .csproj file)
    ENTRYPOINT ["dotnet", "helloworld-gke.dll"]

    Add a .dockerignore file to ensure that local files don't affect the container build process:

    Dockerfile
    README.md
    **/obj/
    **/bin/
    

    PHP

    # Use the official PHP 7.4 image.
    # https://hub.docker.com/_/php
    FROM php:7.4-apache
    
    # Copy local code to the container image.
    COPY index.php /var/www/html/
    
    # Use port 8080 in Apache configuration files.
    RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf
    
    # Configure PHP for development.
    # Switch to the production php.ini for production operations.
    # RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
    # https://hub.docker.com/_/php#configuration
    RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

    Add a .dockerignore file to ensure that local files don't affect the container build process:

    Dockerfile
    README.md
    vendor
    
  2. Get your Google Cloud project ID:

    gcloud config get-value project
    
  3. In this quickstart, you will store your container in Artifact Registry and deploy it to your cluster from the registry. Run the following command to create a repository named hello-repo in the same location as your cluster:

    gcloud artifacts repositories create hello-repo \
        --project=PROJECT_ID \
        --repository-format=docker \
        --location=us-central1 \
        --description="Docker repository"
    

    Replace the following values:

    • PROJECT_ID is your Google Cloud project ID
  4. Build your container image using Cloud Build, which is similar to running docker build and docker push, but the build happens on Google Cloud:

    gcloud builds submit \
      --tag us-central1-docker.pkg.dev/PROJECT_ID/hello-repo/helloworld-gke .
    

    The image is stored in Artifact Registry.

Creating a GKE cluster

A GKE cluster is a managed set of Compute Engine virtual machines that operate as a single GKE cluster.

  1. Create the cluster.

    gcloud container clusters create-auto helloworld-gke \
      --location us-central1
    
  2. Verify that you have access to the cluster. The following command lists the nodes in your container cluster which are up and running and indicates that you have access to the cluster.

    kubectl get nodes
    

    If you run into errors, refer to the Kubernetes Troubleshooting guide.

Deploying to GKE

To deploy your app to the GKE cluster you created, you need two Kubernetes objects.

  1. A Deployment to define your app.
  2. A Service to define how to access your app.

Deploy an app

The app has a frontend server that handles the web requests. You define the cluster resources needed to run the frontend in a new file called deployment.yaml. These resources are described as a Deployment. You use Deployments to create and update a ReplicaSet and its associated Pods.

  1. Create the deployment.yaml file in the same directory as your other files and copy the following content. Replace the following values in your file:

    • $GCLOUD_PROJECT is your Google Cloud project ID:
    • $LOCATION is the repository location, such as us-central1.
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: helloworld-gke
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello
      template:
        metadata:
          labels:
            app: hello
        spec:
          containers:
          - name: hello-app
            # Replace $LOCATION with your Artifact Registry location (e.g., us-west1).
            # Replace $GCLOUD_PROJECT with your project ID.
            image: $LOCATION-docker.pkg.dev/$GCLOUD_PROJECT/hello-repo/helloworld-gke:latest
            # This app listens on port 8080 for web traffic by default.
            ports:
            - containerPort: 8080
            env:
              - name: PORT
                value: "8080"
            resources:
              requests:
                memory: "1Gi"
                cpu: "500m"
                ephemeral-storage: "1Gi"
              limits:
                memory: "1Gi"
                cpu: "500m"
                ephemeral-storage: "1Gi"
  2. Deploy the resource to the cluster:

    kubectl apply -f deployment.yaml
    
  3. Track the status of the Deployment:

    kubectl get deployments
    

    The Deployment is complete when all of the AVAILABLE deployments are READY.

    NAME              READY   UP-TO-DATE   AVAILABLE   AGE
    helloworld-gke    1/1     1            1           20s
    

    If the Deployment has a mistake, run kubectl apply -f deployment.yaml again to update the Deployment with any changes.

  4. After the Deployment is complete, you can see the Pods that the Deployment created:

    kubectl get pods
    

Deploy a Service

Services provide a single point of access to a set of Pods. While it's possible to access a single Pod, Pods are ephemeral and can only be accessed reliably by using a service address. In your Hello World app, the "hello" Service defines a load balancer to access the hello-app Pods from a single IP address. This service is defined in the service.yaml file.

  1. Create the file service.yaml in the same directory as your other source files with the following content:

    # The hello service provides a load-balancing proxy over the hello-app
    # pods. By specifying the type as a 'LoadBalancer', Kubernetes Engine will
    # create an external HTTP load balancer.
    apiVersion: v1
    kind: Service
    metadata:
      name: hello
    spec:
      type: LoadBalancer
      selector:
        app: hello
      ports:
      - port: 80
        targetPort: 8080

    The Pods are defined separately from the service that uses the Pods. Kubernetes uses labels to select the pods that a service addresses. With labels, you can have a service that addresses Pods from different replica sets and have multiple services that point to an individual Pod.

  2. Create the Hello World Service:

    kubectl apply -f service.yaml
    
  3. Get the external IP address of the service:

    kubectl get services
    

    It can take up to 60 seconds to allocate the IP address. The external IP address is listed under the column EXTERNAL-IP for the hello Service.

    NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
    hello        LoadBalancer   10.22.222.222   35.111.111.11   80:32341/TCP   1m
    kubernetes   ClusterIP      10.22.222.1     <none>          443/TCP        20m
    

View a deployed app

You have now deployed all the resources needed to run the Hello World app on GKE.

Use the external IP address from the previous step to load the app in your web browser, and see your running app:

 http://EXTERNAL_IP

Or, you can make a curl call to the external IP address of the service:

curl EXTERNAL_IP

The output displays the following:

Hello World!

Clean up

To avoid incurring charges to your Google Cloud account for the resources used on this page, follow these steps.

You are charged for the Compute Engine instances running in your cluster, as well as for the container image in Artifact Registry.

Delete the project

Deleting your Google Cloud project stops billing for all the resources used within that project.

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Delete your cluster and container

If you want to keep your project but only delete the resources used in this tutorial, delete your cluster and image.

To delete a cluster using the Google Cloud CLI, run the following command for the mode that you used:

gcloud container clusters delete helloworld-gke \
    --location us-central1

To delete an image in your Artifact Registry repository, run the following command:

gcloud artifacts docker images delete \
    us-central1-docker.pkg.dev/PROJECT_ID/hello-repo/helloworld-gke

What's next

For more information on Kubernetes, see the following:

For more information on deploying to GKE, see the following:

For more information on creating, developing, and running applications on GKE directly from your IDE with Cloud Code, see the following: