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. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  7. To initialize the gcloud CLI, run the following command:

    gcloudinit
  8. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

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

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

    Enable the APIs

  11. Install the Google Cloud CLI.
  12. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  13. To initialize the gcloud CLI, run the following command:

    gcloudinit
  14. kubectl is used to manage Kubernetes, the cluster orchestration system used by GKE. You can install kubectl by using gcloud:
    gcloudcomponentsinstallkubectl

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:

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

    packagemainimport("fmt""log""net/http""os")funcmain(){http.HandleFunc("/",handler)port:=os.Getenv("PORT")ifport==""{port="8080"}log.Printf("Listening on localhost:%s",port)log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s",port),nil))}funchandler(whttp.ResponseWriter,r*http.Request){log.Print("Hello world received a request.")target:=os.Getenv("TARGET")iftarget==""{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:

    constexpress=require('express');constapp=express();app.get('/',(req,res)=>{console.log('Hello world received a request.');consttarget=process.env.TARGET||'World';res.send(`Hello ${target}!`);});constport=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:

    importosfromflaskimportFlaskapp=Flask(__name__)@app.route('/')defhello_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)))

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:

    varbuilder=WebApplication.CreateBuilder(args);// Google Cloud Run sets the PORT environment variable to tell this// process which port to listen to.varport=Environment.GetEnvironmentVariable("PORT")??"8080";varurl=$"http://0.0.0.0:{port}";vartarget=Environment.GetEnvironmentVariable("TARGET")??"World";varapp=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/_/golangFROMgolang:1.23.2asbuilderWORKDIR/app# Initialize a new Go module.RUNgomodinitquickstart-go # Copy local code to the container image.COPY*.go./ # Build the command inside the container.RUNCGO_ENABLED=0GOOS=linuxgobuild-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-buildsFROMgcr.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.USERnonroot:nonrootENTRYPOINT["/quickstart-go"]

    Node.js

    # Use the official lightweight Node.js 16 image.# https://hub.docker.com/_/nodeFROMnode:21-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.COPYpackage*.json./ # Install production dependencies.RUNnpminstall--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/_/pythonFROMpython:3.13-slim# Copy local code to the container image.ENVAPP_HOME/app WORKDIR$APP_HOMECOPY../ # Install production dependencies.RUNpipinstallFlaskgunicorn # 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.CMDexecgunicorn--bind:$PORT--workers1--threads8app:app

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

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

    C#

    # Use Microsoft's official lightweight .NET images.FROMmcr.microsoft.com/dotnet/sdk:6.0ASbuildWORKDIR/app# Install production dependencies.# Copy csproj and restore as distinct layers.COPY*.csproj./ RUNdotnetrestore # Copy local code to the container image.COPY../ # Build a release artifact.RUNdotnetpublish-cRelease-oout # Run the web service on container startup in a lean production image.FROMmcr.microsoft.com/dotnet/aspnet:6.0WORKDIR/appCOPY--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/_/phpFROMphp:8.3-apache# Copy local code to the container image.COPYindex.php/var/www/html/ # Use port 8080 in Apache configuration files.RUNsed-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#configurationRUNmv"$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:

    gcloudartifactsrepositoriescreatehello-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:

    gcloudbuildssubmit\--tagus-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.

    gcloudcontainerclusterscreate-autohelloworld-gke\--locationus-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/v1kind:Deploymentmetadata:name:helloworld-gkespec:replicas:1selector:matchLabels:app:hellotemplate:metadata:labels:app:hellospec: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:8080env:-name:PORTvalue:"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:

    kubectlapply-fdeployment.yaml 
  3. Track the status of the Deployment:

    kubectlgetdeployments 

    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:

    kubectlgetpods 

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:v1kind:Servicemetadata:name:hellospec:type:LoadBalancerselector:app:helloports:-port:80targetPort: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:

    kubectlapply-fservice.yaml 
  3. Get the external IP address of the service:

    kubectlgetservices 

    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:

gcloudcontainerclustersdeletehelloworld-gke\--locationus-central1 

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

gcloudartifactsdockerimagesdelete\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: