<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Aws Ecs on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/tags/aws-ecs/</link><description>Recent content in Aws Ecs on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 22 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/tags/aws-ecs/index.xml" rel="self" type="application/rss+xml"/><item><title>Go Production Deployment — AWS ECS vs Cloud Run vs Fly.io</title><link>https://ice-ice-bear.github.io/posts/2026-04-22-go-deployment-full-course/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/posts/2026-04-22-go-deployment-full-course/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Go Production Deployment — AWS ECS vs Cloud Run vs Fly.io" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;The wikidocs.net Korean book &lt;strong&gt;&amp;ldquo;소설처럼 읽는 Go 언어&amp;rdquo;&lt;/strong&gt; has a deployment section that walks through three paths for putting a Go binary on the public internet — &lt;strong&gt;AWS ECS&lt;/strong&gt;, &lt;strong&gt;Google Cloud Run&lt;/strong&gt;, and &lt;strong&gt;Fly.io&lt;/strong&gt; — plus domain connection and performance optimization. The chapters themselves are short, but the pattern they reveal is worth a longer treatment. Here is the decision tree those five chapters encode, with the trade-offs each path actually makes.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;graph TD
 Start["Go binary ready&lt;br/&gt;go build ."] --&gt; Q1{"How much platform&lt;br/&gt;do you want?"}
 Q1 --&gt;|"Infra control + deep AWS"| ECS["AWS ECS&lt;br/&gt;Fargate/EC2&lt;br/&gt;- ALB, IAM, VPC&lt;br/&gt;- complex but capable"]
 Q1 --&gt;|"Simple HTTP + auto-scale"| CR["Google Cloud Run&lt;br/&gt;- container → URL&lt;br/&gt;- scales to 0&lt;br/&gt;- per-request billing"]
 Q1 --&gt;|"Single binary + ops-free"| Fly["Fly.io&lt;br/&gt;- Dockerfile or builder&lt;br/&gt;- per-VM pricing&lt;br/&gt;- global regions"]
 ECS --&gt; Domain
 CR --&gt; Domain
 Fly --&gt; Domain
 Domain["Chapter 04: Domain&lt;br/&gt;- DNS A/AAAA or CNAME&lt;br/&gt;- ACM (AWS) / managed cert (others)"] --&gt; Perf
 Perf["Chapter 12: Performance&lt;br/&gt;- profiling&lt;br/&gt;- connection pooling&lt;br/&gt;- GC tuning"]&lt;/pre&gt;&lt;h2 id="chapter-01-aws-ecs"&gt;Chapter 01: AWS ECS
