PageSourceSearch

https://bartfokker.com/posts/cloud-run/

html bartfokker.com collected 2026-10-09 17:42:43 UTC 17,890 bytes, 208 lines download raw bytes

1<!doctype html>
2
3<html lang="en">
4  <head>
5    <meta charset="utf-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1">
7
8    <title>
9      Running a serverless Go web application | Bart Fokker
10    </title>
11
12    <meta name="description" content="How to run and deploy any Go application in a serverless manner">
13
14    <link rel="icon" href="/favicon.svg" type="image/svg+xml">
15
16    <link
17      rel="alternate"
18      type="application/rss+xml"
19      title="Bart Fokker"
20      href="/rss.xml"
21    >
22
23    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/picnic">
24    <link rel="stylesheet" href="/assets/css/site.css">
25    <link rel="stylesheet" href="/assets/css/prism-theme.css">
26
27    
27<script
28      async
29      src="https://unpkg.com/prismjs@latest/components/prism-core.min.js"
30    ></script>
30
31
32    
32<script
33      async
34      src="https://unpkg.com/prismjs@latest/plugins/autoloader/prism-autoloader.min.js"
35    ></script>
35
36  </head>
37
38  <body id="app">
39    <nav>
40  <div class="nav__container">
41    <a href="/" class="brand">bart@blog..</a>
42
43    <input id="cmenug" type="checkbox" class="show">
44    <label for="cmenug" class="burger pseudo button">&#8801;</label>
45
46    <div class="menu">
47      <a href="/" class="pseudo button menu__button icon-paper-plane">
48        Posts
49      </a>
50      <a href="/about" class="pseudo button icon-info-circled-alt menu__button">
51        About
52      </a>
53
54      <a href="/rss.xml" class="pseudo button menu__button menu__rss icon-rss">
55        RSS
56      </a>
57    </div>
58  </div>
59</nav>
60
61    <main class="main">
62          
63       
64      <div class="breadcrumb">
65        <a href="/">/home</a>      <span class="breadcrumb__sep">/</span>  <a href="/">posts</a>     <span class="breadcrumb__sep">/</span>  <span class="breadcrumb__current">cloud-run</span>   
66      </div>
67       
68<div class="post content-box">
69  <div class="prompt-line prompt-line--post">
70    <span class="prompt-line__left">
71      <span class="prompt-line__prompt">$</span>
72      <span class="prompt-line__path">~/Running a serverless Go web application</span>
73    </span>
74
75    <span class="prompt-line__date">27 October 2019</span>
76  </div>
77
78  <div class="post__content hackernoon">
79    <h2>Introduction</h2>
80<p>Recently Google introduced the beta program for <a href="https://cloud.google.com/run/">Google Cloud Run</a>.
81This is a service to run stateless containers on a fully managed environment by Google.
82It is essentially serverless for any container, scaling your containers up and down as requests (and peaks) come and go.
83The benefit of using tools like Cloud Run is that it abstracts away all the infrastructure, thus you can focus more on what really matters, building your application.</p>
84<p>The reason why I'm so excited is because it essentially allows me to run my (already) dockerized applications in a serverless manner, reducing costs and maintenance burdens.
85You can turn any dockerized application into a serverless application using Cloud Run and in this post we are going to explore this with Go.</p>
86<h2>Setting up</h2>
87<p><strong>Requirements</strong></p>
88<ul>
89<li>You need an Google Cloud Platform account, you can create one <a href="https://cloud.google.com/free/">here</a></li>
90<li>You need to enable billing on your Google Cloud Project to use Cloud Run see <a href="https://cloud.google.com/billing/docs/how-to/modify-project">this</a></li>
91<li>You need to install gcloud sdk locally and make sure that you are logged in, see the <a href="https://cloud.google.com/sdk/gcloud/">guide</a></li>
92</ul>
93<p>With everything set up in terms of account configuration we can enable Cloud Run in the GCP console. In the console head over to the menu on the left and press Cloud Run.
94This will move you to a page where you can enable Cloud Run for your current project (see picture below). After pressing the enable button Cloud Run will be available for your current project.</p>
95<p><img src="/posts/images/cloud-run/cloud-run-enable.png" alt="cloud run enable"></p>
96<p>Since cloud run is still in beta we need to install the beta components in the gcloud utility and set the gcloud region to one of the available regions for Cloud run:</p>
97<p><em>Enable cloud run in the gcloud utility</em></p>
98<pre class="language-bash"><code class="language-bash">gcloud components <span class="token function">install</span> beta</code></pre>
99<p>Set the region to one of the available regions:</p>
100<ul>
101<li>europe-west1</li>
102<li>us-central1</li>
103<li>us-east1</li>
104<li>asia-northeast1</li>
105</ul>
106<p><em>Executing the following command will set your default Cloud run region, this can be overridden with the --region flag</em></p>
107<pre class="language-bash"><code class="language-bash">$ gcloud config <span class="token builtin class-name">set</span> run/region <span class="token operator">&lt;</span>REGION<span class="token operator">></span></code></pre>
108<h2>Building the application</h2>
109<p>With that out of the way we can actually start building the application. We will be building an application which generates a random avatar based on the input you give it.
110We will be using the code from my previous <a href="/posts/identicon">post</a> and turn it in a serverless API, to generate identicons (avatars) on the fly.</p>
111<p>I've adjusted the code from my previous post to be used as a library, this way we can use it for a cli and for the web application we are building right now. You can find the code <a href="https://github.com/barthr/Identicon/tree/feature/identicon-library">here</a></p>
112<p>Let's start writing some code for the web version!</p>
113<pre class="language-go"><code class="language-go"><span class="token keyword">
113import</span> <span class="token punctuation">(</span>
114	<span class="token string">"github.com/barthr/identicon"</span>
115<span class="token punctuation">)</span>
116
117<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
118  http<span class="token punctuation">.</span><span class="token function">HandlerFunc</span><span class="token punctuation">(</span><span class="token string">"/identicon/generate"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">{</span>
119    name <span class="token operator">:=</span> r<span class="token punctuation">.</span>URL<span class="token punctuation">.</span><span class="token function">Query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Get</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span>
120    <span class="token keyword">if</span> name <span class="token operator">==</span> <span class="token string">""</span> <span class="token punctuation">{</span>
121      http<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> <span class="token string">"no name given"</span><span class="token punctuation">,</span> http<span class="token punctuation">.</span>StatusPreconditionFailed<span class="token punctuation">)</span>
122      <span class="token keyword">return</span>
123    <span class="token punctuation">}</span>
124    log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"generating identicon for %s"</span><span class="token punctuation">,</span> name<span class="token punctuation">)</span>
125
126    w<span class="token punctuation">.</span><span class="token function">Header</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Set</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"image/png"</span><span class="token punctuation">)</span>
127    <span class="token keyword">if</span> err <span class="token operator">:=</span> identicon<span class="token punctuation">.</span><span class="token function">Generate</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">WriteImage</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
128      http<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> <span class="token string">"failed generating identicon"</span><span class="token punctuation">,</span> http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">)</span>
129    <span class="token punctuation">}</span>
130  <span class="token punctuation">}</span><span class="token punctuation">)</span>
131  http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":"</span><span class="token operator">+</span>os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"PORT"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
132<span class="token punctuation">}</span></code></pre>
133<p><strong>We can run the code locally by running the following command <code>PORT=8080 go run main.go</code></strong></p>
134<p>This is everything needed to run a simple web application on cloud run. First we register a http handler which expects some input. After this input is considered &quot;valid&quot; we call our identicon library which will return a <code>Identicon struct</code>.</p>
135<p>On the identicon struct there is a method <code>WriteImage(w io.Writer) error</code>, by using a <code>io.Writer</code> here as input we can easily change where the image is written to, in this case the <code>http.ResponseWriter</code> is used but in case of the cli version the image can be written to an <code>os.File</code> or anything which implements the <code>io.Writer</code> interface.</p>
136<p>We are now ready to deploy this to cloud run. Cloud run requires that your co
136de is bundled in a docker container and is able to serve http requests on a port configurable with the <code>PORT</code> environment variable.</p>
137<p>Lets create a Dockerfile, I'll be using multistage builds so that the end container only contains the executable binary, I wont go in depth on how the Dockerfile and multistage builds work, if you want to learn more about this have a look at this <a href="https://docs.docker.com/develop/develop-images/multistage-build/">link</a>.</p>
138<pre class="language-docker"><code class="language-docker"><span class="token instruction"><span class="token keyword">FROM</span> golang:1.13 <span class="token keyword">as</span> builder</span>
139
140<span class="token comment"># Create and change to the app directory.</span>
141<span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span>
142
143<span class="token comment"># Retrieve application dependencies.</span>
144<span class="token instruction"><span class="token keyword">COPY</span> go.* ./</span>
145<span class="token instruction"><span class="token keyword">RUN</span> go mod download</span>
146
147<span class="token comment"># Copy local code to the container image.</span>
148<span class="token instruction"><span class="token keyword">COPY</span> . ./</span>
149
150<span class="token comment"># Build the binary.</span>
151<span class="token instruction"><span class="token keyword">RUN</span> CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server cmd/api/main.go</span>
152
153
154<span class="token instruction"><span class="token keyword">FROM</span> alpine:3</span>
155<span class="token instruction"><span class="token keyword">RUN</span> apk add --no-cache ca-certificates</span>
156
157<span class="token comment"># Copy the binary to the production image from the builder stage.</span>
158<span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">builder</span></span> /app/server /server</span>
159
160<span class="token comment"># Run the web service on container startup.</span>
161<span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"/server"</span>]</span></code></pre>
162<p>With that out of the way we can submit a build to <a href="https://cloud.google.com/cloud-build/">Google cloud build</a>. Google cloud build will build our container and store it in cloud registry.</p>
163<p><em>Execute the following command to publish a build to google cloud build</em></p>
164<pre class="language-bash"><code class="language-bash">$ gcloud builds submit <span class="token parameter variable">--tag</span> gcr.io/<span class="token operator">&lt;</span>PROJECT_ID<span class="token operator">></span>/identicon-generator</code></pre>
165<p>If cloud build successfully builds the container it will be published to the <a href="https://cloud.google.com/container-registry/">Google container registry</a>. We can use the image in the registry to submit a deploy to Cloud run. There are multiple ways to do it but I'll be using the terminal to deploy the container to cloud run.</p>
166<p><em>Execute the following command to publish a deploy to cloud run</em></p>
167<pre class="language-bash"><code class="language-bash">$ gcloud beta run deploy <span class="token parameter variable">--image</span> gcr.io/<span class="token operator">&lt;</span>PROJECT_ID<span class="token operator">></span>/identicon-generator <span class="token parameter variable">--platform</span> managed</code></pre>
168<p>There are a few things going on here. First we tell gcloud that we want to use the managed platform using the <code>--platform</code> flag, it is possible to install Cloud run on your own Kubernetes cluster but that is out of scope of this article. Next we define what image we want to deploy, we point this to the image just published by cloud build, by default it will fetch the latest version of this image but it is also possible to deploy a specific version by adding a tag.</p>
169<p>The command will prompt us with a question if we want allow unauthenticated requests, we input yes and now our cloud run function will be publicly exposed.</p>
170<p><em>You can view your cloud run functions in the Google cloud run dashboard</em></p>
171<p><img src="/posts/images/cloud-run/cloud-run-dashboard.png" alt="cloud run dashboard"></p>
172<p>You can now test your cloud function by going to the url which cloud run provides to us. My identicon generator is running on https://identicon-generator-ziolee7x5a-ew.a.run.app/identicon/generate?name=super+cool. You can test it out with various input
172s, the first boot can be a bit slow because the container is having a cold start but subsequent requests will be very quick because there is no need for a cold start.</p>
173<h2>Conclusion</h2>
174<p>Deploying web applications with cloud run is very easy. It is as simple as defining a Dockerfile and publishing the image to google cloud. It removes the burden to maintain any infrastructure and you only pay for the resources it uses. Scaling is done automatically and in combination with hosted databases like cloud sql you have a fully managed solution which is very easy to maintain and also very scalable.</p>
175<p>The benefits of using a service like cloud run is that your local environment is not depending on any kind of serverless framework or library, it is just a regular go application which you can test and run in the same way as you're used to.</p>
176<p>Cloud run is still in beta and will hopefully soon be covered by the SLA and available in more regions. In a later post I will explain how to automate the build process from a <code>git push</code> to a deployment in cloud run.</p>
177<p>The full code can be found <a href="https://github.com/barthr/Identicon/tree/feature/identicon-library">here</a></p>
178<p><strong>Thanks for reading and happy coding 🚀</strong></p>
179
180  </div>
181</div>
182
183<div class="post-comments"></div>
184
185    </main>
186    <footer class="footer">
187      <span>Copyright Bart Fokker © 2025.</span>
188    </footer>
189    
189<script>
190      (() => {
191        const root = document.body;
192        let ticking = false;
193        const toggleCaret = () => {
194          const shouldPause = window.scrollY > 40;
195          root.classList.toggle("caret-paused", shouldPause);
196          ticking = false;
197        };
198        window.addEventListener("scroll", () => {
199          if (!ticking) {
200            window.requestAnimationFrame(toggleCaret);
201            ticking = true;
202          }
203        });
204        toggleCaret();
205      })();
206    </script>
206
207  
207<script async src="https://scripts.simpleanalyticscdn.com/latest.js" ></script>
207</body>
208</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.