<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Deployment on WebDevStation</title>
    <link>https://webdevstation.com/tags/deployment/</link>
    <description>2 articles tagged Deployment — tutorials, code examples and notes from building real systems, newest first.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor>Alex</managingEditor>
    <webMaster>Alex</webMaster>
    <copyright>© 2026 WebDevStation</copyright>
    <lastBuildDate>Tue, 11 Aug 2026 18:40:00 +0200</lastBuildDate>
    <atom:link href="https://webdevstation.com/tags/deployment/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Graceful Shutdown in Go Web Services: Stop Dropping Requests on Deploy</title>
      <link>https://webdevstation.com/posts/graceful-shutdown-in-go-web-services/</link>
      <pubDate>Tue, 11 Aug 2026 18:40:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/graceful-shutdown-in-go-web-services/</guid>
      <description>How to shut down a Go HTTP server without dropping in-flight requests: signal.NotifyContext, Server.Shutdown, draining background workers, and the timeouts that make…</description>
      <content:encoded><![CDATA[<p>The first time I deployed a Go service behind a rolling update, our error dashboard lit up on every single release. Nothing was broken — the new version was fine, the old version was fine. The problem was the half-second in between, where the old process died mid-request and a few dozen users got a connection reset. Fixing it took about twenty lines of code, and I have copied those twenty lines into every service since.</p>
<h2 id="what-actually-happens-on-shutdown">What Actually Happens on Shutdown</h2>
<p>When your orchestrator wants a container gone, it sends <code>SIGTERM</code> and starts a countdown. If the process is still alive when the countdown ends, it gets <code>SIGKILL</code>, which cannot be caught.</p>
<p>A Go program with no signal handling takes the default action for <code>SIGTERM</code>: immediate termination. Every open connection is severed. Any request that was 90% done is simply gone — the client sees a reset, your retry budget takes the hit, and if that request was a payment you now have a support ticket.</p>
<p>Graceful shutdown means using the window between <code>SIGTERM</code> and <code>SIGKILL</code> to:</p>
<ol>
<li>Stop accepting new connections.</li>
<li>Let in-flight requests finish.</li>
<li>Drain background workers.</li>
<li>Close databases, caches and queues.</li>
<li>Exit before the countdown runs out.</li>
</ol>
<h2 id="the-twenty-lines">The Twenty Lines</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os/signal&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;syscall&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Addr</span>:              <span style="color:#e6db74">&#34;:8080&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Handler</span>:           <span style="color:#a6e22e">newRouter</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadHeaderTimeout</span>: <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadTimeout</span>:       <span style="color:#ae81ff">15</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">WriteTimeout</span>:      <span style="color:#ae81ff">30</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">IdleTimeout</span>:       <span style="color:#ae81ff">60</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ctx is cancelled the first time we receive SIGINT or SIGTERM.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">stop</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">NotifyContext</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;listening&#34;</span>, <span style="color:#e6db74">&#34;addr&#34;</span>, <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Addr</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// ListenAndServe always returns a non-nil error; ErrServerClosed</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// is the expected one after Shutdown.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;listen failed&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stop</span>() <span style="color:#75715e">// restore default handling: a second Ctrl-C now kills us</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;shutdown signal received, draining&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;graceful shutdown failed, forcing close&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;shutdown complete&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That is the whole pattern. A few details are load-bearing:</p>
<p><strong><code>signal.NotifyContext</code> instead of a channel.</strong> Since Go 1.16 this gives you a <code>context.Context</code> that cancels on the listed signals, which composes with everything else that already takes a context. If contexts and cancellation are new to you, <a href="/posts/understanding-golang-context/">understanding Golang context</a> is the background reading.</p>
<p><strong>Calling <code>stop()</code> after the first signal.</strong> It restores the default signal behaviour, so an impatient operator pressing Ctrl-C a second time gets an immediate exit instead of being ignored.</p>
<p><strong>A fresh context for <code>Shutdown</code>.</strong> Deriving it from <code>ctx</code> would be a bug: <code>ctx</code> is already cancelled, so <code>Shutdown</code> would return instantly and drain nothing.</p>
<p><strong>Checking for <code>ErrServerClosed</code>.</strong> <code>ListenAndServe</code> returns it on a clean shutdown. Treating that as a failure produces a scary log line on every normal deploy.</p>
<h2 id="what-shutdown-does-and-does-not-do">What Shutdown Does and Does Not Do</h2>
<p><code>Server.Shutdown</code> closes all open listeners, closes idle connections, and then waits for active ones to become idle. It returns when everything is drained or when its context expires — whichever comes first.</p>
<p>What it does <strong>not</strong> cover:</p>
<ul>
<li><strong>Hijacked connections</strong>, including WebSockets. <code>Shutdown</code> does not wait for them, and it does not close them. You have to track and close them yourself.</li>
<li><strong>Background goroutines</strong> you started outside the request path. Nothing knows about them.</li>
<li><strong>Long-polling or streaming responses.</strong> These are &ldquo;active&rdquo; for as long as they stream, so they will hold the drain open until your timeout fires.</li>
</ul>
<p>For WebSockets, the usual approach is to register a callback that broadcasts a close frame:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">RegisterOnShutdown</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">hub</span>.<span style="color:#a6e22e">CloseAll</span>(<span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">CloseServiceRestart</span>, <span style="color:#e6db74">&#34;server restarting&#34;</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p><code>RegisterOnShutdown</code> callbacks run in their own goroutines as soon as <code>Shutdown</code> starts, so they get the whole drain window to do their work.</p>
<h2 id="draining-background-workers-too">Draining Background Workers Too</h2>
<p>Most real services do more than serve HTTP. If you have consumers, cron loops, or a queue like the one in <a href="/posts/simple-queue-implementation-in-golang/">my simple queue implementation</a>, they need to finish too. <code>errgroup</code> keeps this readable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;golang.org/x/sync/errgroup&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{<span style="color:#a6e22e">Addr</span>: <span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#a6e22e">Handler</span>: <span style="color:#a6e22e">newRouter</span>()}
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">queue</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">NewQueue</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>, <span style="color:#a6e22e">gCtx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errgroup</span>.<span style="color:#a6e22e">WithContext</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 1. Serve HTTP.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;http server: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 2. Consume the queue until the group context is cancelled.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">queue</span>.<span style="color:#a6e22e">Consume</span>(<span style="color:#a6e22e">gCtx</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 3. When anything cancels gCtx — a signal, or a failure in another</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//    goroutine — drain the HTTP server.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">gCtx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">stop</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">NotifyContext</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;service stopped&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;service stopped cleanly&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The nice property here is that failure propagates in both directions. A signal drains the HTTP server; a fatal error in the queue consumer also drains the HTTP server, because <code>errgroup.WithContext</code> cancels <code>gCtx</code> as soon as any goroutine returns an error.</p>
<p>The order of shutdown matters, and it is the reverse of startup: stop accepting work, finish what you have, then close the things that work depends on. Closing your database pool before draining HTTP guarantees a burst of errors from requests that were nearly done.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// After g.Wait() returns, nothing is still using these.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Close</span>()
</span></span></code></pre></div><h2 id="the-load-balancer-problem">The Load Balancer Problem</h2>
<p>Here is the part that surprises people: even a perfectly graceful process can drop requests.</p>
<p>Between the moment your pod receives <code>SIGTERM</code> and the moment the load balancer stops sending it traffic, there is a gap. Endpoint updates propagate asynchronously — through the API server, to kube-proxy or an ingress controller, and finally to the actual routing table. During that gap the balancer is still sending new connections to a server that has already closed its listener. Those connections are refused.</p>
<p>The fix is to keep serving for a few seconds <em>after</em> the signal arrives:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;signal received, waiting for load balancer to deregister&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Keep serving while the endpoint removal propagates.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>)
</span></span></code></pre></div><p>It feels wrong to <code>sleep</code> on purpose, but it is the standard remedy, and Kubernetes has a hook for exactly this so you do not need it in your code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">lifecycle</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">preStop</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">exec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">command</span>: [<span style="color:#e6db74">&#34;/bin/sh&#34;</span>, <span style="color:#e6db74">&#34;-c&#34;</span>, <span style="color:#e6db74">&#34;sleep 5&#34;</span>]
</span></span><span style="display:flex;"><span><span style="color:#f92672">terminationGracePeriodSeconds</span>: <span style="color:#ae81ff">45</span>
</span></span></code></pre></div><p><code>preStop</code> runs <em>before</em> <code>SIGTERM</code> is sent, while the pod is already being removed from the endpoints list. By the time your process sees the signal, traffic has stopped arriving.</p>
<p>Whichever way you do it, keep the arithmetic straight:</p>
<pre tabindex="0"><code>preStop sleep (5s) + drain timeout (20s) + close time (2s) &lt; terminationGracePeriodSeconds (45s)
</code></pre><p>If the total exceeds the grace period, you get <code>SIGKILL</code> mid-drain and you are back where you started. Give yourself real headroom — the default grace period is 30 seconds, which is not much once a slow request is in flight.</p>
<p>A readiness probe that starts failing on <code>SIGTERM</code> achieves the same thing more precisely:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ready</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">Bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() { <span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#66d9ef">true</span>) }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// /readyz</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">readyz</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Load</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;shutting down&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusServiceUnavailable</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// On signal, before draining:</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#66d9ef">false</span>)
</span></span></code></pre></div><p>Keep <code>/healthz</code> (liveness) returning 200 the whole time — if liveness fails during shutdown, the kubelet may kill the container instead of letting it drain.</p>
<h2 id="docker-gotchas">Docker Gotchas</h2>
<p>Two container-level mistakes will silently defeat everything above.</p>
<p><strong>Shell-form <code>CMD</code> makes your process PID 2.</strong> Written as <code>CMD ./server</code>, Docker runs <code>/bin/sh -c ./server</code>. The shell is PID 1, receives <code>SIGTERM</code>, and does not forward it. Your server never hears a thing.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#75715e"># Wrong — signals stop at the shell</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> ./server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># Right — exec form, your binary is PID 1</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;./server&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p><strong><code>docker stop</code> waits 10 seconds by default.</strong> If your drain window is 20 seconds, you will be killed halfway through. Raise it: <code>docker stop -t 45</code>, or <code>stop_grace_period: 45s</code> in Compose.</p>
<h2 id="verifying-it-works">Verifying It Works</h2>
<p>Do not take it on faith — this is easy to test. Add a slow endpoint, start a request, and signal the process mid-flight:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/slow&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">After</span>(<span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;finished\n&#34;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>().<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// The client gave up; Shutdown does not cancel request contexts.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./server &amp;
</span></span><span style="display:flex;"><span>curl -s localhost:8080/slow &amp;     <span style="color:#75715e"># starts a 10s request</span>
</span></span><span style="display:flex;"><span>sleep <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>kill -TERM %1                     <span style="color:#75715e"># signal while it is in flight</span>
</span></span></code></pre></div><p>A correct implementation prints <code>finished</code> after ten seconds and then exits. A broken one prints nothing and the curl reports a reset connection.</p>
<p>For the same check under real traffic, point a load test at the service and restart it mid-run — the technique from <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">an easy way to load test your web apps</a> works well here. With graceful shutdown in place your error count during a restart should be exactly zero; without it, you will see the exact number of requests that were in flight.</p>
<p>Note what <code>Shutdown</code> does <em>not</em> do: it does not cancel <code>r.Context()</code> for in-flight requests. That is deliberate — the request should be allowed to complete. It also means a handler with no timeout of its own can hold the drain open until your shutdown context expires, which is why the <code>WriteTimeout</code> in the first example matters.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li><code>signal.NotifyContext</code> for <code>SIGINT</code> and <code>SIGTERM</code>.</li>
<li><code>srv.Shutdown</code> with its own fresh, bounded context.</li>
<li><code>errors.Is(err, http.ErrServerClosed)</code> treated as success.</li>
<li>Background workers cancelled through the same context, drained before dependencies close.</li>
<li>Dependencies closed last, in reverse order of startup.</li>
<li>Readiness probe flipped to failing before the drain begins.</li>
<li><code>preStop</code> hook or a deliberate sleep to cover load balancer propagation.</li>
<li>Grace period comfortably larger than the sum of your timeouts.</li>
<li>Exec-form <code>CMD</code> in the Dockerfile.</li>
<li>A test that proves an in-flight request survives a <code>SIGTERM</code>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Graceful shutdown is one of those features nobody notices when it works — which is precisely the point. Twenty lines in <code>main</code>, a couple of timeouts that add up correctly, and a container that actually forwards signals will turn every deploy from a small burst of errors into a non-event. It is the cheapest reliability win available to a Go service, and worth adding before the next release rather than after the next incident.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
      <category>DevOps</category>
    </item>
    <item>
      <title>One of The Easiest Ways to Host your Go Web App</title>
      <link>https://webdevstation.com/posts/one-of-thee-easiest-ways-to-host-go-web-apps/</link>
      <pubDate>Tue, 05 Sep 2023 09:53:15 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/one-of-thee-easiest-ways-to-host-go-web-apps/</guid>
      <description>Discover how to host Go web applications for as little as $5 per month using DigitalOcean and Docker. Learn this cost-effective, scalable deployment method with…</description>
      <content:encoded><![CDATA[<p>Hello! In this post, I will explain the cost-effective method I use to host my Go web applications
with varying levels of complexity, all starting from as low as $5 per month. This method also allows to easy
deploy and scale your golang application.</p>
<p>As an example, this is how I host <a href="https://whattoreadafter.xyz" title="What to read after: AI book recommendations">whattoreadafter.xyz</a>, a service that recommends books based on the book you just finished reading, by using AI.</p>
<p>Starting off, let&rsquo;s enumerate the tools we&rsquo;ll be using alongside Golang:</p>
<ul>
<li><a href="https://m.do.co/c/2a29ebc23e4a">DigitalOcean</a> - a cloud computing platform that provides virtual machines and other resources.</li>
<li><a href="https://www.docker.com/">Docker</a> - a set of platform as a service products that use OS-level virtualization to deliver software in packages called containers.</li>
</ul>
<p>In order to follow along, you will need to have a DigitalOcean account. If you don&rsquo;t have one, you can
sign up <a href="https://m.do.co/c/2a29ebc23e4a">here</a> and get $200 in credit over 60 days.</p>
<p>Let&rsquo;s get started!
For simplicity sake, our application will be a simple web server that returns current time in UTC format.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">currentTimeHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">currentTime</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UTC</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;Current Time (UTC): %s&#34;</span>, <span style="color:#a6e22e">currentTime</span>.<span style="color:#a6e22e">Format</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">RFC3339</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">currentTimeHandler</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Server is running on port 8080&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now let&rsquo;s host it!</p>
<p>In order for you to easier follow along, I will list the steps we will take to host our application:</p>
<h2 id="1-prepare-a-dockerfile-for-our-application-and-place-it-in-the-root-of-our-project-alongside-the-maingo-file">1. Prepare a Dockerfile for our application and place it in the root of our project alongside the <code>main.go</code> file.</h2>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">golang:alpine</span> <span style="color:#66d9ef">AS</span> <span style="color:#e6db74">builder</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> apk add --no-cache --update <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        git <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        ca-certificates<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ADD</span> . /app<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">WORKDIR</span> <span style="color:#e6db74">/app</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">COPY</span> go.mod ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span>  go mod download<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> CGO_ENABLED<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> GOOS<span style="color:#f92672">=</span>linux GOARCH<span style="color:#f92672">=</span>amd64 go build -a -o /main .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">alpine</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">COPY</span> --from<span style="color:#f92672">=</span>builder /main ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> chmod +x ./main<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENTRYPOINT</span> [<span style="color:#e6db74">&#34;./main&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p> </p>
<h2 id="2-push-your-code-to-a-github-repository-you-can-make-it-private-if-you-want">2. Push your code to a GitHub repository. You can make it private if you want.</h2>
<p> </p>
<h2 id="3-go-to-digitalocean-and-create-a-new-app-you-can-do-so-by-clicking-on-the-apps-tab-in-the-left-sidebar-and-then-clicking-create-app">3. Go to <a href="https://cloud.digitalocean.com/apps">DigitalOcean</a> and create a new App. You can do so by clicking on the &ldquo;Apps&rdquo; tab in the left sidebar and then clicking &ldquo;Create App&rdquo;.</h2>
<p> </p>
<h2 id="4-select-github-as-your-source-and-click-continue">4. Select &ldquo;GitHub&rdquo; as your &ldquo;Source&rdquo; and click &ldquo;Continue&rdquo;.</h2>
<p><img src="/images/2023/do1.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="5-select-the-repository-you-want-to-deploy-and-click-next">5. Select the repository you want to deploy and click &ldquo;Next&rdquo;.</h2>
<p><img src="/images/2023/do2.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="6-select-dockerfile-as-your-build-type-and-click-next">6. Select &ldquo;Dockerfile&rdquo; as your &ldquo;Build type&rdquo; and click &ldquo;Next&rdquo;.</h2>
<p><img src="/images/2023/do3.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="7-configure-environment-variables-if-you-need-to-and-click-next">7. Configure environment variables if you need to and click &ldquo;Next&rdquo;.</h2>
<p> </p>
<h2 id="8-select-region-for-your-app-and-click-next">8. Select region for your app and click &ldquo;Next&rdquo;.</h2>
<p><img src="/images/2023/do4.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="9-in-the-billing-section-select-the-plan-you-want-to-use-you-can-start-with-the-5-per-month-plan-and-scale-up-later-if-you-need-to">9. In the billing section, select the plan you want to use. You can start with the $5 per month plan and scale up later if you need to.</h2>
<p><img src="/images/2023/do5.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="10-click-create-resources-and-wait-for-your-app-to-be-deployed">10. Click &ldquo;Create Resources&rdquo; and wait for your app to be deployed.</h2>
<p> </p>
<h2 id="11-once-your-app-is-deployed-you-can-access-it-by-clicking-on-the-live-app-link">11. Once your app is deployed, you can access it by clicking on the &ldquo;Live App&rdquo; link.</h2>
<p><img src="/images/2023/do6.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<p>That&rsquo;s it! You have successfully deployed your Golang application!</p>
<p>You can also add your own domain name to your app by clicking on the &ldquo;Settings&rdquo; tab and then clicking on &ldquo;Domains&rdquo;.</p>
<p>Now, every time you push a new commit to your repository, DigitalOcean will automatically build and deploy your application.
This is a great way to host your Golang applications, especially if you are just starting out and don&rsquo;t want to spend a lot of money on hosting.</p>
<p>Thank you for reading! If you have any questions, feel free to reach out to me on <a href="https://twitter.com/oleks_i">Twitter</a>.</p>
<p>Before you point real traffic at it, two things are worth having in place: <a href="/posts/graceful-shutdown-in-go-web-services/">graceful shutdown</a>, so redeploys stop dropping requests, and <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">a load test</a>, so you know what the box can actually take.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>DevOps</category>
      <category>Web Development</category>
    </item>
  </channel>
</rss>
