<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Alessandro Marino's Tech Blog]]></title><description><![CDATA[A tech blog about Cloud, AWS, Containers, Serverless and more. Written by an AWS Community Builder.]]></description><link>https://alessandromarinoac.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 08 Sep 2026 11:53:11 GMT</lastBuildDate><atom:link href="https://alessandromarinoac.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Boosting Monolith Performance: Caching with OpenResty, SRCache, and Redis]]></title><description><![CDATA[Intro
When working on a monolith and traffic increases, you might decide to start caching to manage the new volume. If your system is modern or small, you have several caching strategies:

Handle the caching logic within the code by integrating somet...]]></description><link>https://alessandromarinoac.com/boosting-monolith-performance-caching-with-openresty-srcache-and-redis</link><guid isPermaLink="true">https://alessandromarinoac.com/boosting-monolith-performance-caching-with-openresty-srcache-and-redis</guid><category><![CDATA[nginx]]></category><category><![CDATA[Redis]]></category><category><![CDATA[caching]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Wed, 24 Dec 2025 10:50:45 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>When working on a monolith and traffic increases, you might decide to start caching to manage the new volume. If your system is modern or small, you have several caching strategies:</p>
<ol>
<li><p>Handle the caching logic within the code by integrating something like Redis directly into the app code.</p>
</li>
<li><p>Use an external cache, such as a CDN or reverse proxy.</p>
</li>
</ol>
<p>When the system is large and outdated, adding caching logic directly into the code might take months of work. So, you might opt for an external solution like a CDN.</p>
<p>A Content Delivery Network (CDN) such as Cloudflare or CloudFront is an easy solution in most cases. However, it might not suit your needs if:</p>
<ol>
<li><p>Your traffic is not public</p>
</li>
<li><p>You need highly consistent and fast cache invalidation</p>
</li>
<li><p>You have a write-heavy workload</p>
</li>
</ol>
<p>So, if a CDN doesn't suit your needs and you can't modify the application code to handle caching, your option is to use a CDN alternative like reverse proxy caching, such as Nginx proxy_cache or Varnish.</p>
<h3 id="heading-openresty">OpenResty</h3>
<p>OpenResty is a web server based on Nginx that can be extended using Lua code. Because it's built on Nginx, OpenResty is fast and reliable. Its ability to be customised with Lua code makes it extremely flexible.</p>
<p>OpenResty also includes a range of components and libraries, such as lua-resty-redis, lua-resty-mysql, and lua-resty-jwt.</p>
<h3 id="heading-srcache">SRCache</h3>
<p>SRCache is an Nginx module that provides a transparent caching layer for various Nginx locations. This module allows customisation of the storage backend used, so it can be combined with Redis.</p>
<p>Since it's an Nginx module, you don't need OpenResty to use SRCache. However, if you want to use Lua code within SRCache locations, you will need OpenResty.</p>
<p><a target="_blank" href="https://github.com/openresty/srcache-nginx-module"><img src="https://camo.githubusercontent.com/7211f61b1cd60582e70f43133976f22e462de318d735038d62d6cf4d3d2fe727/687474703a2f2f6167656e747a682e6f72672f6d6973632f696d6167652f737263616368652d666c6f7763686172742e706e67" alt="NGINX SRCache module workflow" /></a></p>
<p>An example of SRCache implementation using Redis, the example is from the <a target="_blank" href="https://github.com/openresty/srcache-nginx-module">SRCache github repository.</a></p>
<pre><code class="lang-nginx"> <span class="hljs-attribute">location</span> /api {
     <span class="hljs-attribute">default_type</span> text/css;

     <span class="hljs-attribute">set</span> <span class="hljs-variable">$key</span> <span class="hljs-variable">$uri</span>;
     <span class="hljs-attribute">set_escape_uri</span> <span class="hljs-variable">$escaped_key</span> <span class="hljs-variable">$key</span>;

     <span class="hljs-attribute">srcache_fetch</span> GET /redis <span class="hljs-variable">$key</span>;
     <span class="hljs-attribute">srcache_store</span> PUT /redis2 key=<span class="hljs-variable">$escaped_key</span>&amp;exptime=<span class="hljs-number">120</span>;

     <span class="hljs-comment"># fastcgi_pass/proxy_pass/drizzle_pass/postgres_pass/echo/etc</span>
 }

 <span class="hljs-attribute">location</span> = /redis {
     internal;

     <span class="hljs-attribute">set_md5</span> <span class="hljs-variable">$redis_key</span> <span class="hljs-variable">$args</span>;
     <span class="hljs-attribute">redis_pass</span> <span class="hljs-number">127.0.0.1:6379</span>;
 }

 <span class="hljs-attribute">location</span> = /redis2 {
     internal;

     <span class="hljs-attribute">set_unescape_uri</span> <span class="hljs-variable">$exptime</span> <span class="hljs-variable">$arg_exptime</span>;
     <span class="hljs-attribute">set_unescape_uri</span> <span class="hljs-variable">$key</span> <span class="hljs-variable">$arg_key</span>;
     <span class="hljs-attribute">set_md5</span> <span class="hljs-variable">$key</span>;

     <span class="hljs-attribute">redis2_query</span> set <span class="hljs-variable">$key</span> <span class="hljs-variable">$echo_request_body</span>;
     <span class="hljs-attribute">redis2_query</span> expire <span class="hljs-variable">$key</span> <span class="hljs-variable">$exptime</span>;
     <span class="hljs-attribute">redis2_pass</span> <span class="hljs-number">127.0.0.1:6379</span>;
 }
</code></pre>
<h3 id="heading-a-practical-example">A Practical Example</h3>
<p>This is an example of a complete solution where we aim to cache the generated invoices. We use the invoice ID as the caching key.</p>
<p>SRCache internal locations use the rediscluster library to access a Redis cluster for storing our cache. In this example, we use the Redis <code>get</code> command, but with Lua code, we could use more complex Redis commands like <code>hset</code>.</p>
<pre><code class="lang-nginx">
    <span class="hljs-attribute">location</span> <span class="hljs-regexp">~* ^/invoice/([0-9]+)(.pdf)?$</span> {

        <span class="hljs-attribute">set</span> <span class="hljs-variable">$key</span> <span class="hljs-string">"<span class="hljs-variable">$1</span>"</span>;
        <span class="hljs-attribute">set</span> <span class="hljs-variable">$ttl</span> <span class="hljs-number">432000</span>; 

        <span class="hljs-attribute">set</span> <span class="hljs-variable">$cache_skip</span> <span class="hljs-number">0</span>;
        <span class="hljs-attribute">srcache_store_skip</span> <span class="hljs-variable">$cache_skip</span>;
        <span class="hljs-attribute">srcache_store_statuses</span> <span class="hljs-number">200</span> <span class="hljs-number">201</span> <span class="hljs-number">304</span>;
        <span class="hljs-attribute">srcache_ignore_content_encoding</span> <span class="hljs-literal">on</span>;

        <span class="hljs-attribute">set_escape_uri</span> <span class="hljs-variable">$escaped_key</span> <span class="hljs-variable">$key</span>;

        <span class="hljs-attribute">srcache_fetch</span> GET /redis-fetch key=<span class="hljs-variable">$key</span>&amp;field=<span class="hljs-variable">$field</span>;
        <span class="hljs-attribute">srcache_store</span> PUT /redis-store key=<span class="hljs-variable">$key</span>&amp;field=<span class="hljs-variable">$field</span>&amp;ttl=<span class="hljs-variable">$ttl</span>;

        <span class="hljs-attribute">proxy_pass</span> http://backend;

        <span class="hljs-attribute">add_header</span> <span class="hljs-string">'L2-Cache-Fetch-Status'</span> <span class="hljs-variable">$srcache_fetch_status</span>; 
        <span class="hljs-attribute">add_header</span> <span class="hljs-string">'L2-Cache-Store-Status'</span> <span class="hljs-variable">$srcache_store_status</span>;
    }


    <span class="hljs-attribute">location</span> = /redis-fetch {
        internal;

        <span class="hljs-section">content_by_lua_block</span> {
            <span class="hljs-attribute">local</span> config = {
                ...
                <span class="hljs-attribute">REDIS_CONFIG_OPTIONS</span>
                ...
            }

            local redis_cluster = require <span class="hljs-string">"resty/rediscluster"</span>
            local red_c = assert(redis_cluster:new(config))

            local args,err = ngx.req.get_uri_args()
            local key = assert(args[<span class="hljs-string">"key"</span>], <span class="hljs-string">"no key found"</span>)
            local field = assert(args[<span class="hljs-string">"field"</span>], <span class="hljs-string">"no field found"</span>)
            ngx.log(ngx.INFO, key, field, err)  --Debug only

            local data = assert(red_c:get(key))
            if data == ngx.null then
                return ngx.exit(<span class="hljs-number">404</span>)
            end
            ngx.print(data)
        }
    }

    location = /redis-store {
        internal;

        <span class="hljs-section">content_by_lua_block</span> {
            <span class="hljs-attribute">local</span> config = {
                ...
                <span class="hljs-attribute">REDIS_CONFIG_OPTIONS</span>
                ...
            }

            local redis_cluster = require <span class="hljs-string">"resty/rediscluster"</span>
            local red_c = assert(redis_cluster:new(config))

            local args,err = ngx.req.get_uri_args()
            local key = assert(args[<span class="hljs-string">"key"</span>], <span class="hljs-string">"no key found"</span>)
            local ttl = assert(args[<span class="hljs-string">"ttl"</span>], <span class="hljs-string">"no ttl arg found"</span>)
            local value = assert(ngx.req.get_body_data(), <span class="hljs-string">"no value found"</span>)
            ngx.log(ngx.INFO, key, err)  --Debug only

            red_c:init_pipeline()
            red_c:set(key, value)
            red_c:expire(key,ttl,<span class="hljs-string">"NX"</span>)
            local data = assert(red_c:commit_pipeline())
        }
    }