&lt;/h2&gt;&lt;p&gt;ECS is the &amp;ldquo;you already live in AWS&amp;rdquo; answer. The workflow looks like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build the Go binary inside a multi-stage Docker image.&lt;/li&gt;
&lt;li&gt;Push to ECR.&lt;/li&gt;
&lt;li&gt;Define a Task Definition (CPU/RAM, container image, env, logging to CloudWatch).&lt;/li&gt;
&lt;li&gt;Create a Service on a Cluster (Fargate if you want serverless containers; EC2 if you want to manage the host).&lt;/li&gt;
&lt;li&gt;Put an ALB in front; set up target groups, health checks, and a Route 53 record.&lt;/li&gt;
&lt;li&gt;Add IAM policies so the task can read from S3, Secrets Manager, etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What ECS gives you: &lt;strong&gt;deep integration with the rest of AWS.&lt;/strong&gt; If your app needs to read from DynamoDB, publish to SNS, consume from SQS, assume a role to access another account&amp;rsquo;s S3 bucket — ECS makes all of that clean because everything speaks IAM. What it costs you: a multi-hour first-time setup, a VPC + subnets + security groups you need to understand, and a pager that goes off when ALB health checks and the container start-up sequence disagree.&lt;/p&gt;
&lt;h2 id="chapter-02-google-cloud-run"&gt;Chapter 02: Google Cloud Run
&lt;/h2&gt;&lt;p&gt;Cloud Run is ECS&amp;rsquo;s opposite number. You hand it a container image (or even just a source directory and a &lt;code&gt;Dockerfile&lt;/code&gt;) and it returns a URL. The service:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scales from 0 to many instances on demand.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bills per 100ms of request time&lt;/strong&gt; — if no requests, zero cost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic HTTPS&lt;/strong&gt; on the provided &lt;code&gt;run.app&lt;/code&gt; URL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No load balancer configuration&lt;/strong&gt; required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Go deployment shape on Cloud Run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.21-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; . .&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; go build -o main .&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.18&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;build /app/main /main&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then &lt;code&gt;gcloud run deploy --source .&lt;/code&gt; and you&amp;rsquo;re live.&lt;/p&gt;
&lt;p&gt;Cloud Run&amp;rsquo;s catch: &lt;strong&gt;cold starts.&lt;/strong&gt; Scaling to 0 means the first request after idle pays a startup cost. For a Go binary this is usually under a second, which is fine for most workloads — but if you care about tail latency, set &lt;code&gt;min-instances: 1&lt;/code&gt; and accept the bill.&lt;/p&gt;
&lt;h2 id="chapter-03-flyio"&gt;Chapter 03: Fly.io
&lt;/h2&gt;&lt;p&gt;Fly is the third path, and &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-22-fly-migration-economics/" &gt;covered in more depth separately&lt;/a&gt;. The Go + Fly shape:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fly launch&lt;/code&gt; — generates &lt;code&gt;fly.toml&lt;/code&gt; from your Dockerfile.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fly deploy&lt;/code&gt; — builds via Fly&amp;rsquo;s remote builder and deploys to your chosen region.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fly certs add yourdomain.com&lt;/code&gt; — adds a custom domain with automatic Let&amp;rsquo;s Encrypt.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Against ECS, Fly wins on setup simplicity. Against Cloud Run, Fly wins when you need &lt;strong&gt;a small always-on footprint&lt;/strong&gt; (Cloud Run&amp;rsquo;s scale-to-zero is great for bursty; Fly&amp;rsquo;s $2/VM/month is great for steady low-volume).&lt;/p&gt;
&lt;h2 id="chapter-04-domain-connection"&gt;Chapter 04: Domain Connection
&lt;/h2&gt;&lt;p&gt;The generic pattern across all three:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A record&lt;/strong&gt; pointing to a stable IPv4 (ECS via ALB DNS; Fly via allocated IP; Cloud Run via Google-managed domain mapping).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AAAA record&lt;/strong&gt; for IPv6 where available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS certificate&lt;/strong&gt; — ACM on AWS (automatic with ALB), Google-managed on Cloud Run, Let&amp;rsquo;s Encrypt via Fly&amp;rsquo;s &lt;code&gt;fly certs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The quiet advice: &lt;strong&gt;do not run your domain through a single registrar + nameserver setup you can&amp;rsquo;t replicate.&lt;/strong&gt; Use a DNS provider (Cloudflare, Route 53, Gandi) whose records you can export as a zone file. This is the kind of detail that only matters once, when you need to migrate away from a provider you&amp;rsquo;ve grown to dislike.&lt;/p&gt;
&lt;h2 id="chapter-12-performance-optimization"&gt;Chapter 12: Performance Optimization
&lt;/h2&gt;&lt;p&gt;The wikidocs performance chapter collects the Go-specific optimizations worth caring about. The ones with the biggest return:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GOGC&lt;/code&gt; tuning.&lt;/strong&gt; The default 100 is fine for most workloads; set it higher (200, 400) if you have spare memory and want fewer GC pauses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection pool limits&lt;/strong&gt; on &lt;code&gt;database/sql&lt;/code&gt;. &lt;code&gt;SetMaxOpenConns&lt;/code&gt; and &lt;code&gt;SetMaxIdleConns&lt;/code&gt; are the two knobs that matter. Default of 0 (unlimited) bites under load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pprof&lt;/code&gt; endpoint&lt;/strong&gt; exposed on a separate port, protected by auth. 90% of Go performance problems are diagnosed in &lt;code&gt;pprof/heap&lt;/code&gt; and &lt;code&gt;pprof/goroutine&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured logging via &lt;code&gt;slog&lt;/code&gt;.&lt;/strong&gt; Faster than &lt;code&gt;log&lt;/code&gt; + &lt;code&gt;fmt.Sprintf&lt;/code&gt;, and the structured output plays better with CloudWatch / Cloud Logging / Grafana Loki.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;go:embed&lt;/code&gt;&lt;/strong&gt; for static assets — no CDN required for small-to-medium sites, and one fewer external dependency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="decision-framework"&gt;Decision Framework
&lt;/h2&gt;&lt;p&gt;The real utility of reading the five chapters together is the framework they suggest — a three-line decision tree:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Do you need deep AWS service integration?&lt;/strong&gt; → ECS. Otherwise, no.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is your traffic bursty with zero baseline?&lt;/strong&gt; → Cloud Run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Otherwise — small team, steady-ish traffic, don&amp;rsquo;t want to think about infra?&lt;/strong&gt; → Fly.io.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have not seen a Go production workload in the last year where ECS was clearly the right answer unless the project was already embedded in an AWS account full of DynamoDB tables and Lambda functions.&lt;/p&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;p&gt;The trend is obvious once you see the three side by side: &lt;strong&gt;the platforms have absorbed the ops work, and the only question left is how much platform you want.&lt;/strong&gt; ECS lets you customize everything and requires you to operate everything. Cloud Run gives you an HTTP URL in exchange for a container. Fly.io gives you a container + region + custom domain in exchange for a Dockerfile. A Go binary is small and boring in the best way — it plugs into all three. For most production Go workloads the honest recommendation is &amp;ldquo;Cloud Run for bursty, Fly for steady, ECS only if you already live there.&amp;rdquo; The performance chapter&amp;rsquo;s real message isn&amp;rsquo;t which optimization to apply first; it&amp;rsquo;s that Go is usually fast enough without any of them, and you should only start tuning after pprof points somewhere specific.&lt;/p&gt;</description></item></channel></rss>