<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cloud Frontier</title>
    <description>The latest articles on DEV Community by Cloud Frontier (@cloudfrontier).</description>
    <link>https://dev.to/cloudfrontier</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4027561%2F801991d0-b81c-4a26-99b0-b703e1d60dee.png</url>
      <title>DEV Community: Cloud Frontier</title>
      <link>https://dev.to/cloudfrontier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloudfrontier"/>
    <language>en</language>
    <item>
      <title>Docker Images Without the Bloat</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Mon, 10 Aug 2026 08:01:15 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/docker-images-without-the-bloat-g7a</link>
      <guid>https://dev.to/cloudfrontier/docker-images-without-the-bloat-g7a</guid>
      <description>&lt;h2&gt;
  
  
  Stop Shipping the Kitchen Sink
&lt;/h2&gt;

&lt;p&gt;We've all been there: you build a Docker image for a simple Python script, and it's 800MB. You pull it on a slow connection and watch the progress bar crawl. The image contains compilers, headers, and a full OS package manager you'll never use. It's time to trim the fat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the Right Base
&lt;/h2&gt;

&lt;p&gt;Your base image sets the ceiling. &lt;code&gt;ubuntu:latest&lt;/code&gt; is a convenience, not a goal. For most apps, you can go much smaller.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alpine&lt;/code&gt; is tiny (around 5MB) and uses musl, but sometimes native modules need extra work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;debian:bookworm-slim&lt;/code&gt; is a middle ground: Glibc, but without the bloat.&lt;/li&gt;
&lt;li&gt;For Go or Rust, consider &lt;code&gt;scratch&lt;/code&gt; or &lt;code&gt;distroless&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a quick comparison for a simple Python app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="c"&gt;# ~120MB&lt;/span&gt;

&lt;span class="c"&gt;# python:3.12-alpine&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-alpine&lt;/span&gt;
&lt;span class="c"&gt;# ~50MB&lt;/span&gt;

&lt;span class="c"&gt;# python:3.12 (full)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12&lt;/span&gt;
&lt;span class="c"&gt;# ~1GB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always check the official images for slim or alpine variants. They exist for a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Stage Builds: The Killer Feature
&lt;/h2&gt;

&lt;p&gt;If you need compilers to build, don't ship them. Use a multi-stage build: compile in one stage, copy only the artifacts to a clean final stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&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.22&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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; myapp .

&lt;span class="c"&gt;# Final stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:latest&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; ca-certificates
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/myapp /usr/local/bin/myapp&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Go binary is static, so we copy it into a minimal Alpine. Final image size? Around 10MB. Compare that to shipping the Go toolchain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean Up Within a Layer
&lt;/h2&gt;

&lt;p&gt;If you're stuck with a single-stage build (legacy, or just quick), at least clean up in the same &lt;code&gt;RUN&lt;/code&gt; command. Each &lt;code&gt;RUN&lt;/code&gt; creates a layer, and files deleted in a later layer still exist in the previous one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get purge &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get autoremove &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; chain: this prevents intermediate layers from holding onto temporary files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use .dockerignore
&lt;/h2&gt;

&lt;p&gt;Your build context can be huge if you're not careful. A stray &lt;code&gt;node_modules&lt;/code&gt; or &lt;code&gt;.git&lt;/code&gt; folder gets sent to the daemon, slowing builds and bloating cache. Add a &lt;code&gt;.dockerignore&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;git&lt;/span&gt;
&lt;span class="n"&gt;node_modules&lt;/span&gt;
&lt;span class="err"&gt;__&lt;/span&gt;&lt;span class="n"&gt;pycache__&lt;/span&gt;
*.&lt;span class="n"&gt;md&lt;/span&gt;
.&lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Copy Specific Files
&lt;/h2&gt;

&lt;p&gt;Instead of &lt;code&gt;COPY . .&lt;/code&gt;, copy only what you need. This also helps with layer caching: if only your code changes, you don't invalidate the layer with dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Don't Install Unnecessary Packages
&lt;/h2&gt;

&lt;p&gt;That's obvious, but also think about package manager caches. &lt;code&gt;pip install&lt;/code&gt; leaves &lt;code&gt;.pyc&lt;/code&gt; files and caches. Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For npm, &lt;code&gt;npm ci --only=production&lt;/code&gt; skips devDependencies and uses the lockfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distroless and Scratch
&lt;/h2&gt;

&lt;p&gt;For production, consider &lt;code&gt;gcr.io/distroless&lt;/code&gt; images. They have no shell, no package manager, just your app and runtime. This reduces attack surface and size.&lt;/p&gt;

&lt;p&gt;If your app is fully static (Go, Rust), you can use &lt;code&gt;scratch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/myapp /myapp&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's as small as it gets: just the binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Your Layers
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;docker history&lt;/code&gt; to see what's taking space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;myimage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll spot layers that are unexpectedly large. Also, &lt;code&gt;docker images&lt;/code&gt; shows the total size, but remember that shared layers between images don't count twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Slimming Down a Python API
&lt;/h2&gt;

&lt;p&gt;Here's a before and after for a FastAPI app.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Size: ~1GB&lt;/p&gt;

&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin /usr/local/bin&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Size: ~150MB. The trick is copying only the installed packages, not the entire builder image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Bloat isn't just about download speed. Smaller images mean faster startups, less disk usage, and fewer security vulnerabilities. Start with a slim base, use multi-stage builds, and always clean up. Your future self (and your CI pipeline) will thank you.&lt;/p&gt;