</code></pre>
<h3 id="heading-purging-the-cache">Purging the cache</h3>
<p>What if we want to invalidate a single invoice from our example below?</p>
<pre><code class="lang-nginx">  <span class="hljs-attribute">location</span> <span class="hljs-regexp">~* ^/purge/([0-9]+)(.pdf)?$</span> {

        <span class="hljs-attribute">set</span> <span class="hljs-variable">$key</span> <span class="hljs-string">"<span class="hljs-variable">$1</span>"</span>;

        <span class="hljs-section">content_by_lua_block</span> {
            <span class="hljs-attribute">local</span> config = {
                ...
                <span class="hljs-attribute">REDIS_CONFIG_OPTIONS</span>
                ...
            }

            local redis_cluster = require <span class="hljs-string">"resty/rediscluster"</span>
            local red_c = assert(redis_cluster:new(config))

            local data = assert(red_c:del(ngx.var.key)) 
            return ngx.exit(<span class="hljs-number">204</span>)
        }
    }
</code></pre>
<p>I've kept these examples as simple as possible, but SRCache offers a lot of customisation. You can also explore other features like request coalescing, rate limiting, and more.</p>
<h3 id="heading-performances">Performances</h3>
<p>The solution in this example works very well because it uses two high-performing components: Redis and Nginx. I haven't done a full load test, but you can try it by setting up a simple stack with Docker.</p>
<hr />
<p>Do you need help implementing something like this? Contact me on LinkedIn<br /><a target="_blank" href="https://www.linkedin.com/in/alessandro-marino-ac/">https://www.linkedin.com/in/alessandro-marino-ac/</a></p>
]]></content:encoded></item><item><title><![CDATA[AWS CLI S3 Cheatsheet - s3 sync]]></title><description><![CDATA[The AWS CLI sync command is a powerful tool for copying new and updated files between a source and destination, with at least one of them being an S3 Bucket.
This command can also be used to perform sync between S3 Buckets without the need to transit...]]></description><link>https://alessandromarinoac.com/aws-cli-s3-cheatsheet-s3-sync</link><guid isPermaLink="true">https://alessandromarinoac.com/aws-cli-s3-cheatsheet-s3-sync</guid><category><![CDATA[AWS]]></category><category><![CDATA[S3]]></category><category><![CDATA[aws cli]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Fri, 17 Mar 2023 11:33:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679052808214/1fb76159-adac-49e5-b752-b4926c3c6345.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The AWS CLI <code>sync</code> command is a powerful tool for copying new and updated files between a source and destination, with at least one of them being an S3 Bucket.</p>
<p>This command can also be used to perform sync between S3 Buckets without the need to transit files on a local machine. In this article, we will explore the various options available with the <code>sync</code> command, including the exclude and include options, the delete option, and the dry run option. We will also discuss how to use a specific storage class when uploading files to S3.</p>
<pre><code class="lang-bash"><span class="hljs-comment">#From local to S3</span>
~ aws s3 sync . s3://my-test-bucket

<span class="hljs-comment">#From S3 to local</span>
~ aws s3 sync s3://my-test-bucket .

