Using system packages tutorial
Stay organized with collections
Save and categorize content based on your preferences.
This tutorial shows how to build a custom Cloud Run service
that transforms a graph description input parameter into a diagram in the PNG
image format. It uses Graphviz and
is installed as a system package in the service's container environment.
Graphviz is used via command-line utilities to serve requests.
To generate a cost estimate based on your projected usage,
use the pricing calculator.
New Google Cloud users might be eligible for a free trial.
Before you begin
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.
In the Google Cloud console, on the project selector page,
select or create a Google Cloud project.
Roles required to select or create a project
Select a project: Selecting a project doesn't require a specific
IAM role—you can select any project that you've been
granted a role on.
Create a project: To create a project, you need the Project Creator role
(roles/resourcemanager.projectCreator), which contains the
resourcemanager.projects.create permission. Learn how to grant
roles.
In the Google Cloud console, on the project selector page,
select or create a Google Cloud project.
Roles required to select or create a project
Select a project: Selecting a project doesn't require a specific
IAM role—you can select any project that you've been
granted a role on.
Create a project: To create a project, you need the Project Creator role
(roles/resourcemanager.projectCreator), which contains the
resourcemanager.projects.create permission. Learn how to grant
roles.
To configure gcloud with defaults for your Cloud Run service:
Set your default project:
gcloudconfigsetprojectPROJECT_ID
Replace PROJECT_ID with the name of the project you created for
this tutorial.
Configure gcloud for your chosen region:
gcloudconfigsetrun/regionREGION
Replace REGION with the supported Cloud Run
region
of your choice.
Cloud Run locations
Cloud Run is regional, which means the infrastructure that
runs your Cloud Run services is located in a specific region and is
managed by Google to be redundantly available across
all the zones within that region.
Meeting your latency, availability, or durability requirements are primary
factors for selecting the region where your Cloud Run services are run.
You can generally select the region nearest to your users but you should consider
the location of the other Google Cloud
products that are used by your Cloud Run service.
Using Google Cloud products together across multiple locations can affect
your service's latency as well as cost.
Change to the directory that contains the Cloud Run sample
code:
Node.js
cdnodejs-docs-samples/run/system-package/
Python
cdpython-docs-samples/run/system-package/
Go
cdgolang-samples/run/system_package/
Java
cdjava-docs-samples/run/system-package/
Visualizing the architecture
The basic architecture looks like this:
Diagram showing request flow from user to web service to graphviz dot utility.
For the diagram source, see the
DOT Description
The user makes an HTTP request to the Cloud Run service which executes
a Graphviz utility to transform the request into an image. That image is
delivered to the user as the HTTP response.
Understanding the code
Defining your environment configuration with the Dockerfile
Your Dockerfile is specific to the language and base operating environment,
such as Ubuntu, that your service will use.
This service requires one or more additional system packages not
available by default.
Open the Dockerfile in an editor.
Look for a DockerfileRUN
statement. This statement allows running arbitrary shell commands to modify
the environment. If the Dockerfile has multiple stages, identified by
finding multiple FROM statements, it will be found in the last stage.
The specific packages required and the mechanism to install them varies by
the operating system declared inside the container.
To get instructions for your operating system or base image, click the
appropriate tab.
Alpine
Alpine requires a second package for font support.
RUNapk--no-cacheaddgraphviz
To determine the operating system of your container image, check the name in
the FROM statement or a README associated with your base image. For example,
if you extend from node, you can find documentation and the parent
Dockerfile on Docker Hub.
The sample service uses parameters from the incoming HTTP request to invoke a
system call that executes the appropriate dot utility command.
In the HTTP handler below, a graph description input parameter is extracted from
the dot querystring variable.
Graph descriptions can include characters which must be
URL encoded for use in a querystring.
Node.js
app.get('/diagram.png',(req,res)=>{try{constimage=createDiagram(req.query.dot);res.setHeader('Content-Type','image/png');res.setHeader('Content-Length',image.length);res.setHeader('Cache-Control','public, max-age=86400');res.send(image);}catch(err){console.error(`error: ${err.message}`);consterrDetails=(err.stderr||err.message).toString();if(errDetails.includes('syntax')){res.status(400).send(`Bad Request: ${err.message}`);}else{res.status(500).send('Internal Server Error');}}});
Python
@app.route("/diagram.png",methods=["GET"])defindex():"""Takes an HTTP GET request with query param dot and returns a png with the rendered DOT diagram in a HTTP response. """try:image=create_diagram(request.args.get("dot"))response=make_response(image)response.headers.set("Content-Type","image/png")returnresponseexceptExceptionase:print(f"error: {e}")# If no graphviz definition or bad graphviz def, return 400if"syntax"instr(e):returnf"Bad Request: {e}",400return"Internal Server Error",500
Go
// diagramHandler renders a diagram using HTTP request parameters and the dot command.funcdiagramHandler(whttp.ResponseWriter,r*http.Request){ifr.Method!=http.MethodGet{log.Printf("method not allowed: %s",r.Method)http.Error(w,fmt.Sprintf("HTTP Method %s Not Allowed",r.Method),http.StatusMethodNotAllowed)return}q:=r.URL.Query()dot:=q.Get("dot")ifdot==""{log.Print("no graphviz definition provided")http.Error(w,"Bad Request",http.StatusBadRequest)return}// Cache header must be set before writing a response.w.Header().Set("Cache-Control","public, max-age=86400")input:=strings.NewReader(dot)iferr:=createDiagram(w,input);err!=nil{log.Printf("createDiagram: %v",err)// Do not cache error responses.w.Header().Del("Cache-Control")ifstrings.Contains(err.Error(),"syntax"){http.Error(w,"Bad Request: DOT syntax error",http.StatusBadRequest)}else{http.Error(w,"Internal Server Error",http.StatusInternalServerError)}}}
Java
get("/diagram.png",(req,res)->{InputStreamimage=null;try{Stringdot=req.queryParams("dot");image=createDiagram(dot);res.header("Content-Type","image/png");res.header("Content-Length",Integer.toString(image.available()));res.header("Cache-Control","public, max-age=86400");}catch(Exceptione){if(e.getMessage().contains("syntax")){res.status(400);returnString.format("Bad Request: %s",e.getMessage());}else{res.status(500);return"Internal Server Error";}}returnimage;});
You'll need to differentiate between internal server errors and invalid user
input. This sample service returns an Internal Server Error for all dot
command-line errors unless the error message contains the string syntax, which
indicates a user input problem.
Generating a diagram
The core logic of diagram generation uses the dot command-line tool to process
the graph description input parameter into a diagram in the PNG image format.
Node.js
// Generate a diagram based on a graphviz DOT diagram description.constcreateDiagram=dot=>{if(!dot){thrownewError('syntax: no graphviz definition provided');}// Adds a watermark to the dot graphic.constdotFlags=['-Glabel="Made on Cloud Run"','-Gfontsize=10','-Glabeljust=right','-Glabelloc=bottom','-Gfontcolor=gray',].join(' ');constimage=execSync(`/usr/bin/dot ${dotFlags} -Tpng`,{input:dot,});returnimage;};
Python
defcreate_diagram(dot):"""Generates a diagram based on a graphviz DOT diagram description. Args: dot: diagram description in graphviz DOT syntax Returns: A diagram in the PNG image format. """ifnotdot:raiseException("syntax: no graphviz definition provided")dot_args=[# These args add a watermark to the dot graphic."-Glabel=Made on Cloud Run","-Gfontsize=10","-Glabeljust=right","-Glabelloc=bottom","-Gfontcolor=gray","-Tpng",]# Uses local `dot` binary from Graphviz:# https://graphviz.gitlab.ioimage=subprocess.run(["dot"]+dot_args,input=dot.encode("utf-8"),stdout=subprocess.PIPE).stdoutifnotimage:raiseException("syntax: bad graphviz definition provided")returnimage
Go
// createDiagram generates a diagram image from the provided io.Reader written to the io.Writer.funccreateDiagram(wio.Writer,rio.Reader)error{stderr:=new(bytes.Buffer)args:=[]string{"-Glabel=Made on Cloud Run","-Gfontsize=10","-Glabeljust=right","-Glabelloc=bottom","-Gfontcolor=gray","-Tpng",}cmd:=exec.Command("/usr/bin/dot",args...)cmd.Stdin=rcmd.Stdout=wcmd.Stderr=stderriferr:=cmd.Run();err!=nil{returnfmt.Errorf("exec(%s) failed (%w): %s",cmd.Path,err,stderr.String())}returnnil}
Java
// Generate a diagram based on a graphviz DOT diagram description.publicstaticInputStreamcreateDiagram(Stringdot){if(dot==null||dot.isEmpty()){thrownewNullPointerException("syntax: no graphviz definition provided");}// Adds a watermark to the dot graphic.List<String>args=newArrayList<>();args.add("/usr/bin/dot");args.add("-Glabel=\"Made on Cloud Run\"");args.add("-Gfontsize=10");args.add("-Glabeljust=right");args.add("-Glabelloc=bottom");args.add("-Gfontcolor=gray");args.add("-Tpng");StringBuilderoutput=newStringBuilder();InputStreamstdout=null;try{ProcessBuilderpb=newProcessBuilder(args);Processprocess=pb.start();OutputStreamstdin=process.getOutputStream();stdout=process.getInputStream();// The Graphviz dot program reads from stdin.Writerwriter=newOutputStreamWriter(stdin,"UTF-8");writer.write(dot);writer.close();process.waitFor();}catch(Exceptione){System.out.println(e);}returnstdout;}
Designing a secure service
Any vulnerabilities in the dot tool are potential vulnerabilities of
the web service. You can mitigate this by using up-to-date versions of the
graphviz package through re-building the container image on a regular basis.
If you extend the current sample to accept user input as command-line
parameters, you should protect against
command-injection attacks. Some of
the ways to prevent injection attacks include:
Mapping inputs to a dictionary of supported parameters
Validating inputs match a range of known-safe values, perhaps using regular
expressions
Escaping inputs to ensure shell syntax is not evaluated
You can further mitigate potential vulnerabilities by deploying the service
with a service account that has not been granted any permissions to use Google Cloud
services, rather than using the default account, which has commonly used
permissions. For that reason, the steps in this tutorial
create and use a new service account.
Shipping the code
To ship your code, you build with Cloud Build, and upload to
Artifact Registry, and deploy to Cloud Run:
Where PROJECT_ID is your Google Cloud project ID, and graphviz is the
name you want to give your service.
Upon success, you will see a SUCCESS message containing the ID, creation
time, and image name. The image is stored in Artifact Registry and can be
re-used if desired.
Where PROJECT_ID is your Google Cloud project ID, and graphviz is the
name you want to give your service.
Upon success, you will see a SUCCESS message containing the ID, creation
time, and image name. The image is stored in Artifact Registry and can be
reused if desired.
Where PROJECT_ID is your Google Cloud project ID, and graphviz is the
name you want to give your service.
Upon success, you will see a SUCCESS message containing the ID, creation
time, and image name. The image is stored in Artifact Registry and can be
reused if desired.
Java
This sample uses Jib to build
Docker images using common Java tools. Jib optimizes container builds without
the need for a Dockerfile or having Docker
installed. Learn more about building Java containers with Jib.
Using the Dockerfile, configure and build a base image with the system
packages installed to override Jib's default base image:
Create a new service account. Your code, including any system packages
it uses, can use only those
Google Cloud services that have been granted to this service account.
gcloudiamservice-accountscreateSA_NAME
Where SA_NAME is a name you give this service account. If there is
an error or a vulnerability in your code, your code will not be able to access any
of your other Google Cloud project resources.
Where PROJECT_ID is your Google Cloud project ID, SA_NAME is
the name of the service account you created, and graphviz is the
name of the container from above and graphviz-web is the name of the
service.
Respond Y to the "allow unauthenticated"
prompt. See Managing access for more
details on IAM-based authentication.
Wait for the deployment to finish. This can take about half a minute.
On success, the command line displays the service URL.
The following Terraform code creates a Cloud Run service.
resource"google_service_account""graphviz"{account_id="graphviz"display_name="GraphViz Tutorial Service Account"}resource"google_cloud_run_v2_service""default"{name="graphviz-example"location="us-central1"deletion_protection=false # set to "true" in productiontemplate{containers{ # Replace with the URL of your graphviz image # gcr.io/<YOUR_GCP_PROJECT_ID>/graphvizimage="us-docker.pkg.dev/cloudrun/container/hello"}service_account=google_service_account.graphviz.email}}
Replace IMAGE_URL with a reference to the container image, for
example, us-docker.pkg.dev/cloudrun/container/hello:latest. If you use Artifact Registry,
the repositoryREPO_NAME must
already be created. The URL follows the format of LOCATION-docker.pkg.dev/PROJECT_ID/REPO_NAME/PATH:TAG
.
The following Terraform code makes your Cloud Run service public.
# Make Cloud Run service publicly accessibleresource"google_cloud_run_service_iam_member""allow_unauthenticated"{service=google_cloud_run_v2_service.default.namelocation=google_cloud_run_v2_service.default.locationrole="roles/run.invoker"member="allUsers"}
If you want to deploy a code update to the service, repeat the previous
steps. Each deployment to a service creates a new revision and automatically
starts serving traffic when ready.
Try it out
Try out your service by sending HTTP POST requests with DOT syntax
descriptions in the request payload.
Send an HTTP request to your service.
Copy the URL into your browser URL bar and update [SERVICE_DOMAIN]:
To avoid additional charges to your Google Cloud account, delete all the resources
you deployed with this tutorial.
Delete the project
If you created a new project for this tutorial, delete the project.
If you used an existing project and need to keep it without the changes you added
in this tutorial, delete resources that you created for the tutorial.
The easiest way to eliminate billing is to delete the project that you
created for the tutorial.
To delete the project:
In the Google Cloud console, go to the Manage resources page.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Hard to understand","hardToUnderstand","thumb-down"],["Incorrect information or sample code","incorrectInformationOrSampleCode","thumb-down"],["Missing the information/samples I need","missingTheInformationSamplesINeed","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025年11月20日 UTC."],[],[]]