&lt;p&gt;Happy trimming!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cutting Cloud Costs with a Few Habits</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Sat, 08 Aug 2026 08:00:28 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/cutting-cloud-costs-with-a-few-habits-4b3e</link>
      <guid>https://dev.to/cloudfrontier/cutting-cloud-costs-with-a-few-habits-4b3e</guid>
      <description>&lt;h2&gt;
  
  
  The Cloud Bill Isn't a Mystery
&lt;/h2&gt;

&lt;p&gt;Every month, the same shock. The cloud bill arrives, and it's higher than expected. I used to blame the provider, then my team, then the phase of the moon. But after years of cleaning up messes, I realized the problem isn't the cloud - it's our habits. A few small changes in how we deploy, monitor, and think about resources can cut costs dramatically without sacrificing performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habit 1: Right-Size Everything, Regularly
&lt;/h2&gt;

&lt;p&gt;Your instances are probably oversized. We tend to pick a size that feels safe, then never revisit it. I've seen production boxes running at 10% CPU for months. That's money burning for no reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The habit:&lt;/strong&gt; Every quarter, check your usage metrics. Look at CPU, memory, and network. If an instance has been under 20% utilization for 30 days, downgrade it. Automate this if you can - some providers offer rightsizing recommendations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick check with AWS CLI (example)&lt;/span&gt;
aws cloudwatch get-metric-statistics &lt;span class="nt"&gt;--namespace&lt;/span&gt; AWS/EC2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric-name&lt;/span&gt; CPUUtilization &lt;span class="nt"&gt;--dimensions&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;InstanceId,Value&lt;span class="o"&gt;=&lt;/span&gt;i-1234567890 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--start-time&lt;/span&gt; 2024-01-01T00:00:00Z &lt;span class="nt"&gt;--end-time&lt;/span&gt; 2024-01-31T00:00:00Z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--period&lt;/span&gt; 86400 &lt;span class="nt"&gt;--statistics&lt;/span&gt; Average
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using Kubernetes, look at &lt;code&gt;kubectl top nodes&lt;/code&gt; and adjust your resource requests. The goal is to match capacity to actual demand, not to your fear of a spike.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habit 2: Turn Off What You're Not Using
&lt;/h2&gt;

&lt;p&gt;Development and staging environments are the worst offenders. They run 24/7, but nobody touches them after 6 PM. I once found a test database that hadn't been queried in three months. It was costing $400 a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The habit:&lt;/strong&gt; Implement a schedule. Shut down non-production resources outside business hours. Use a simple cron job or a cloud function to stop instances at 7 PM and start them at 7 AM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: AWS Lambda with boto3 to stop instances tagged 'dev' after hours
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop_dev_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tag:Environment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Values&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dev&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Reservations&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Instances&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;State&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;InstanceId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stopped &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;InstanceId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it a policy: if a resource isn't used for a week, it gets deleted or stopped. You can always start it again later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habit 3: Use Managed Services Wisely
&lt;/h2&gt;

&lt;p&gt;Managed services like RDS, ElastiCache, or Cloud SQL are convenient, but they come with a premium. Sometimes that premium is worth it. Often, it isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The habit:&lt;/strong&gt; For every managed service, ask: "Can I run this myself with a small VM?" If the answer is yes and you have the operational capacity, consider self-hosting. For example, a small PostgreSQL database on a t3.micro might cost $15/month, while RDS for the same workload could be $30.&lt;/p&gt;

&lt;p&gt;But don't go overboard. If you have a team that can't handle patching, backups, and failover, the managed service is worth the extra cost. The key is to make a conscious choice, not a default one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habit 4: Set Budgets and Alerts
&lt;/h2&gt;

&lt;p&gt;You can't manage what you don't measure. I've seen teams get a $10,000 surprise because they never set up billing alerts. That's a painful way to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The habit:&lt;/strong&gt; Set a monthly budget for each project or environment. Configure alerts at 50%, 80%, and 100% of that budget. Most cloud providers have this built in. If you're on AWS, use Budgets; on GCP, use Budgets and Alerts; on Azure, use Cost Management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AWS CLI example: create a budget alert&lt;/span&gt;
aws budgets create-budget &lt;span class="nt"&gt;--account-id&lt;/span&gt; 123456789012 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--budget&lt;/span&gt; &lt;span class="s1"&gt;'{"BudgetName":"monthly-prod","BudgetLimit":{"Amount":"5000","Unit":"USD"},"TimeUnit":"MONTHLY","BudgetType":"COST"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--notifications&lt;/span&gt; &lt;span class="s1"&gt;'[{"NotificationType":"ACTUAL","ComparisonOperator":"GREATER_THAN","Threshold":80,"ThresholdType":"PERCENTAGE","NotificationState":"ALARM"}]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an alert fires, don't ignore it. Investigate immediately. Usually, it's a runaway resource or a forgotten instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habit 5: Review Your Storage and Snapshots
&lt;/h2&gt;

&lt;p&gt;Storage is sneaky. You take a snapshot once, and then it grows forever. Old backups pile up. Orphaned volumes sit there charging you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The habit:&lt;/strong&gt; Once a month, list all your storage resources. Delete snapshots older than 30 days unless you have a compliance reason to keep them. Remove unattached volumes. Use lifecycle policies to automate this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List unattached EBS volumes (AWS example)&lt;/span&gt;
aws ec2 describe-volumes &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;status,Values&lt;span class="o"&gt;=&lt;/span&gt;available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For object storage like S3, enable lifecycle rules to transition old files to cheaper tiers (like Glacier) or delete them after a set period.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Bad Habits
&lt;/h2&gt;