<span class="hljs-comment"># From S3 to S3</span>
~ aws s3 sync s3://my-test-bucket s3://my-copy-test-bucket
</code></pre>
<h1 id="heading-options">Options</h1>
<h2 id="heading-exclude-option">Exclude Option</h2>
<p>The <code>--exclude</code> option in the AWS CLI <code>sync</code> command can be used to exclude files or objects from the sync based on specific patterns. This option allows users to exclude files based on their file extension, file name, or path. Users can also use the <code>--exclude</code> option multiple times to exclude multiple patterns.</p>
<pre><code class="lang-bash">~ aws s3 sync . s3://my-test-bucket --exclude <span class="hljs-string">"*.jpg"</span>
</code></pre>
<p>Exclude a single large file:</p>
<pre><code class="lang-bash">~ aws s3 sync . s3://my-test-bucket --exclude <span class="hljs-string">"mydirectory/large-file.tar"</span>
</code></pre>
<h2 id="heading-include-option">Include Option</h2>
<p>In addition, the <code>--exclude</code> option can be used in combination with the <code>--include</code> option to fine-tune file selection. This allows users to include specific files or objects while excluding others based on specific patterns.</p>
<pre><code class="lang-bash">aws s3 sync . s3://my-test-bucket --exclude <span class="hljs-string">"*"</span> --include <span class="hljs-string">".png"</span> --include <span class="hljs-string">".jpg"</span>
</code></pre>
<h2 id="heading-delete-option">Delete Option</h2>
<p>The delete option in the AWS CLI <code>sync</code> command is used to remove files that exist in the destination but not in the source. This is a useful feature for keeping the destination in sync with the source and ensuring that outdated files are removed.</p>
<pre><code class="lang-bash">~ aws s3 sync --delete . s3://my-test-bucket
delete: s3://my-test-bucket/testfile1
</code></pre>
<h2 id="heading-dryrun-option">DryRun Option</h2>
<p>The <code>--dryrun</code> option in the AWS CLI <code>sync</code> command allows users to preview the operation that would be executed without actually running it. This is a useful feature for testing and verifying your command before running it to ensure that it will perform the desired action.</p>
<pre><code class="lang-bash">~ aws s3 sync --dryrun --delete . s3://my-test-bucket
(dryrun) delete: s3://my-test-bucket/testfile1
</code></pre>
<h2 id="heading-storage-class">Storage Class</h2>
<p>The storage class option in the AWS CLI <code>sync</code> command allows users to specify a specific storage class when uploading files to S3.</p>
<p>This can be useful in situations where certain files require a different storage class than the default. For example, if a user needs to store infrequently accessed files, they can use the <code>--storage-class</code> option to specify the <code>STANDARD_IA</code> class.</p>
<pre><code class="lang-bash">~ aws s3 sync --storage-class <span class="hljs-string">"STANDARD_IA"</span> . s3://my-test-bucket
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Deploy a React application on AWS with CloudFront and S3]]></title><description><![CDATA[Web applications built with React are commonly hosted and served as static files, so if you are building a Single Page Application and you want to host it on AWS you can use the combination of CloudFront and S3.
Introduction
Cloudfront is a Content D...]]></description><link>https://alessandromarinoac.com/deploy-a-react-application-on-aws-with-cloudfront-and-s3</link><guid isPermaLink="true">https://alessandromarinoac.com/deploy-a-react-application-on-aws-with-cloudfront-and-s3</guid><category><![CDATA[AWS]]></category><category><![CDATA[React]]></category><category><![CDATA[cloudfront]]></category><category><![CDATA[Single Page Application]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Thu, 02 Mar 2023 14:54:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677515009149/4acb1b02-cfd2-4466-833d-9f7c39ea9bd8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Web applications built with React are commonly hosted and served as static files, so if you are building a Single Page Application and you want to host it on AWS you can use the combination of CloudFront and S3.</p>
<h3 id="heading-introduction">Introduction</h3>
<p>Cloudfront is a Content Distribution Network (CDN), a service that allows you to host your content close to the final users worldwide with low latencies. To know more about CloudFront and CDNs <a target="_blank" href="https://alessandromarinoac.com/introduction-to-amazon-cloudfront">check my article</a>.</p>
<p>S3 is a robust and reliable object storage that will scale on demand and let you only pay for used resources, check it on the <a target="_blank" href="https://aws.amazon.com/s3/">AWS page</a>.</p>
<h1 id="heading-setup-react">Setup React</h1>
<p>This setup will work with all static websites but let's focus on React.</p>
<p>So let's start with a simple hello world application created with Create React App <a target="_blank" href="https://create-react-app.dev/docs/getting-started">https://create-react-app.dev/docs/getting-started</a></p>
<pre><code class="lang-bash">npx create-react-app my-app
<span class="hljs-built_in">cd</span> my-app
npm start
</code></pre>
<p>This will create our app and start it, so we can take a look at it in our browser locally on the PC.</p>
<p>Now that we have our sample application ready let's take a look at the structure of the project:</p>
<pre><code class="lang-bash">my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── setupTests.js
</code></pre>
<p>The structure contains multiple js and media files but those are only the source of our final application, before using it in production we need to build the application and generate an artifact.</p>
<p>We can now kill the React development server we just started and instead we can now build our application:</p>
<pre><code class="lang-bash">npm run build
</code></pre>
<p>The output of the command will be put in the <code>build</code> directory. All the files inside our build directory are what will be uploaded to our static hosting.</p>
<h1 id="heading-create-s3-resources">Create S3 resources</h1>
<p>With our artifacts ready we can now create an S3 bucket, for this tutorial we will use the AWS console but for your production workloads, I suggest using IaC tools like CDK, Terraform or Cloudformation.</p>
<p>Let's create our bucket:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760753271/a8884333-8449-4dce-bf67-48248bd2e9cf.png" alt="S3 create bucket" class="image--center mx-auto" /></p>
<p>After creating it, the empty bucket is ready to receive our artifacts:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760761645/143dbd10-d6e4-4b55-9a9b-5c8acaa8f822.png" alt="S3 bucket created" class="image--center mx-auto" /></p>
<p>We can now upload our artifacts, let's do it with the aws-cli, and follow the blog for a future post where we automate this with CI/CD.</p>
<pre><code class="lang-bash">aws s3 sync --delete build s3://react-on-aws-demo-alessandromarinoac
</code></pre>
<h1 id="heading-create-cloudfront-resources">Create CloudFront resources</h1>
<p>Let's create now our CloudFront distribution and let's select our bucket as origin, using the console make this process really simple.</p>
<p>To secure the connection between CloudFront and the bucket we need to create or use an existing Origin Access Control Settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760772753/8f69ab9b-6cd2-4ab4-9dbc-ed4f732b9ed6.png" alt="CloudFront create distribution" class="image--center mx-auto" /></p>
<p>If you don't already have one Control Settings let's create it using the button on the right:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760830993/8c7ded5d-fc5e-48c3-9c7b-5622dd314e4e.png" alt="CloudFront create control settings" class="image--center mx-auto" /></p>
<p>Now with the origin secured, we can start setting up the cache options for our CloudFront distribution, we will for example redirect all HTTP requests to HTTPS.</p>
<p>We can also create a custom cache policy if we need to specify specific TTL or caching options, if not we can use the default CachingOptimized that will help us save some time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760868468/a535513e-3a7d-4754-9fdc-ef4b83ceef21.png" alt="CloudFront create cache behaviour " class="image--center mx-auto" /></p>
<p>Let's go ahead and scroll to the general settings section, here we can select a price class if we want to save some money or we can for example set our default root object that will be necessary to make React work properly.</p>
<p>We are setting the root object as <code>index.html</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760909477/beb7c137-6e1e-4e49-b94c-f71d3e496e51.png" alt="CloudFront distribution creation settings" class="image--center mx-auto" /></p>
<p>We can now click on <code>Create distribution</code> and we have the confirmation. The distribution needs some time to be ready (~10min) so have some patience here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677760991890/9810b782-892d-4561-b1b8-bb477e3a72c2.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-secure-our-s3-bucket">Secure our S3 Bucket</h1>
<p>Now as the console advise us to, let's copy the S3 bucket policy from the blue banner and let's go back to our S3 bucket to apply it. We need to navigate to the bucket and then to the Permission tab:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677761067354/cb841fbe-f1e3-4ea3-a559-09f95b3f292d.png" alt="S3 bucket policy" class="image--center mx-auto" /></p>
<p>We can now apply the copied S3 Bucket Policy to restrict access to the bucket:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677761099286/ef3644dd-21ba-4751-bcf0-408b979f6356.png" alt="S3 bucket policy for CloudFront" class="image--center mx-auto" /></p>
<p>With this policy in place, we are authorizing CloudFront to read from our bucket, without the need of having a public bucket.</p>
<h1 id="heading-check-if-its-working">Check if it's working</h1>
<p>Let's now go back to the CloudFront console and retrieve the distribution domain name to check our app online:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677761171209/5c2b8833-77c2-41d1-ae02-4ba117a9397e.png" alt="CloudFront distribution domain" class="image--center mx-auto" /></p>
<p>If we use the browser to open that link, we can see our app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677761196872/929611d3-922d-44ba-b209-d3afc1781be6.png" alt="CloudFront React app example" class="image--center mx-auto" /></p>
<p>So now we have our React application up and running.</p>
<h1 id="heading-setup-errors-pages">Setup Errors Pages</h1>
<p>There is still a step we need to take. If we try to navigate to any page other than the root we will get an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677762174000/7dfe448c-2b02-4115-b5c8-39ede96f11fe.png" alt="CloudFront error pages" class="image--center mx-auto" /></p>
<p>We need to set Error Pages on CloudFront so instead of getting errors from CloudFront we will let React take care of those.</p>
<p>Let's create two custom error responses, one for <code>403 Forbidden</code> and one for <code>404 Not Found</code> , both will have a response code of <code>200 OK</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677762415142/a471a8d6-79bc-485c-b786-564e34fb4141.png" alt="CloudFront error pages list" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677762420573/e616f8d7-e059-421a-a979-ac9f032b5783.png" alt="CloudFront 403 error page for React" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677762424932/b03f61bc-b287-4f88-9bdd-2d5777bc170a.png" alt="CloudFront 404 error page for React" class="image--center mx-auto" /></p>
<p>With these new settings, we can now navigate to every path of our applications without receiving errors from CloudFront:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677762392443/7388ca0f-de41-410a-aae6-63cc68d9470a.png" alt="CloudFront React app successfully working" class="image--center mx-auto" /></p>
<p>To create pages on different paths inside react we will need to use something like <a target="_blank" href="https://reactrouter.com/en/main">https://reactrouter.com/en/main</a></p>
<p>With react-router configured we can also set up <a target="_blank" href="https://reactrouter.com/en/main/start/tutorial#:~:text=Set%20the%20%3C-,ErrorPage,-%3E%20as%20the">custom 404 pages</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Unleashing the Power of Large-Scale Parallel Data Processing on AWS]]></title><description><![CDATA[AWS has recently released a new feature for AWS Step Function that allows you to perform large-scale parallel data processing AWS News Article
Why you could need large-scale parallel data processing?
Processing large CSVs or processing every item in ...]]></description><link>https://alessandromarinoac.com/unleashing-the-power-of-large-scale-parallel-data-processing-on-aws</link><guid isPermaLink="true">https://alessandromarinoac.com/unleashing-the-power-of-large-scale-parallel-data-processing-on-aws</guid><category><![CDATA[AWS]]></category><category><![CDATA[stepfunction]]></category><category><![CDATA[state-machines]]></category><category><![CDATA[serverless]]></category><category><![CDATA[lambda]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Sun, 29 Jan 2023 12:55:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677416299115/1eb6e7da-826d-418d-963a-d7b012971338.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AWS has recently released a new feature for AWS Step Function that allows you to perform large-scale parallel data processing <a target="_blank" href="https://aws.amazon.com/blogs/aws/step-functions-distributed-map-a-serverless-solution-for-large-scale-parallel-data-processing/">AWS News Article</a></p>
<h2 id="heading-why-you-could-need-large-scale-parallel-data-processing">Why you could need large-scale parallel data processing?</h2>
<p>Processing large CSVs or processing every item in an S3 Bucket is something that can be necessary from time to time.<br />Some examples use cases:</p>
<ol>
<li><p>Import data from a large CSV file in a short time</p>
</li>
<li><p>Create many ordered requests to third-party service with retry and backoff, where each request uses the output from the precedent.</p>
</li>
<li><p>Reprocess every image you already have on your S3 bucket to follow a new standard size</p>
</li>
</ol>
<h2 id="heading-why-use-serverless-resources-for-it">Why use serverless resources for it?</h2>
<p>Processing millions of items with a tight deadline in a traditional way, using containers or virtual machines, can need quite some effort of pre-provisioning. If our input doesn't come at a predictable time this can be even harder.<br />By using a combination of services like Step Function and Lambda we can run our workflow without pre-provisioning, run it when needed, and pay only for the usage.</p>
<p>Make sure to verify your <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html">AWS Service Limits</a> if you plan to run a large number of Lambda.</p>
<h2 id="heading-demo">Demo</h2>
<p>I built a small demo using AWS CDK to run an image resizing process on an S3 bucket containing 8088 images of dogs. The project uses AWS CDK (typescript) and the lambda function executing the processing is built with Go.</p>
<p><a target="_blank" href="https://github.com/alessandromr/aws-sfn-map-demo">The GitHub repository</a><br /><a target="_blank" href="http://vision.stanford.edu/aditya86/ImageNetDogs">The dataset used for testing</a></p>
<h3 id="heading-the-step-function">The Step Function</h3>
<p><img src="https://s3-eu-west-1.amazonaws.com/alessandromarinoac.com/sfn-distributed-map/sfn-diagram.png" alt="Step Function distributed map diagram" /></p>
<p>The step function for this demo is really simple:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Comment"</span>: <span class="hljs-string">"A description of my state machine"</span>,
    <span class="hljs-attr">"StartAt"</span>: <span class="hljs-string">"Map"</span>,
    <span class="hljs-attr">"States"</span>: {
        <span class="hljs-attr">"Map"</span>: {
            <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Map"</span>,
            <span class="hljs-attr">"ItemProcessor"</span>: {
                <span class="hljs-attr">"ProcessorConfig"</span>: {
                    <span class="hljs-attr">"Mode"</span>: <span class="hljs-string">"DISTRIBUTED"</span>,
                    <span class="hljs-attr">"ExecutionType"</span>: <span class="hljs-string">"EXPRESS"</span>
                },
                <span class="hljs-attr">"StartAt"</span>: <span class="hljs-string">"Lambda Invoke"</span>,
                <span class="hljs-attr">"States"</span>: {
                    <span class="hljs-attr">"Lambda Invoke"</span>: {
                        <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
                        <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:states:::lambda:invoke"</span>,
                        <span class="hljs-attr">"OutputPath"</span>: <span class="hljs-string">"$.Payload"</span>,
                        <span class="hljs-attr">"Parameters"</span>: {
                            <span class="hljs-attr">"Payload.$"</span>: <span class="hljs-string">"$"</span>,
                            <span class="hljs-attr">"FunctionName.$"</span>: <span class="hljs-string">"$.BatchInput.lambda_processor_arn"</span>
                        },
                        <span class="hljs-attr">"Retry"</span>: [
                            {
                                <span class="hljs-attr">"ErrorEquals"</span>: [
                                    <span class="hljs-string">"Lambda.ServiceException"</span>,
                                    <span class="hljs-string">"Lambda.AWSLambdaException"</span>,
                                    <span class="hljs-string">"Lambda.SdkClientException"</span>,
                                    <span class="hljs-string">"Lambda.TooManyRequestsException"</span>
                                ],
                                <span class="hljs-attr">"IntervalSeconds"</span>: <span class="hljs-number">2</span>,
                                <span class="hljs-attr">"MaxAttempts"</span>: <span class="hljs-number">6</span>,
                                <span class="hljs-attr">"BackoffRate"</span>: <span class="hljs-number">2</span>
                            }
                        ],
                        <span class="hljs-attr">"End"</span>: <span class="hljs-literal">true</span>
                    }
                }
            },
            <span class="hljs-attr">"End"</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">"Label"</span>: <span class="hljs-string">"Map"</span>,
            <span class="hljs-attr">"MaxConcurrency"</span>: <span class="hljs-number">1000</span>,
            <span class="hljs-attr">"ItemReader"</span>: {
                <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:states:::s3:listObjectsV2"</span>,
                <span class="hljs-attr">"Parameters"</span>: {
                    <span class="hljs-attr">"Bucket.$"</span>: <span class="hljs-string">"$.input.source_bucket_name"</span>,
                    <span class="hljs-attr">"Prefix.$"</span>: <span class="hljs-string">"$.input.bucket_path"</span>
                }
            },
            <span class="hljs-attr">"ItemBatcher"</span>: {
                <span class="hljs-attr">"MaxItemsPerBatch"</span>: <span class="hljs-number">100</span>,
                <span class="hljs-attr">"BatchInput"</span>: {
                    <span class="hljs-attr">"lambda_processor_arn.$"</span>: <span class="hljs-string">"$.input.lambda_processor_arn"</span>,
                    <span class="hljs-attr">"source_bucket_name.$"</span>: <span class="hljs-string">"$.input.source_bucket_name"</span>,
                    <span class="hljs-attr">"destination_bucket_name.$"</span>: <span class="hljs-string">"$.input.destination_bucket_name"</span>
                }
            }
        }
    }
}
</code></pre>
<p>We make use of the freshly released <code>DISTRIBUTED</code> mode of the Map state and we create a single Lambda function as a child of this Map state.</p>
<p>We have multiple possible options to use as a source for our distributed processing, in this case, we are using <code>arn:aws:states:::s3:listObjectsV2</code> that will perform a <code>list</code> operation on the s3 path provided and it will trigger a child execution for each file found (or for a batch in this case, see <code>ItemBatcher</code> item).</p>
<p>Other than the batch settings (the number of items that will go in each child execution) we can also set how many parallel child executions we want with the <code>MaxConcurrency</code> parameter.</p>
<p>In this example I'm setting a batch of 100 items with a max concurrency of 1000 execution, so theoretically the 8088 files will be executed by 81 Lambda functions.</p>
<h3 id="heading-results">Results</h3>
<p>If we run our Step Function we can see the events that take place:</p>
<p><img src="https://s3-eu-west-1.amazonaws.com/alessandromarinoac.com/sfn-distributed-map/sfn-events.png" alt="Step Function dashboard execution events" /></p>
<p>And if we take a look at the <code>Map Run</code> dashboard we can see the details about the child executions of the Step Function:</p>
<p><img src="https://s3-eu-west-1.amazonaws.com/alessandromarinoac.com/sfn-distributed-map/sfn-map-run-dashboard.png" alt="Step Function Map Run Dashboard" /></p>
<p>With these batch settings, we run the resizing process on 8088 photos in just 8.4 seconds, and if we take a look at Lambda metrics we can see that Lambda duration has an average value of 2.3 seconds and we have exactly 81 invocations with 81 concurrent executions, so exactly what we were expecting.</p>
<p>I also did run an experiment with a batch size of 10 and 809 invocations were triggered with a peak of 341 concurrent execution and an average duration of 432ms.</p>
<p>Our resized dog:</p>
<p><img src="https://s3-eu-west-1.amazonaws.com/alessandromarinoac.com/sfn-distributed-map/sfn-before-after.png" alt="Images Before and After" /></p>
]]></content:encoded></item><item><title><![CDATA[Introduction to Amazon CloudFront]]></title><description><![CDATA[Amazon CloudFront
CloudFront is Amazon’s offer for Content Delivery Network. The CDN is composed of a globally distributed set of caching servers that provide low latency and high throughput all around the world.
The network is based on more than 300...]]></description><link>https://alessandromarinoac.com/introduction-to-amazon-cloudfront</link><guid isPermaLink="true">https://alessandromarinoac.com/introduction-to-amazon-cloudfront</guid><category><![CDATA[AWS]]></category><category><![CDATA[cloudfront]]></category><category><![CDATA[CDN]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Fri, 26 Nov 2021 11:24:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677410723062/e2340be0-600d-4311-8c96-920981857467.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-amazon-cloudfront">Amazon CloudFront</h1>
<p>CloudFront is Amazon’s offer for Content Delivery Network. The CDN is composed of a globally distributed set of caching servers that provide low latency and high throughput all around the world.</p>
<p>The network is based on more than 300 PoP (Points of Presence) around the world.<br />These PoPs are for majority edge locations that serve traffic to the final users but, usually corresponding to AWS regions, there are also regional edge locations. This latter one can be considered a mid-tier cache.</p>
<p><img src="https://d1.awsstatic.com/global-infrastructure/maps/Cloudfront-Map_9.24_2x.2eeac6e52bf404816c6d0aac3edbeb7b6b87fdaa.png" alt="AWS CloudFront point of presence map" /></p>
<p><a target="_blank" href="https://aws.amazon.com/cloudfront/features/?nc=sn&amp;loc=2&amp;whats-new-cloudfront.sort-by=item.additionalFields.postDateTime&amp;whats-new-cloudfront.sort-order=desc">Image from AWS Docs</a></p>
<h2 id="heading-distributions">Distributions</h2>
<p>A CloudFront distribution is the main resource you can create to distribute content globally, for example when creating a WordPress site we can create a distribution for WordPress media. One of the main settings that a Distribution allows you to specify it’s the content origin, this origin is where CloudFront will try to fetch content before caching it.</p>
<h2 id="heading-origins">Origins</h2>
<p>CloudFront supports multiple origins and natively integrates with many AWS Services.<br />One of the most used integration is with Amazon S3. The combination of S3 + CloudFront it’s widely used to host static websites.<br />When using CloudFront with Amazon S3 it’s possible to secure the origin’s bucket with an Origin Access Identity (OAI), this feature will protect the bucket from traffic that it’s not originated within your CloudFront distribution.</p>
<h2 id="heading-caching-behaviour">Caching Behaviour</h2>
<p>CloudFront allows you to customize how requests are handled with multiple cache behavior. Each cache behavior allows the user to define a pattern (eg: <code>*.jpg</code>) and some rules that apply to this pattern.</p>
<p>Static websites provide a good example of how to use cache behaviors. All the media files (JPG, PNG, SVG) are generally cached for a long time meanwhile <code>index.html</code> is generally cached for a shorter time.<br />So by having multiple cache behaviors we can have all these customized settings for each file type.</p>
<h2 id="heading-functions">Functions</h2>
<h3 id="heading-lambdaedge">Lambda@Edge</h3>
<p>These Lambda functions allow to run code near the end user, specifically, Lambda@Edge are executed in a regional edge location.</p>
<p>Lambda@Edge can be considered normal Lambda functions with some more limitations on execution times, runtimes, and other settings. Some actions that are commonly performed in Lambda@Edge:</p>
<ul>
<li><p>connect to a database</p>
</li>
<li><p>execute network calls (eg: HTTP)</p>
</li>
</ul>
<p>Lambda@Edge are invoked by CloudFront on specific events:</p>
<ol>
<li><p>Viewer Request</p>
</li>
<li><p>Origin Request</p>
</li>
<li><p>Origin Response</p>
</li>
<li><p>Viewer Response</p>
</li>
</ol>
<p>So for example by using the event “Origin Request” you can trigger a Lambda@Edge every time CloudFront contacts your origin (every cache miss).</p>
<h3 id="heading-cloudfront-functions">CloudFront Functions</h3>
<p>CloudFront functions are lightweight functions that are executed at the edge, so in one of the 300+ PoP. Instead, Lambda@Edge are executed in regional edge location (13 at the time of writing).</p>
<p>CloudFront functions have low latency but also come with some hard limitations on execution time and maximum memory:</p>
<ul>
<li><p>Maximum execution time: 1ms</p>
</li>
<li><p>Maximum memory: 2MB</p>
</li>
</ul>
<p>Also, CloudFront functions come without network or filesystem access. Common uses for CloudFront functions are:</p>
<ul>
<li><p>URL rewrite and redirects</p>
</li>
<li><p>Cache-key manipulations and normalizations</p>
</li>
<li><p>HTTP header manipulation</p>
</li>
<li><p>Access Authorisation</p>
</li>
</ul>
<p>CloudFront functions are invoked by CloudFront on specific events (less than Lambda@Edge):</p>
<ol>
<li><p>Viewer Request</p>
</li>
<li><p>Viewer Response</p>
</li>
</ol>
<h3 id="heading-differences-recap">Differences recap</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>CloudFront functions</td><td>Lambda@Edge</td></tr>
</thead>
<tbody>
<tr>
<td>Runtimes</td><td>JS</td><td>JS/Python</td></tr>
<tr>
<td>Max execution time</td><td>1ms</td><td>5s for Viewer request/response &amp; 30s for origin request/response</td></tr>
<tr>
<td>Max memory</td><td>2MB</td><td>2MB for Viewer request/response &amp; 10GB for Origin request/response</td></tr>
<tr>
<td>Network Access</td><td>No</td><td>Yes</td></tr>
<tr>
<td>File System Access</td><td>No</td><td>Yes</td></tr>
</tbody>
</table>
</div><h2 id="heading-georestriction">GeoRestriction</h2>
<p>CloudFront allows the user to block certain geographical zones from accessing the content. To make an example of the use of this feature: streaming services block the viewing of some content in regions where copyright it’s held by other companies.</p>
<h2 id="heading-pricing">Pricing</h2>
<p>When using CloudFront with AWS services you pay only for outbound traffic: from CloudFront to the final users. Traffic from AWS service to CloudFront it’s free.</p>
<p>Pricing on AWS services tends to change often so please refer to https://aws.amazon.com/cloudfront/pricing</p>
<h3 id="heading-free-tier">Free Tier</h3>
<p>CloudFront has recently updated its free tier, starting from Dec 1, 2021, the new free tier it’s not limited to 12 months after signup but extends indefinitely. New limits are:</p>
<ul>
<li><p>Data Traffic: 1TB/month</p>
</li>
<li><p>Requests: 10M/month</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Using Terraform Workspace on AWS for multi-account, multi-environment deployments]]></title><description><![CDATA[Originally posted on 27 May 2020.
What Terraform Workspaces are?
Terraform supports two concepts of workspaces: CLI workspace and Terraform Cloud/Enterprise workspace. This article is focused on CLI Workspaces also known as OSS Workspace.
CLI workspa...]]></description><link>https://alessandromarinoac.com/using-terraform-workspace-on-aws-for-multi-account-multi-environment-deployments</link><guid isPermaLink="true">https://alessandromarinoac.com/using-terraform-workspace-on-aws-for-multi-account-multi-environment-deployments</guid><category><![CDATA[AWS]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Thu, 27 May 2021 10:44:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677412019402/e8ef01cd-0416-46b3-ab55-2b5908e6f441.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Originally posted on 27 May 2020.</p>
<h2 id="heading-what-terraform-workspaces-are">What Terraform Workspaces are?</h2>
<p>Terraform supports two concepts of workspaces: CLI workspace and Terraform Cloud/Enterprise workspace. This article is focused on CLI Workspaces also known as OSS Workspace.</p>
<p><strong>CLI workspaces</strong> are a feature that allows us to manage a single Terraform configuration and provision the resulting resources multiple times.<br />This allows us to have similar groups of resources without managing multiple stacks. To use CLI workspaces, we have to use a compatible backend, like S3.</p>
<p>From this point, I will call CLI Workspaces as <strong>workspaces</strong>.</p>
<h2 id="heading-use-case">Use Case</h2>
<p>Let's imagine a simple infrastructure composed of some virtual machines deployed in three different environments:</p>
<ol>
<li><p>Development</p>
</li>
<li><p>Staging</p>
</li>
<li><p>Production</p>
</li>
</ol>
<p>Each one of those three environments is on a dedicated AWS account, and they don't share anything. Configuration files for the environments are the same and must remain consistent and identical.</p>
<p>Differences like the environment tags, instance size, instance number, and other parameters are given with a dedicated variable file <code>tfvars</code>.</p>
<p>We usually have two ways to deploy this configuration multiple times:</p>
<ul>
<li><p>different directories</p>
</li>
<li><p>workspaces</p>
</li>
</ul>
<h4 id="heading-different-directories">Different directories</h4>
<p>This approach guarantees the optimal separation between environments and helps reduce errors, like deploying to the wrong environment.</p>
<p>Having multiple directories also implies that the same code is replicated in all of them, so this is a big downside but can be also an advantage for use cases where heavy customization between environments is required.</p>
<p>In our case, wanting three identical environments, this approach will only be more complex and harder to maintain.</p>
<h4 id="heading-cli-workspaces">CLI Workspaces</h4>
<p>Workspaces are usually my way to resolve this issue and avoid replicated code. Workspaces as a "copy" of the state file that relays on the same terraform configuration files, so you can write once and deploy several times the exact replica of your infrastructure.</p>
<p>When working with workspaces, we manage multiple replicas of the deployed infrastructure from the same configuration file, in a single directory. Working in this single directory can introduce some human errors like the deploy in the wrong environment or destroying the wrong environment.</p>
<hr />
<h3 id="heading-workspace-setup">Workspace setup</h3>
<p>This is a small example with S3 as a remote backend. This example will be deployed on three different AWS accounts, one for each environment:</p>
<ul>
<li><p><code>dev</code></p>
</li>
<li><p><code>stage</code></p>
</li>
<li><p><code>prod</code></p>
</li>
</ul>
<h4 id="heading-initial-setup-backend">Initial setup (Backend)</h4>
<p>Using S3 as our remote backend our Terraform state will be saved on Amazon S3. To maintain all our Terraform states in a single place we choose to use our production account as storage. So we need to create an S3 bucket and a DynamoDB table on our production account, the bucket for this example will be named <code>my-terraform-backend-state</code>. <a target="_blank" href="https://www.terraform.io/docs/backends/types/s3.html">Set up S3 Backend</a></p>
<h4 id="heading-initial-setup-cli">Initial setup (CLI)</h4>
<p>Before applying the terraform configuration we will have to set up the AWS CLI.<br /><a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html">Configuring the AWS CLI</a></p>
<p>With the AWS CLI installed we can set up our AWS Profile: <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html">AWS CLI Named Profiles</a></p>
<p><code>~/.aws/credentials</code></p>
<pre><code class="lang-plaintext">[my-account-dev]
aws_access_key_id=********************
aws_secret_access_key==******************

[my-account-stage]
aws_access_key_id=********************
aws_secret_access_key==******************

[my-account-prod]
aws_access_key_id=********************
aws_secret_access_key==******************
</code></pre>
<h4 id="heading-the-terraform-configuration">The Terraform Configuration</h4>
<pre><code class="lang-plaintext">provider "aws" {
  region  = var.aws_region
  profile = "my-account-${terraform.workspace}"
}

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 3.0"
    }
  }

  backend "s3" {
    profile              = "my-account-prod"
    bucket               = "my-terraform-backend-state"
    workspace_key_prefix = "eu-west-1/tutorial"
    key                  = "shared-infra"
    region               = "eu-west-1"

    dynamodb_table = "terraform-states-lock-table"
  }
}
</code></pre>
<p>This configuration hides a lot of details, so we have to deep dive a little bit into each one.</p>
<h5 id="heading-profiles">Profiles</h5>
<p>Profiles are used by the Terraform AWS Provider to authenticate by the use of the named profile we set up early. The complex part here is that we use the <code>profile</code> parameter two times. The first use of <code>profile</code> is inside the <code>provider</code> block, in this case, the parameter is used to deploy the resources we declare in our stack. So if we have to deploy the stack in our development account the profile here must be <code>my-account-dev</code> and by using</p>
<pre><code class="lang-plaintext">profile = "my-account-${terraform.workspace}"
</code></pre>
<p>we interpolate our current workspace and in case we are deploying to the development account the workspace name will exactly be <code>dev</code>. So the profile after interpolation will be: <code>my-account-dev</code>, exactly what we need.</p>
<p>The second use of <code>profile</code> is inside the <code>backend</code> block, here we use the parameter to indicate Terraform where our backend (S3 Bucket + DynamoDB Table) will be. In our case, we choose to centralize states and the DynamoDB lock table on the production account so the profile will be <code>my-account-prod</code>.</p>
<h3 id="heading-deployment">Deployment</h3>
<h4 id="heading-workspaces-commands">Workspaces Commands</h4>
<ol>
<li>Creating a new workspace:</li>
</ol>
<pre><code class="lang-plaintext">terraform workspace new dev
</code></pre>
<ol>
<li>Select an existing workspace:</li>
</ol>
<pre><code class="lang-plaintext">terraform workspace select dev
</code></pre>
<ol>
<li>Apply using a specific var file</li>
</ol>
<pre><code class="lang-plaintext">terraform apply -var-file=terraform.dev.tfvars
</code></pre>
<h3 id="heading-resulting-state-files">Resulting State Files</h3>
<p>The example below if deployed to all our three accounts will produce three different state files.</p>
<pre><code class="lang-plaintext">s3://my-terraform-backend-state/eu-west-1/tutorial/dev/shared-infra
s3://my-terraform-backend-state/eu-west-1/tutorial/stage/shared-infra
s3://my-terraform-backend-state/eu-west-1/tutorial/prod/shared-infra
</code></pre>
<p>This state file is composed by:</p>
<pre><code class="lang-plaintext">s3://{bucket_name}/{workspace_key_prefix}/{workspace_name}/{key}.json
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Using Multiple Terraform's Providers With AWS]]></title><description><![CDATA[Originally posted on 29 May 2020.
What Terraform Providers are?
Terraform providers are the Terraform internal component responsible for API understanding and API communication.Providers also understand authentication and authorization for the extern...]]></description><link>https://alessandromarinoac.com/using-multiple-terraforms-providers-with-aws</link><guid isPermaLink="true">https://alessandromarinoac.com/using-multiple-terraforms-providers-with-aws</guid><category><![CDATA[Terraform]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Fri, 07 May 2021 10:40:18 GMT</pubDate><content:encoded><![CDATA[<p>Originally posted on 29 May 2020.</p>
<h3 id="heading-what-terraform-providers-are">What Terraform Providers are?</h3>
<p>Terraform providers are the Terraform internal component responsible for API understanding and API communication.<br />Providers also understand authentication and authorization for the external API.</p>
<h3 id="heading-aws-provider">AWS Provider</h3>
<p>One of the most used Terraform providers is the <a target="_blank" href="https://www.terraform.io/docs/providers/aws/index.html">AWS Terraform Provider</a>. This provider handles all the communication with AWS API allowing you to provision resources on AWS.</p>
<p>AWS Provider Example Usage (from Terraform docs)</p>
<pre><code class="lang-plaintext"># Configure the AWS Provider
provider "aws" {
  region  = "us-east-1"
}

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 3.0"
    }
  }
}