&lt;p&gt;These aren't one-time fixes. They're habits. The first month you'll save a little. The second month, more. Over a year, you'll be surprised at how much you've cut. But the real benefit is the mindset shift: you start thinking about cost as a first-class citizen, not an afterthought.&lt;/p&gt;

&lt;p&gt;I've seen teams cut their cloud bill by 40% just by doing these five things consistently. No magic, no expensive software. Just habits.&lt;/p&gt;

&lt;p&gt;Start with one. Pick the one that hurts the most, and make it a routine. Your future self (and your finance team) will thank you.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>costoptimization</category>
      <category>aws</category>
    </item>
    <item>
      <title>AWS S3 basics every dev should know</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:05:15 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/aws-s3-basics-every-dev-should-know-1lfh</link>
      <guid>https://dev.to/cloudfrontier/aws-s3-basics-every-dev-should-know-1lfh</guid>
      <description>&lt;h2&gt;
  
  
  Why S3 matters
&lt;/h2&gt;

&lt;p&gt;Amazon S3 is the object storage service that powers a huge part of the internet. If you build anything that stores files, serves static assets, or handles uploads, you'll likely touch S3. It's simple in theory: buckets hold objects (files), and each object has a key (path). But the details matter, especially around permissions, consistency, and cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buckets and keys
&lt;/h2&gt;

&lt;p&gt;A bucket is a global namespace. Bucket names must be unique across all AWS accounts, so you can't just name yours &lt;code&gt;uploads&lt;/code&gt;. Use a prefix like &lt;code&gt;mycompany-uploads&lt;/code&gt;. Keys are just strings; they can contain slashes to mimic folders, but S3 doesn't really have folders. That's why listing objects with a prefix is the way to "browse" a directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://mycompany-uploads/images/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That lists all objects whose key starts with &lt;code&gt;images/&lt;/code&gt;. Empty "folders" don't exist unless you create a zero-byte object with a trailing slash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions are the hard part
&lt;/h2&gt;

&lt;p&gt;By default, everything is private. You control access with IAM policies for users/roles, bucket policies for cross-account or public access, and ACLs (legacy, avoid). The most common mistake is making a bucket public when you only need a few objects public. Instead, use a bucket policy to allow &lt;code&gt;s3:GetObject&lt;/code&gt; on a specific prefix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::mycompany-uploads/public/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For uploads from a web app, never give the client full bucket access. Use presigned URLs: generate a temporary URL that allows a specific PUT or GET for a limited time. Your backend signs the request, so you control size, expiration, and path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;put_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mycompany-uploads&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user-123/photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Consistency: strong now
&lt;/h2&gt;

&lt;p&gt;S3 used to have eventual consistency for reads after writes, but since 2020 it's strongly consistent for all operations. That means you can write an object and immediately read it, list it, or overwrite it. No more "wait a second" hacks. This simplifies a lot of logic, but still be careful with concurrent writes: last write wins, so use versioning if you need history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage classes and lifecycle
&lt;/h2&gt;

&lt;p&gt;Not all data needs the same durability or access speed. S3 has several classes: Standard for frequent access, Intelligent-Tiering for unknown patterns, Glacier for archives. You can set lifecycle rules to transition objects automatically. For example, move logs to Standard-IA after 30 days, then to Glacier after a year.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ArchiveLogs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logs/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Transitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STANDARD_IA"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GLACIER"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves money without manual effort. But beware: retrieving from Glacier takes minutes to hours, so don't put hot data there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning and deletion
&lt;/h2&gt;

&lt;p&gt;Enable versioning on buckets that hold important data. It protects against accidental overwrites and deletes. When you delete an object, S3 adds a delete marker instead of removing the data. You can always recover previous versions. This is a lifesaver for production.&lt;/p&gt;

&lt;p&gt;However, versioning doubles your storage cost because every version is stored. Combine it with lifecycle rules to clean up old versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost gotchas
&lt;/h2&gt;

&lt;p&gt;S3 pricing seems cheap, but it adds up. The main costs are storage, requests, and data transfer. Request costs are per 1000 operations, so high-frequency small reads can be more expensive than the storage itself. Use CloudFront as a CDN in front of S3 to reduce requests and data transfer costs. Also, avoid listing buckets frequently in code; that's a request-heavy operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Block public access unless absolutely necessary.&lt;/li&gt;
&lt;li&gt;Enable server-side encryption (SSE-S3 is free, or use KMS for more control).&lt;/li&gt;
&lt;li&gt;Use bucket policies with least privilege.&lt;/li&gt;
&lt;li&gt;Enable access logging to track who does what.&lt;/li&gt;
&lt;li&gt;Never put secrets in object keys or metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;aws s3 sync&lt;/code&gt; for uploading local folders; it only transfers changed files.&lt;/li&gt;
&lt;li&gt;For large files, use multipart uploads. The SDKs do this automatically for files over a threshold.&lt;/li&gt;
&lt;li&gt;When serving static websites, enable static website hosting on the bucket, but remember that requires public read access.&lt;/li&gt;
&lt;li&gt;Use S3 Select to query CSV/JSON files without downloading them, but it's rarely worth it unless you have huge files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;S3 is a workhorse. Once you understand the core concepts, you can build reliable and cost-effective storage. Start with a private bucket, use presigned URLs for uploads, and enable versioning. That covers most use cases. The rest is learning by doing.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Docker Images Without the Bloat</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Mon, 03 Aug 2026 00:01:23 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/docker-images-without-the-bloat-an</link>
      <guid>https://dev.to/cloudfrontier/docker-images-without-the-bloat-an</guid>
      <description>&lt;h2&gt;
  
  
  The Problem with Fat Images