# Create a VPC
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}
</code></pre>
<p>Without special needs, most users stop at these simple provider definitions, but AWS provider (like other providers) has a lot of possible customizations. Some examples:</p>
<pre><code class="lang-plaintext"># Configure the AWS Provider
provider "aws" {
  region  = "us-east-1"

  profile = "my-custom-aws-profile"
  shared_credentials_file = "~/.aws/credentials"

  allowed_account_ids = ["123456789012"]
  forbidden_account_ids = ["98765432198"]
}

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 3.0"
    }
  }
}
</code></pre>
<p>Parameters like <code>allowed_account_ids</code> and <code>forbidden_account_ids</code> help you avoid applying modification on the wrong account by simply using the wrong profile or environment variables.</p>
<h3 id="heading-multiple-providers">Multiple Providers</h3>
<p>Now the article's main topic: using multiple AWS providers.<br />A single Terraform AWS provider is limited to an account and a specific region.<br />What if we want to deploy resources within the same stack but in two regions. This need is not as remote as you can imagine, it's actually a pretty common use case.<br />When you use a CloudFront Distribution with a custom SSL certificate you have to create an ACM certificate in <strong>N.Virginia</strong>.<br />So here we have a small example of a CloudFront distribution in <strong>Ireland</strong> and the needed ACM Certificate in <strong>N.Virginia</strong>, all within the same stack and even within the same file.</p>
<pre><code class="lang-plaintext">provider "aws" {
  region = "eu-west-1"
}

provider "aws" {
  alias  = "uswest"
  region = "us-east-1"
}

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 3.0"
    }
  }
}
</code></pre>
<p>To use our multiple providers:</p>
<pre><code class="lang-plaintext">resource "aws_cloudfront_distribution" "distribution" {
    viewer_certificate_arguments {
        acm_certificate_arn = aws_acm_certificate.cert.arn
        ....
    }
    ....
}

resource "aws_acm_certificate" "cert" {
    provider = aws.uswest
    ....
}
</code></pre>
<h3 id="heading-example-s3-cross-region-replication">Example (S3 Cross-Region Replication)</h3>
<pre><code class="lang-plaintext">resource "aws_s3_bucket" "uswest_bucket" {
  provider = aws.uswest
  bucket   = "alessandromarinoac-uswest-bucket"
  acl      = "private"
  versioning {
    enabled = true
  }
}

resource "aws_s3_bucket" "euwest_bucket" {
  bucket = "alessandromarinoac-euwest-bucket"
  acl    = "private"
  versioning {
    enabled = true
  }

  replication_configuration {
    role = aws_iam_role.s3_fullaccess_role.arn
    rules {
      id     = "testReplication"
      status = "Enabled"
      destination {
        bucket        = aws_s3_bucket.uswest_bucket.arn
        storage_class = "STANDARD"
      }
    }
  }
}

resource "aws_iam_role" "s3_fullaccess_role" {
  name               = "s3-fullaccess-role"
  assume_role_policy = data.aws_iam_policy_document.s3_fullaccess_role_assume_policy.json
}

resource "aws_iam_role_policy" "s3_fullaccess_policy" {
  name   = "s3_fullaccess_policy"
  role   = aws_iam_role.s3_fullaccess_role.id
  policy = data.aws_iam_policy_document.s3_fullaccess_role_policy.json
}

data "aws_iam_policy_document" "s3_fullaccess_role_assume_policy" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["s3.amazonaws.com"]
    }
    actions = [
      "sts:AssumeRole"
    ]
  }
}