&lt;/h2&gt;

&lt;p&gt;We've all been there: you pull an image, run &lt;code&gt;docker images&lt;/code&gt;, and see a 1.2GB monster staring back at you. That's not just disk space wasted; it's slower pulls, slower deploys, and a larger attack surface. The usual suspects? Base images with unnecessary tools, build dependencies left behind, and layers that contain temp files or caches.&lt;/p&gt;

&lt;p&gt;I've been guilty of shipping images that could have been 20x smaller. Over time, I've adopted a few techniques that make a real difference. Here's what actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Small: Choose the Right Base
&lt;/h2&gt;

&lt;p&gt;Your base image sets the floor. &lt;code&gt;ubuntu:latest&lt;/code&gt; is around 78MB, but &lt;code&gt;alpine:latest&lt;/code&gt; is under 5MB. If you're running a Go binary, you don't even need a full OS: &lt;code&gt;scratch&lt;/code&gt; is literally empty. For Python, consider &lt;code&gt;python:3.12-slim&lt;/code&gt; instead of &lt;code&gt;python:3.12&lt;/code&gt; (which includes compilers and headers you likely don't need at runtime).&lt;/p&gt;

&lt;p&gt;For example, a simple Python app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's already a fraction of the size of the full image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Stage Builds: The Game Changer
&lt;/h2&gt;

&lt;p&gt;If you need build tools, don't ship them. Multi-stage builds let you compile in one stage and copy only the artifacts to a clean final stage.&lt;/p&gt;

&lt;p&gt;Here's a Go example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&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.22-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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /app/myapp

&lt;span class="c"&gt;# Final stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/myapp /myapp&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final image is just the binary. For a Go app, that's typically 10-20MB. For a Node.js app, you can do the same: install dependencies in a builder, then copy &lt;code&gt;node_modules&lt;/code&gt; and your code to a slim runtime image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean Up in the Same Layer
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;RUN&lt;/code&gt; command creates a layer. If you install packages and then delete them in a separate &lt;code&gt;RUN&lt;/code&gt;, the deletion doesn't remove the data from the previous layer; it just adds a new layer on top. The size remains.&lt;/p&gt;

&lt;p&gt;Always combine cleanup in the same &lt;code&gt;RUN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get purge &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, use &lt;code&gt;--no-install-recommends&lt;/code&gt; for &lt;code&gt;apt&lt;/code&gt; and &lt;code&gt;--no-cache-dir&lt;/code&gt; for &lt;code&gt;pip&lt;/code&gt; to avoid pulling in extras.&lt;/p&gt;

&lt;h2&gt;
  
  
  .dockerignore: Stop Copying Junk
&lt;/h2&gt;

&lt;p&gt;If you're copying your entire project directory, you might be including &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, test files, or local caches. A minimal &lt;code&gt;.dockerignore&lt;/code&gt; can save megabytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.git
node_modules
__pycache__
*.pyc
.env
Dockerfile
.dockerignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also speeds up the build context transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Distroless Images
&lt;/h2&gt;

&lt;p&gt;If you need a runtime but not a shell, consider Google's distroless images. They contain only your runtime (e.g., Python, Node) and necessary libraries, no package manager or shell. This reduces attack surface and size. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/python3-debian12&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: you can't &lt;code&gt;docker exec&lt;/code&gt; into a distroless container (no shell), so debugging is trickier. That's a trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Your Layers
&lt;/h2&gt;

&lt;p&gt;After building, inspect your image with &lt;code&gt;docker history&lt;/code&gt; to see what's taking up space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;myimage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the size each layer added. If you see a huge layer for something you thought you cleaned up, you know you missed combining commands.&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;docker images&lt;/code&gt; shows the total size, but &lt;code&gt;docker system df&lt;/code&gt; gives a breakdown of all your images, containers, and build cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Realistic Example: Python API
&lt;/h2&gt;

&lt;p&gt;Let's put it together. A FastAPI app with a couple of dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/install &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /install /usr/local&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids installing build dependencies in the final image. You can even go distroless if you're comfortable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Shrinking images isn't just about aesthetics. Smaller images pull faster, start faster, and have fewer vulnerabilities. The techniques are straightforward: pick a slim base, use multi-stage builds, clean up in the same layer, and ignore unnecessary files.&lt;/p&gt;

&lt;p&gt;Next time you build an image, run &lt;code&gt;docker images&lt;/code&gt; and ask yourself: do I need all that? The answer is usually no.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Docker Images Without the Bloat</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Sat, 01 Aug 2026 00:01:00 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/docker-images-without-the-bloat-1mk7</link>
      <guid>https://dev.to/cloudfrontier/docker-images-without-the-bloat-1mk7</guid>
      <description>&lt;h2&gt;
  
  
  The Problem with Fat Images
&lt;/h2&gt;

&lt;p&gt;We've all been there: you pull a Docker image, and it's 1.2GB. You run a simple Node app, and the image is bigger than your entire OS. The culprit? Base images like &lt;code&gt;node:latest&lt;/code&gt; or &lt;code&gt;python:3.11&lt;/code&gt; come with a full OS, package managers, compilers, and a ton of stuff you don't need.&lt;/p&gt;

&lt;p&gt;But here's the thing: your container doesn't need a shell, a package manager, or even a full OS. It needs your binary and its runtime dependencies. That's it.&lt;/p&gt;

&lt;p&gt;In this article, I'll show you practical ways to slim down your Docker images, often by 90% or more, without sacrificing functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a Minimal Base
&lt;/h2&gt;

&lt;p&gt;Instead of &lt;code&gt;ubuntu&lt;/code&gt; or &lt;code&gt;debian&lt;/code&gt;, use &lt;code&gt;alpine&lt;/code&gt; or even better, &lt;code&gt;distroless&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alpine&lt;/strong&gt; is a tiny Linux distribution (~5MB) with musl libc. It's great for many apps, but you might hit issues with native modules that expect glibc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distroless&lt;/strong&gt; images are even more minimal. They contain only your runtime and dependencies, no package manager, no shell, no extra binaries. They are more secure because there's less attack surface.&lt;/p&gt;

&lt;p&gt;Example for a Python app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Runtime stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's already better, but we can go further with multi-stage builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Stage Builds: The Game Changer
&lt;/h2&gt;

&lt;p&gt;Multi-stage builds let you use one Dockerfile with multiple &lt;code&gt;FROM&lt;/code&gt; statements. You build in the first stage, copy only the artifacts to the final stage, and discard everything else.&lt;/p&gt;

&lt;p&gt;Classic example: compiling a Go binary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&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&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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; myapp .

&lt;span class="c"&gt;# Runtime stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/myapp /myapp&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scratch&lt;/code&gt; is an empty image. The final image is just your binary. For a Go app, that's often 10-20MB total.&lt;/p&gt;

&lt;p&gt;For interpreted languages, you can copy the installed dependencies and your source, but you still need the runtime. Distroless images are perfect for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18&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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/nodejs18-debian11&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice no &lt;code&gt;RUN apt-get&lt;/code&gt; or &lt;code&gt;npm install&lt;/code&gt; in the final stage. That kills a ton of bloat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean Up in the Same Layer
&lt;/h2&gt;

&lt;p&gt;If you must use a package manager, clean up in the same &lt;code&gt;RUN&lt;/code&gt; command to avoid leaving cached files in intermediate layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always remove apt lists and cache. For pip, use &lt;code&gt;--no-cache-dir&lt;/code&gt;. For npm, use &lt;code&gt;npm ci --only=production&lt;/code&gt; which doesn't install dev dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use .dockerignore
&lt;/h2&gt;

&lt;p&gt;This is often overlooked. Your Docker build context includes everything in the directory, including &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, and other junk. That not only makes the build slower but can accidentally copy unnecessary files into the image.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.dockerignore&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.git
npm-debug.log
Dockerfile*
.dockerignore
.gitignore
*.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the build context small and the image clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combine Layers
&lt;/h2&gt;

&lt;p&gt;Each &lt;code&gt;RUN&lt;/code&gt;, &lt;code&gt;COPY&lt;/code&gt;, and &lt;code&gt;ADD&lt;/code&gt; creates a layer. Layers are not compressed individually, so more layers mean more space. Combine related commands into one &lt;code&gt;RUN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Specific Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;python:3.11-slim&lt;/code&gt; as a base, and if you have native dependencies, use multi-stage to compile wheels in a full image and copy them over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11&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;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip wheel &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--no-deps&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /wheels &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /wheels /wheels&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; /wheels/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /wheels
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;node:18-alpine&lt;/code&gt; and prune dev dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check Your Image Size
&lt;/h2&gt;

&lt;p&gt;After building, inspect the layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;myimage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows you which layers are big. You can also use tools like &lt;code&gt;dive&lt;/code&gt; to interactively explore, but a quick &lt;code&gt;docker history&lt;/code&gt; helps spot obvious bloat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Slimming Docker images is not just about disk space. Smaller images pull faster, deploy faster, and have a smaller attack surface.&lt;/p&gt;

&lt;p&gt;Start by switching to a minimal base, use multi-stage builds, clean up package managers, and add a &lt;code&gt;.dockerignore&lt;/code&gt;. You'll be surprised how small your images can get.&lt;/p&gt;

&lt;p&gt;I recently cut a Python API image from 900MB to 120MB just by using &lt;code&gt;slim&lt;/code&gt; and cleaning up pip cache. And a Go service went from 800MB to 15MB with &lt;code&gt;scratch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Give it a try on your next project. Your CI pipeline and your deployment servers will thank you.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Getting Started with Infrastructure as Code Using Terraform</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Thu, 30 Jul 2026 08:00:55 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/getting-started-with-infrastructure-as-code-using-terraform-5hjm</link>
      <guid>https://dev.to/cloudfrontier/getting-started-with-infrastructure-as-code-using-terraform-5hjm</guid>
      <description>&lt;h2&gt;
  
  
  What is Infrastructure as Code?
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through machine-readable definition files, rather than manual processes. Instead of clicking around in a cloud console, you write code that describes your desired state. This brings version control, repeatability, and automation to infrastructure management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Terraform?
&lt;/h2&gt;

&lt;p&gt;Terraform by HashiCorp is one of the most popular IaC tools. It is cloud-agnostic, meaning you can use the same workflow to manage AWS, Azure, GCP, or even on-premise resources. Terraform uses a declarative language (HCL) where you define what you want, and Terraform figures out how to make it happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider&lt;/strong&gt;: A plugin that interfaces with a cloud or service API (e.g., AWS, Azure, Kubernetes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource&lt;/strong&gt;: A component of your infrastructure, like a virtual machine, a DNS record, or a database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt;: A file that maps the real-world resources to your configuration. Terraform uses this to track what it manages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt;: A dry run that shows what will be created, changed, or destroyed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply&lt;/strong&gt;: Executes the plan to make changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your First Terraform Configuration
&lt;/h2&gt;

&lt;p&gt;Let's create a simple configuration to deploy an AWS EC2 instance. First, install Terraform from the official website. Then, create a file named &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MyFirstInstance"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Terraform to use the AWS provider and create an EC2 instance with a specific AMI and instance type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialization and Planning
&lt;/h2&gt;

&lt;p&gt;Run these commands in the same directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This downloads the AWS provider plugin. Next, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a detailed output of what Terraform will do. It's safe to run this as many times as you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying the Configuration
&lt;/h2&gt;

&lt;p&gt;To actually create the instance, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will show the plan again and ask for confirmation. Type &lt;code&gt;yes&lt;/code&gt; and press Enter. After a few moments, your instance is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing State
&lt;/h2&gt;

&lt;p&gt;Terraform automatically creates a &lt;code&gt;terraform.tfstate&lt;/code&gt; file. This is sensitive because it contains IDs and possibly secrets. Never commit it to version control. Use remote state backends (like S3) for teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Destroying Resources
&lt;/h2&gt;

&lt;p&gt;When you're done, tear everything down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the biggest advantages of IaC: you can cleanly remove all resources without manual cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables and Outputs
&lt;/h2&gt;

&lt;p&gt;Hardcoding values isn't ideal. Use variables to make your configuration reusable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MyInstance"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can set variables via a &lt;code&gt;terraform.tfvars&lt;/code&gt; file or command-line flags.&lt;/p&gt;

&lt;p&gt;Outputs let you extract information after apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"public_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Modules: Organizing Your Code
&lt;/h2&gt;

&lt;p&gt;As your infrastructure grows, break your configuration into modules. A module is a directory with Terraform files. For example, create a &lt;code&gt;modules/ec2&lt;/code&gt; folder with its own &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;variables.tf&lt;/code&gt;, and &lt;code&gt;outputs.tf&lt;/code&gt;. Then use it from your root configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"web_server"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/ec2"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"WebServer"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use version control&lt;/strong&gt;: Keep all &lt;code&gt;.tf&lt;/code&gt; files in Git.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock provider versions&lt;/strong&gt;: Specify versions in &lt;code&gt;required_providers&lt;/code&gt; to avoid unexpected changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use workspaces&lt;/strong&gt;: They let you manage multiple environments (dev, staging, prod) with the same configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run plan before apply&lt;/strong&gt;: Always review the plan to avoid surprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store state remotely&lt;/strong&gt;: Use Terraform Cloud, S3, or Azure Storage to share state with your team.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Terraform transforms infrastructure management from a manual, error-prone process into a repeatable, version-controlled workflow. Start small: define a single resource, then gradually expand. The learning curve is gentle, and the payoff is huge. Give it a try on your next project.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructure</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Infrastructure as Code with Terraform: The Basics You Need to Know</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Mon, 27 Jul 2026 00:00:48 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/infrastructure-as-code-with-terraform-the-basics-you-need-to-know-5g11</link>
      <guid>https://dev.to/cloudfrontier/infrastructure-as-code-with-terraform-the-basics-you-need-to-know-5g11</guid>
      <description>&lt;h2&gt;
  
  
  What is Infrastructure as Code?
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure (servers, networks, databases) through machine-readable definition files, rather than manual processes or interactive configuration tools. The benefits are huge: repeatability, version control, auditability, and speed.&lt;/p&gt;

&lt;p&gt;Terraform by HashiCorp is one of the most popular IaC tools. It's cloud-agnostic, meaning you can use the same workflow to manage AWS, Azure, GCP, or even on-prem resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Declarative Configuration
&lt;/h3&gt;

&lt;p&gt;You describe the &lt;strong&gt;desired state&lt;/strong&gt; of your infrastructure in &lt;code&gt;.tf&lt;/code&gt; files. Terraform figures out what to create, update, or delete to reach that state.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Providers
&lt;/h3&gt;

&lt;p&gt;Providers are plugins that let Terraform interact with cloud APIs. For example, the AWS provider knows how to create EC2 instances, S3 buckets, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Resources
&lt;/h3&gt;

&lt;p&gt;Resources are the actual infrastructure components you manage. Each resource block has a type (e.g., &lt;code&gt;aws_instance&lt;/code&gt;) and a local name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_server"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. State
&lt;/h3&gt;

&lt;p&gt;Terraform keeps a state file (&lt;code&gt;terraform.tfstate&lt;/code&gt;) that maps your config to real-world resources. It's critical for tracking what's deployed. Never edit it manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your First Terraform Project
&lt;/h2&gt;

&lt;p&gt;Let's walk through a minimal example: deploying an AWS EC2 instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install Terraform
&lt;/h3&gt;

&lt;p&gt;Download from &lt;a href="https://www.terraform.io/downloads" rel="noopener noreferrer"&gt;terraform.io/downloads&lt;/a&gt; and add to your PATH.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a project directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;learn-terraform
&lt;span class="nb"&gt;cd &lt;/span&gt;learn-terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Write &lt;code&gt;main.tf&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TerraformExample"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Initialize
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This downloads the AWS provider plugin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Plan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows what will be created. Review it carefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Apply
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; to confirm. Terraform creates the EC2 instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Destroy (when done)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleans up all resources defined in your config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables and Outputs
&lt;/h2&gt;

&lt;p&gt;Hardcoding values is bad practice. Use variables to make configs reusable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_type"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs let you expose useful information after apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use remote state&lt;/strong&gt;: Store &lt;code&gt;terraform.tfstate&lt;/code&gt; in a shared backend like S3 (with DynamoDB for locking). Never commit state to Git.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organize modules&lt;/strong&gt;: Break large configs into reusable modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control everything&lt;/strong&gt;: All &lt;code&gt;.tf&lt;/code&gt; files belong in Git.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;terraform fmt&lt;/code&gt;&lt;/strong&gt;: Auto-format your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate with &lt;code&gt;terraform validate&lt;/code&gt;&lt;/strong&gt;: Catch syntax errors early.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Terraform makes infrastructure management systematic and reproducible. Start small, learn the workflow (init, plan, apply, destroy), and gradually adopt more advanced features like modules, workspaces, and provisioning. The official &lt;a href="https://developer.hashicorp.com/terraform/docs" rel="noopener noreferrer"&gt;Terraform documentation&lt;/a&gt; is excellent when you're ready to go deeper.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructure</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Getting Started with Infrastructure as Code Using Terraform</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Fri, 24 Jul 2026 16:01:49 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/getting-started-with-infrastructure-as-code-using-terraform-3ppo</link>
      <guid>https://dev.to/cloudfrontier/getting-started-with-infrastructure-as-code-using-terraform-3ppo</guid>
      <description>&lt;h2&gt;
  
  
  What is Infrastructure as Code?
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code (IaC) lets you manage your cloud resources with configuration files instead of clicking around a web console. It is repeatable, versionable, and less error prone. Terraform by HashiCorp is one of the most popular IaC tools because it is cloud agnostic and uses a declarative language called HCL (HashiCorp Configuration Language).&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Terraform
&lt;/h2&gt;

&lt;p&gt;Terraform is a single binary. Download the appropriate package for your OS from the official site, unzip it, and ensure the &lt;code&gt;terraform&lt;/code&gt; binary is in your PATH. Verify with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Your First Terraform Configuration
&lt;/h2&gt;

&lt;p&gt;Create a directory for your project and a file named &lt;code&gt;main.tf&lt;/code&gt;. Here is a minimal example that provisions an AWS EC2 instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ExampleInstance"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration tells Terraform to use the AWS provider, set the region, and create an EC2 instance with a specific AMI and instance type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Terraform Workflow
&lt;/h2&gt;

&lt;p&gt;Terraform follows a simple three step workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Init&lt;/strong&gt; - Initialize the working directory and download the required providers.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; - Preview the changes Terraform will make without actually applying them.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Apply&lt;/strong&gt; - Execute the changes to create or update resources.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running &lt;code&gt;apply&lt;/code&gt;, Terraform will prompt you to confirm. Type &lt;code&gt;yes&lt;/code&gt; to proceed. When you are done, you can destroy the resources with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  State Management
&lt;/h2&gt;

&lt;p&gt;Terraform keeps track of the resources it manages in a state file (by default &lt;code&gt;terraform.tfstate&lt;/code&gt;). This file is crucial; never edit it manually. For team use, store state remotely (e.g., in an S3 bucket with DynamoDB locking) to avoid conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables and Outputs
&lt;/h2&gt;

&lt;p&gt;Hardcoding values is bad practice. Use variables to make your configuration reusable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name tag for the instance"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MyInstance"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c55b159cbfafe1f0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And outputs to display useful information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass variable values via a &lt;code&gt;terraform.tfvars&lt;/code&gt; file or the &lt;code&gt;-var&lt;/code&gt; flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules for Reusability
&lt;/h2&gt;

&lt;p&gt;Modules are self contained packages of Terraform configurations. You can call a module from a registry or your own local directory. For example, to use the VPC module from the Terraform Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/vpc/aws"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-vpc"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;

  &lt;span class="nx"&gt;azs&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1b"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.101.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.102.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_nat_gateway&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Terraform&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use version control for your Terraform files.&lt;/li&gt;
&lt;li&gt;Format your code with &lt;code&gt;terraform fmt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Validate configurations with &lt;code&gt;terraform validate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep secrets out of code; use environment variables or a secrets manager.&lt;/li&gt;
&lt;li&gt;Use remote state with locking for team collaboration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Terraform gives you a powerful, declarative way to manage infrastructure across multiple cloud providers. Start small, learn the workflow, and gradually adopt modules and remote state. The time you invest in IaC pays off in consistency, auditability, and speed.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructure</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Serverless: When It Helps and When It Hurts</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Thu, 23 Jul 2026 00:01:02 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/serverless-when-it-helps-and-when-it-hurts-60g</link>
      <guid>https://dev.to/cloudfrontier/serverless-when-it-helps-and-when-it-hurts-60g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Serverless computing has become a buzzword in cloud architecture. But like any tool, it has sweet spots and sharp edges. After building and maintaining several serverless applications, I've learned where it shines and where it creates headaches. This article shares those lessons.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Serverless Helps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Event-Driven Workloads
&lt;/h3&gt;

&lt;p&gt;Serverless excels when your code runs in response to events: file uploads, database changes, HTTP requests. The pay-per-execution model means you don't pay for idle time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AWS Lambda handler for image resizing&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// resize and save&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Variable or Unpredictable Traffic
&lt;/h3&gt;

&lt;p&gt;If your app has occasional spikes (e.g., a marketing campaign), serverless auto-scales instantly. No need to provision for peak load.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rapid Prototyping and MVPs
&lt;/h3&gt;

&lt;p&gt;You can deploy a fully functional API in minutes without managing servers. This accelerates feedback loops.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Microservices and Glue Code
&lt;/h3&gt;

&lt;p&gt;Serverless functions are perfect for small, single-purpose services that connect other services (e.g., processing webhooks, data transformation).&lt;/p&gt;

&lt;h2&gt;
  
  
  When Serverless Hurts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Long-Running Processes
&lt;/h3&gt;

&lt;p&gt;Most providers have a maximum execution timeout (e.g., 15 minutes for AWS Lambda). Batch processing or video transcoding may hit this limit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This will timeout if processing takes &amp;gt; 15 minutes
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;process_large_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Cold Starts
&lt;/h3&gt;

&lt;p&gt;After a period of inactivity, the first request may have a delay of several seconds. This is detrimental for latency-sensitive applications like synchronous APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Stateful Applications
&lt;/h3&gt;

&lt;p&gt;Serverless is stateless by design. If you need persistent connections (e.g., WebSockets) or local state, you'll need additional services like Redis or DynamoDB, adding complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. High, Steady Load
&lt;/h3&gt;

&lt;p&gt;If your service runs 24/7 with constant traffic, provisioned servers are often cheaper. Serverless per-invocation cost can exceed a fixed monthly server cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Complex Debugging and Testing
&lt;/h3&gt;

&lt;p&gt;Local emulation (e.g., SAM, serverless-offline) helps but never fully replicates the production environment. Debugging distributed traces across functions can be painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Guidance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Serverless When:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your workload is event-driven or intermittent&lt;/li&gt;
&lt;li&gt;You want to minimize operational overhead&lt;/li&gt;
&lt;li&gt;You need rapid scaling from zero&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Avoid Serverless When:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have long-running or stateful processes&lt;/li&gt;
&lt;li&gt;You need consistent low latency (cold starts hurt)&lt;/li&gt;
&lt;li&gt;Your traffic is constant and high volume&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Serverless is a powerful paradigm, but not a silver bullet. Evaluate your workload's characteristics before adopting it. When used appropriately, it reduces complexity and cost. When misapplied, it creates new problems. Choose wisely.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>architecture</category>
      <category>aws</category>
    </item>
    <item>
      <title>The Biggest Myth About Cloud Computing</title>
      <dc:creator>Cloud Frontier</dc:creator>
      <pubDate>Mon, 13 Jul 2026 16:33:58 +0000</pubDate>
      <link>https://dev.to/cloudfrontier/the-biggest-myth-about-cloud-computing-49nc</link>
      <guid>https://dev.to/cloudfrontier/the-biggest-myth-about-cloud-computing-49nc</guid>
      <description>&lt;h1&gt;
  
  
  The Cloud Didn't Make Infrastructure Easier
&lt;/h1&gt;

&lt;p&gt;It made it programmable.&lt;/p&gt;

&lt;p&gt;Years ago, provisioning infrastructure could take days—or even weeks.&lt;/p&gt;

&lt;p&gt;Today, you can spin up an entire environment with a single command.&lt;/p&gt;

&lt;p&gt;Sounds easier, right?&lt;/p&gt;

&lt;p&gt;Not always.&lt;/p&gt;

&lt;p&gt;Now we have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;li&gt;IAM permissions&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Secrets management&lt;/li&gt;
&lt;li&gt;Monitoring&lt;/li&gt;
&lt;li&gt;Cost optimization&lt;/li&gt;
&lt;li&gt;Disaster recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cloud didn't remove complexity.&lt;/p&gt;

&lt;p&gt;It shifted it.&lt;/p&gt;

&lt;p&gt;The good news?&lt;/p&gt;

&lt;p&gt;Once your infrastructure becomes code, you gain something traditional infrastructure never offered:&lt;/p&gt;

&lt;p&gt;Version control.&lt;br&gt;
Automation.&lt;br&gt;
Repeatability.&lt;br&gt;
Confidence.&lt;/p&gt;

&lt;p&gt;That's why learning cloud isn't just about learning AWS, Azure, or GCP.&lt;/p&gt;

&lt;p&gt;It's about learning how modern systems are built, automated, and operated.&lt;/p&gt;

&lt;p&gt;The cloud is just the platform.&lt;/p&gt;

&lt;p&gt;Engineering is still the real skill.&lt;/p&gt;

&lt;p&gt;What's one cloud lesson you wish someone had taught you sooner?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Title:&lt;/strong&gt; The Biggest Myth About Cloud Computing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;cloud&lt;/code&gt; &lt;code&gt;aws&lt;/code&gt; &lt;code&gt;devops&lt;/code&gt; &lt;code&gt;softwareengineering&lt;/code&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>aws</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