data "aws_iam_policy_document" "s3_fullaccess_role_policy" {
  statement {
    effect = "Allow"
    actions = [
      "s3:GetReplicationConfiguration",
      "s3:ListBucket"
    ]
    resources = [
      "arn:aws:s3:::alessandromarinoac-euwest-bucket",
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:GetObjectVersion",
      "s3:GetObjectVersionAcl",
      "s3:GetObjectVersionTagging"
    ]
    resources = [
      "arn:aws:s3:::alessandromarinoac-euwest-bucket/*"
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:ReplicateObject",
      "s3:ReplicateDelete",
      "s3:ReplicateTags"
    ]
    resources = [
      "${aws_s3_bucket.uswest_bucket.arn}/*"
    ]
  }
}
</code></pre>
<p>The example will create two S3 buckets in two different AWS regions:</p>
<p><img src="https://s3-eu-west-1.amazonaws.com/alessandromarinoac.com/MultiProvider.PNG" alt="Terraform Multi Provider Buckets" /></p>
<p>With replication enabled:</p>
<p><img src="https://s3-eu-west-1.amazonaws.com/alessandromarinoac.com/MultiProvider1.PNG" alt="Terraform Multi Provider Replication" /></p>
]]></content:encoded></item><item><title><![CDATA[AWS CLI S3 Cheatsheet - s3 ls]]></title><description><![CDATA[The ls command can be used for listing S3 buckets or objects inside a bucket. When used to list objects, the command will show you only objects in the current prefix.
List all buckets
~ aws s3 ls
2019-12-15 16:31:53 testbucket1
2020-03-11 14:44:32 te...]]></description><link>https://alessandromarinoac.com/s3-cli-basics-ls</link><guid isPermaLink="true">https://alessandromarinoac.com/s3-cli-basics-ls</guid><category><![CDATA[AWS]]></category><category><![CDATA[Amazon S3]]></category><category><![CDATA[S3 CLI]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Thu, 17 Sep 2020 09:53:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677409804581/2464798a-ea99-45d2-9d40-915d7ca5eb9e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <code>ls</code> command can be used for listing S3 buckets or objects inside a bucket. When used to list objects, the command will show you only objects in the current prefix.</p>
<h2 id="heading-list-all-buckets">List all buckets</h2>
<pre><code class="lang-bash">~ aws s3 ls
2019-12-15 16:31:53 testbucket1
2020-03-11 14:44:32 testbucket112312031230
2020-06-01 09:06:26 myPersonalTestBucket
</code></pre>
<h2 id="heading-list-objects-inside-a-bucket">List objects inside a bucket</h2>
<pre><code class="lang-bash">~ aws s3 ls s3://myPersonalTestBucket/
                           PRE 2020/
2019-12-15 18:05:39      69483 test.PNG
</code></pre>
<p>When <code>ls</code> it's used inside a bucket, it will return all objects and prefixes inside the current level. You can navigate prefixes as a sort of virtual directory.<br />In this case, we can also navigate down inside the <code>2020</code> prefix:</p>
<pre><code class="lang-bash">~ aws s3 ls s3://myPersonalTestBucket/2020/09/01/20/20/
2019-12-15 18:23:46   32864363 complete-logs.txt
2019-12-15 18:23:46     463004 light-logs.pdf
</code></pre>
<h3 id="heading-path-matching">Path matching</h3>
<p>Let's take into consideration a bucket with content similar to this one:</p>
<pre><code class="lang-bash">~ aws s3 ls s3://testbucket1
                           PRE ABC/
                           PRE ACC/
                           PRE ACZ/
                           PRE AZC/
                           PRE BCA/
                           PRE BCZ/
</code></pre>
<p>What if we want to list all prefixes that start with <code>A</code> or <code>AC</code>:</p>
<pre><code class="lang-bash">~ aws s3 ls s3://testbucket1/A
                           PRE ABC/
                           PRE ACC/
                           PRE ACZ/
                           PRE AZC/
~ aws s3 ls s3://testbucket1/AC
                           PRE ACC/
                           PRE ACZ/
</code></pre>
<p>And what if we want to list all objects starting with a prefix:</p>
<pre><code class="lang-bash">~ aws s3 ls s3://testbucket1/AC --recursive
2019-12-15 18:22:17     99731 ACC/test1.pdf
2019-12-15 18:22:17     1916928 ACZ/test2.pdf
2019-12-15 18:22:17     112989 ACZ/test3.pdf
</code></pre>
<h3 id="heading-human-option">Human option</h3>
<p>The human option helps read the filesize.</p>
<pre><code class="lang-bash">~ aws s3 ls s3://testbucket1/AC --recursive --human
2019-12-15 18:22:17     97.4 KiB ACC/test1.pdf
2019-12-15 18:22:17     1.8 MiB ACZ/test2.pdf
2019-12-15 18:22:17     110.3 KiB ACZ/test3.pdf
</code></pre>
<h3 id="heading-summarize-option">Summarize option</h3>
<p>Summarize option add two rows at the end of the output with a short recap:</p>
<ul>
<li><p>total objects listed</p>
</li>
<li><p>the total size of objects listed</p>
</li>
</ul>
<p>You can use <code>--summarize</code> in combination with <code>--human</code> to have the total size of objects listed a little more readable.</p>
<pre><code class="lang-bash">~ aws s3 ls s3://testbucket1/AC --recursive --human --summarize
2019-12-15 18:22:17     97.4 KiB ACC/test1.pdf
2019-12-15 18:22:17     1.8 MiB ACZ/test2.pdf
2019-12-15 18:22:17     110.3 KiB ACZ/test3.pdf
Total Objects: 3
   Total Size: 2.1 MiB
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Install Terraform on MacOS]]></title><description><![CDATA[Installing Terraform on MacOS is pretty simple, we have several different ways of doing it:

Using Homebrew

Manual Installation from binary

Using TFSwitch


Official Terraform documentation.
Using TFSwitch
I will start with the third method because...]]></description><link>https://alessandromarinoac.com/install-terraform-on-macos</link><guid isPermaLink="true">https://alessandromarinoac.com/install-terraform-on-macos</guid><category><![CDATA[Terraform]]></category><category><![CDATA[macOS]]></category><category><![CDATA[Homebrew]]></category><category><![CDATA[terminal]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Wed, 16 Sep 2020 09:58:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677410470594/2ee1aaf2-b898-462c-9ac8-e592b7e62c68.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Installing Terraform on MacOS is pretty simple, we have several different ways of doing it:</p>
<ul>
<li><p>Using Homebrew</p>
</li>
<li><p>Manual Installation from binary</p>
</li>
<li><p>Using TFSwitch</p>
</li>
</ul>
<p>Official Terraform <a target="_blank" href="https://learn.hashicorp.com/tutorials/terraform/install-cli">documentation</a>.</p>
<h3 id="heading-using-tfswitch">Using TFSwitch</h3>
<p>I will start with the third method because I like the tool. TFSwitch is an open-source project that allows you to easily switch Terraform's version. TFSwitch is created and maintained by warrensbox, <a target="_blank" href="https://github.com/warrensbox/terraform-switcher">here the GitHub repo</a>.<br />Before starting make sure to uninstall Terraform if you have it already installed.</p>
<p>To install TFSwitch we can use homebrew:</p>
<pre><code class="lang-bash">brew install warrensbox/tap/tfswitch
</code></pre>
<h3 id="heading-using-homebrew">Using Homebrew</h3>
<p>If you want to stick to the classical Terraform installation, you can still use homebrew:</p>
<pre><code class="lang-bash">brew install hashicorp/tap/terraform
</code></pre>
<p>You can verify your current version with:</p>
<pre><code class="lang-bash">terraform -v
</code></pre>
<h3 id="heading-manual-install">Manual Install</h3>
<p>Terraform is distributed as a single binary, you can find the latest version on <a target="_blank" href="https://www.terraform.io/downloads.html">Terraform website</a>.<br />After downloading Terraform we can simply put the binary in a directory listed in <code>PATH</code>.</p>
<pre><code class="lang-bash">mv ~/Downloads/terraform /usr/<span class="hljs-built_in">local</span>/bin/
</code></pre>
<p>To check if the installation is working properly:</p>
<pre><code class="lang-bash">terraform -v
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Switching Terraform Versions Easily]]></title><description><![CDATA[If you are working on multiple Terraform projects at once, you probably also manage multiple versions of the tool. You may stick to an older version for various reasons, one of the most common is breaking changes in Terraform. A clear example of brea...]]></description><link>https://alessandromarinoac.com/switching-terraform-versions-easily</link><guid isPermaLink="true">https://alessandromarinoac.com/switching-terraform-versions-easily</guid><category><![CDATA[#IaC]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[macOS]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Mon, 14 Sep 2020 10:03:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677410501480/046245f5-43da-4ef3-b10e-629aa0401879.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are working on multiple Terraform projects at once, you probably also manage multiple versions of the tool. You may stick to an older version for various reasons, one of the most common is breaking changes in Terraform. A clear example of breaking changes is the switch from HCL 1 to HCL 2 which requires some work to update the stack, so you may stick with an older version for some weeks.</p>
<p>If you are on MacOS or Linux there is a really useful tool written by warrensbox called tfswitch. You can find it on <a target="_blank" href="https://github.com/warrensbox/terraform-switcher">GitHub</a>.</p>
<p>tfswitch allows you to easily switch between Terraform versions, and if you don't have the selected version yet, it will download and install it for you.</p>
<h3 id="heading-installation">Installation</h3>
<h4 id="heading-macos">MacOS</h4>
<p>You can install tfswitch with homebrew:</p>
<pre><code class="lang-bash">brew install warrensbox/tap/tfswitch
</code></pre>
<h4 id="heading-linux">Linux</h4>
<p>You can install tfswitch with curl script:</p>
<pre><code class="lang-bash">curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh | bash
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Terraform Numeric Functions Cheatsheet]]></title><description><![CDATA[This is a recap of Terraform numeric functions, to read the updated and complete documentation visit the hashicorp page.
min
min(set number) number
Given a set of numbers (or a compatible structure), this function returns the smallest one
>min(1,55,1...]]></description><link>https://alessandromarinoac.com/terraform-numeric-functions-cheatsheet</link><guid isPermaLink="true">https://alessandromarinoac.com/terraform-numeric-functions-cheatsheet</guid><category><![CDATA[#IaC]]></category><category><![CDATA[Terraform]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Wed, 27 May 2020 10:08:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677410579381/c43f9ae4-63c6-4dc9-9129-c9768a98fd59.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a recap of Terraform numeric functions, to read the updated and complete documentation visit the <a target="_blank" href="https://www.terraform.io/docs/configuration/functions.html">hashicorp page</a>.</p>
<h3 id="heading-min">min</h3>
<p><code>min(set number) number</code></p>
<p>Given a set of numbers (or a compatible structure), this function returns the smallest one</p>
<pre><code class="lang-plaintext">&gt;min(1,55,127,4,10)
1

&gt;min([55,4,10]...)
4
</code></pre>
<h3 id="heading-max">max</h3>
<p><code>max(set number) number</code></p>
<p>Given a set of numbers (or a compatible structure), this function returns the biggest one</p>
<pre><code class="lang-plaintext">&gt;max(1,55,127,4,10)
127

&gt;max([55,4,10]...)
55
</code></pre>
<h3 id="heading-parseint">parseint</h3>
<p><code>parseint(string stringNumber, number base) number</code></p>
<p>Given a string representation of a number and its base, this function will return an integer.<br />The base must be between 2 and 62 inclusive.</p>
<pre><code class="lang-plaintext">&gt;parseint("25", 10)
25

&gt;parseint("1FF", 16)
511

&gt;parseint("111", 2)
7
</code></pre>
<h3 id="heading-floor">floor</h3>
<p><code>floor(number num)</code></p>
<p>This function will return the closest whole number that is less or equal to the input number. The input number can be decimal.</p>
<pre><code class="lang-plaintext">&gt;floor(11.9)
11

&gt;floor(11)
11

floor(9.1)
9
</code></pre>
<h3 id="heading-ceil">ceil</h3>
<p><code>ceil(number num)</code></p>
<p>This function will return the closest whole number that is greater or equal to the input number. The input number can be decimal.</p>
<pre><code class="lang-plaintext">&gt;ceil(11.9)
12

&gt;floor(11)
11

floor(9.1)
10
</code></pre>
<h3 id="heading-abs">abs</h3>
<p><code>abs(number num) number</code></p>
<p>Given an integer number, <code>abs</code> will return its absolute value. If the input number is negative, it will be multiplied by <code>-1</code>.</p>
<pre><code class="lang-plaintext">&gt;abs(10)
10

&gt;abs(-10)
10

&gt;abs(0)
0
</code></pre>
<h3 id="heading-pow">pow</h3>
<p><code>pow(number num, number elevation)</code></p>
<p>This function will elevate the first argument <code>num</code> to the second argument <code>elevation</code>.</p>
<pre><code class="lang-plaintext">&gt;pow(4,2)
16

&gt;pow(3,1)
3

&gt;pow(9,0)
1
</code></pre>
<h3 id="heading-signum">signum</h3>
<p><code>signum(number num)</code></p>
<p>This function returns a number representing the symbol of the input number.</p>
<pre><code class="lang-plaintext">&gt;signum(-1)
-1

&gt;signum(1)
1

&gt;signum(9999)
1

&gt;signum(0)
0
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Build Small Docker Images with multi-stage builds]]></title><description><![CDATA[One of the hardest things to do when working with containers is maintaining the image size small.Adding resources and commands to Dockerfile is easy and fast, but cleaning the artifacts these commands produce is not.To keep the size small we can try ...]]></description><link>https://alessandromarinoac.com/build-small-docker-images-with-multi-stage-builds</link><guid isPermaLink="true">https://alessandromarinoac.com/build-small-docker-images-with-multi-stage-builds</guid><category><![CDATA[Docker]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Alessandro Marino]]></dc:creator><pubDate>Wed, 20 May 2020 10:35:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/jOqJbvo1P9g/upload/a5ea7b3ce2188f1fcf99c7682c26198e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the hardest things to do when working with containers is maintaining the image size small.<br />Adding resources and commands to Dockerfile is easy and fast, but cleaning the artifacts these commands produce is not.<br />To keep the size small we can try to remove all unused packages, and files and clean every cache we can think of with some scripts, but Docker offers us a better and simpler way to achieve these results: <strong>multi-stage builds</strong>.</p>
<h3 id="heading-what-are-multi-stage-builds">What are multi-stage builds?</h3>
<p>Multi-stage builds are a feature Docker has implemented to offer a better and cleaner organization of the Dockerfile. This is achieved using multiple sections inside a single Dockerfile, those sections are called stages.<br />Each stage allows you to use a different <code>FROM</code> statement (a different base image) and selectively copy artifacts from one stage to another.<br />When you build a Dockerfile containing multiple stages, Docker will build all those different stages separately. Before multi-stage builds the only way to achieve this, was to use, and maintain, different Docker files.</p>
<h3 id="heading-how-we-can-use-a-multi-stage-build-to-reduce-image-size">How we can use a multi-stage build to reduce image size?</h3>
<p>This feature allows you to bring your own artifacts between stages, so you can easily build your application in a different stage than the one that would run it.</p>
<h4 id="heading-why-this-is-an-advantage">Why this is an advantage?</h4>
<p>When building an application we need to install a lot of development dependencies, which maybe are not required to run the resulting artifacts. So we can easily discard all of them.<br />Surely you can try to remove all of those dependencies with some inverse commands but I will show you how simpler is to do that with multi-stage builds. An example can be packages installed with a package manager like <code>apk</code> or <code>apt</code>, which usually brings with them a lot of dependencies.</p>
<h5 id="heading-dockerfile">Dockerfile</h5>
<pre><code class="lang-plaintext">FROM golang:1.13-alpine3.10 AS build
RUN apk --no-cache add git
WORKDIR /app
COPY . .
ENV GO111MODULE=on
RUN go mod download
RUN GOOS=linux go build -ldflags="-s -w" -o ./test ./main.go
CMD ["/app/test"]

FROM scratch
WORKDIR /
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app/test /app
CMD ["/app"]
</code></pre>
<hr />
<h5 id="heading-maingo">main.go</h5>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"log"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    log.Println(<span class="hljs-string">"Test"</span>)
}
</code></pre>
<h3 id="heading-how-it-works">How it works</h3>
<p>In this multi-stage Dockerfile, we build our Go package in a stage called <code>build</code> and we get the resulting executable in a clean stage based on a <code>scratch</code> image.<br />So the actual image produced is only the size of the base image (<code>scratch</code>) and the executable that, in this case, is only some KB large.</p>
<h3 id="heading-results">Results</h3>
<ul>
<li><p>Image produced with multi-stage build: <code>1.74MB</code></p>
</li>
<li><p>Image produced with classical single stage build from Go image: <code>79MB</code></p>
</li>
</ul>
<p>With some optimization, the classic build size can also be reduced, example of those optimizations are cache cleaning, and file removal...<br />Even with those optimizations, the size difference is huge and definitively notable.</p>
]]></content:encoded></item></channel></rss>