Advertise with Googlier.com Ahmed Alani http://www.ahmedalani.com/index.xml Recent content on Ahmed Alani Hugo -- gohugo.io en-us Mon, 18 Sep 2017 15:07:00 -0500 Moya, Decodable, and RxSwift Bindings http://www.ahmedalani.com/post/moya-decodable-rxswift-bindings/ Mon, 18 Sep 2017 15:07:00 -0500 http://www.ahmedalani.com/post/moya-decodable-rxswift-bindings/ <p>If your application integrates with a REST API, one of the first things you&rsquo;ll implement is a network abstraction and serialization layer. Moya and Decodable are examples of libraries that make this dead simple for Swift / iOS. You <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" target="_blank">don&rsquo;t want to repeat</a> the same boilerplate code accessing each resource, so let&rsquo;s create generic extensions that will make our life easier.</p> <p></p> <blockquote> <blockquote> <blockquote> <p>Moya and Decodable</p> </blockquote> </blockquote> </blockquote> <p><a href="https://github.com/Moya/Moya" target="_blank">Moya</a> is a thin facade around the Alamofire HTTP client. Instead of writing <code>Alamofire.request(&quot;https://api.mysite.com/v1/orders/\(userId)&quot;)</code> and managing parameters, paths, encoding, etc., we can implement <a href="https://github.com/Moya/Moya/blob/master/Sources/Moya/TargetType.swift" target="_blank">Moya&rsquo;s protocol</a> and access an endpoint via enum: <code>MoyaProvider&lt;MyApi&gt;().request(.getOrders(userId))</code>. Not only does this keep our API configuration encapsulated and consistent, but it&rsquo;s much easier to read. The API call returns a <code>Moya.Response</code>, which ultimately needs to be converted into a model.</p> <p><a href="https://github.com/Anviking/Decodable" target="_blank">Decodable</a> is an object mapping library that handles JSON serialization and deserialization into Swift objects. Mappings are specified as extensions on models and&ndash;once specified&ndash;deserialization is a simple call to the <code>decode</code> extension: <code>let orders = try [Order].decode(json)</code>.</p> <p>Even in the simplest case of making a request and mapping a response, we want to avoid writing the above code for every single resource we interact with. Here&rsquo;s a generic extension that will handle mapping for us against any response; all we need to provide is the type information:</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span><span style="color: #006699; font-weight: bold">public</span> <span style="color: #006699; font-weight: bold">extension</span> <span style="color: #00AA88; font-weight: bold">Moya</span>.Response { <span style="color: #006699; font-weight: bold">public</span> <span style="color: #006699; font-weight: bold">func</span> <span style="color: #CC00FF">map</span>&lt;T: Decodable&gt;(<span style="color: #006699; font-weight: bold">_</span> type: T.<span style="color: #006699; font-weight: bold">Type</span>) throws -&gt; T { <span style="color: #006699; font-weight: bold">return</span> try type.decode(try <span style="color: #006699; font-weight: bold">self</span>.mapJSON()) } <span style="color: #006699; font-weight: bold">public</span> <span style="color: #006699; font-weight: bold">func</span> <span style="color: #CC00FF">map</span>&lt;T: Decodable&gt;(<span style="color: #006699; font-weight: bold">_</span> type: [T.<span style="color: #006699; font-weight: bold">Type</span>]) throws -&gt; [T] { <span style="color: #006699; font-weight: bold">return</span> try <span style="color: #336666">Array</span>&lt;T&gt;.decode(try <span style="color: #006699; font-weight: bold">self</span>.mapJSON()) } } </pre></div> <ul> <li>Here we see two <code>map</code> methods, the first for returning a single mapped object and second for an array of objects.</li> <li>The <code>self.mapJSON()</code> call is just a Moya extension that will convert a Response to JSON using <code>JSONSerialization.jsonObject</code>. Feel free to use your own serialization code.</li> </ul> <p>The net result is the API calls are now fluent, succinct, and readable for all resources:</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span><span style="color: #006699; font-weight: bold">internal</span> <span style="color: #006699; font-weight: bold">func</span> <span style="color: #CC00FF">getOrders</span>(userId: <span style="color: #336666">String</span>) throws -&gt; [Orders] { <span style="color: #006699; font-weight: bold">return</span> provider.request(.getOrders(userId)) .<span style="color: #336666">map</span>([Orders.<span style="color: #006699; font-weight: bold">self</span>]) } </pre></div> <blockquote> <blockquote> <blockquote> <p>RxSwift-ifying our Code</p> </blockquote> </blockquote> </blockquote> <p>If you use <a href="https://github.com/ReactiveX/RxSwift" target="_blank">RxSwift</a> and want to utilize the above extensions in an Observable chain, we can employ the same strategy with the <code>flatMap</code> and <code>just</code> operators:</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span><span style="color: #006699; font-weight: bold">public</span> <span style="color: #006699; font-weight: bold">extension</span> <span style="color: #00AA88; font-weight: bold">ObservableType</span> <span style="color: #006699; font-weight: bold">where</span> E == Moya.Response { <span style="color: #006699; font-weight: bold">public</span> <span style="color: #006699; font-weight: bold">func</span> <span style="color: #CC00FF">map</span>&lt;T: Decodable&gt;(<span style="color: #006699; font-weight: bold">_</span> type: T.<span style="color: #006699; font-weight: bold">Type</span>) -&gt; Observable&lt;T&gt; { <span style="color: #006699; font-weight: bold">return</span> flatMap { (response : Response) -&gt; Observable&lt;T&gt; <span style="color: #006699; font-weight: bold">in</span> <span style="color: #006699; font-weight: bold">return</span> Observable.just(try response.<span style="color: #336666">map</span>(type)) } } <span style="color: #006699; font-weight: bold">public</span> <span style="color: #006699; font-weight: bold">func</span> <span style="color: #CC00FF">map</span>&lt;T: Decodable&gt;(<span style="color: #006699; font-weight: bold">_</span> type: [T.<span style="color: #006699; font-weight: bold">Type</span>]) -&gt; Observable<span style="color: #555555">&lt;</span>[T]<span style="color: #555555">&gt;</span> { <span style="color: #006699; font-weight: bold">return</span> flatMap { (response : Response) -&gt; Observable<span style="color: #555555">&lt;</span>[T]<span style="color: #555555">&gt;</span> <span style="color: #006699; font-weight: bold">in</span> <span style="color: #006699; font-weight: bold">return</span> Observable.just(try response.<span style="color: #336666">map</span>(type)) } } } </pre></div> <p>The reactive code is now just as clean and easy to read as the non-reactive code:</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>provider.rx.request(.getOrders(userId)) .<span style="color: #336666">map</span>([Orders.<span style="color: #006699; font-weight: bold">self</span>]) .subscribe { orders <span style="color: #006699; font-weight: bold">in</span> <span style="color: #0099FF; font-style: italic">// Handle orders ...</span> } </pre></div> <blockquote> <blockquote> <blockquote> <p>Improvements</p> </blockquote> </blockquote> </blockquote> <p>As an exercise, I suggest you consider these improvements for a &ldquo;real&rdquo; implementations:</p> <ol> <li>Utilize ViewModels. Your API call(s) may return data that does not exactly conform to your view. Are there any well established design patterns that can help you handle this separation of concerns? How would this code work with the above examples?</li> <li>Errors in an RxSwift observable chain will terminate a subscription, which is something you want to avoid in most cases. If any sort of error occurs in the above examples (network issue, mapping issue, etc.), our subscription will be terminated and updates will cease. How might you handle this problem?</li> </ol> <p>All of the above code is available on GitHub <a href="https://github.com/alani/Moya-Decodable" target="_blank">here</a>.</p> Dockerfiles for Hugo and S3 Website http://www.ahmedalani.com/post/dockerfiles-hugo-s3-site/ Thu, 24 Aug 2017 15:07:00 -0500 http://www.ahmedalani.com/post/dockerfiles-hugo-s3-site/ <p>In the <a href="http://www.ahmedalani.com/post/so-recursive-it-hurts/">last post</a> we used two docker images to publish a static site, one for encapsulating our Hugo process and another for the S3 website tool. I received a few direct messages asking how to customize these images. Here&rsquo;s a quick addendum to show you the Dockerfiles for both.</p> <p></p> <blockquote> <blockquote> <blockquote> <p>Hugo Image</p> </blockquote> </blockquote> </blockquote> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span><span style="color: #006699; font-weight: bold">FROM</span><span style="color: #CC3300"> gliderlabs/alpine:latest</span> <span style="color: #006699; font-weight: bold">MAINTAINER</span><span style="color: #CC3300"> Ahmed Alani</span> <span style="color: #006699; font-weight: bold">ENV</span><span style="color: #CC3300"> HUGO_VERSION=0.20</span> <span style="color: #006699; font-weight: bold">RUN</span> apk add --update <span style="color: #CC3300; font-weight: bold">\</span> wget <span style="color: #CC3300; font-weight: bold">\</span> ca-certificates <span style="color: #CC3300; font-weight: bold">\</span> python <span style="color: #CC3300; font-weight: bold">\</span> python-dev <span style="color: #CC3300; font-weight: bold">\</span> py-pip <span style="color: #CC3300; font-weight: bold">\</span> bash <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> pip install pygments <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> wget https://github.com/spf13/hugo/releases/download/v<span style="color: #AA0000">${</span><span style="color: #003333">HUGO_VERSION</span><span style="color: #AA0000">}</span>/hugo_<span style="color: #AA0000">${</span><span style="color: #003333">HUGO_VERSION</span><span style="color: #AA0000">}</span>_linux-64bit.tar.gz <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> tar xzf hugo_<span style="color: #AA0000">${</span><span style="color: #003333">HUGO_VERSION</span><span style="color: #AA0000">}</span>_linux-64bit.tar.gz <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> rm hugo_<span style="color: #AA0000">${</span><span style="color: #003333">HUGO_VERSION</span><span style="color: #AA0000">}</span>_linux-64bit.tar.gz <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> mkdir -p /usr/local/bin <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> mv hugo_<span style="color: #AA0000">${</span><span style="color: #003333">HUGO_VERSION</span><span style="color: #AA0000">}</span>_linux_amd64/hugo* /usr/local/bin/hugo <span style="color: #006699; font-weight: bold">VOLUME</span><span style="color: #CC3300"> /src</span> <span style="color: #006699; font-weight: bold">WORKDIR</span><span style="color: #CC3300"> /src</span> <span style="color: #006699; font-weight: bold">ENTRYPOINT</span><span style="color: #CC3300"> [&quot;hugo&quot;]</span> </pre></div> <p><br /></p> <p>View the <a href="https://github.com" target="_blank">GitHub Source</a>. If you need details regarding the Dockerfile syntax, see <a href="https://docs.docker.com/engine/reference/builder/" target="_blank">the reference</a>. Quick notes:</p> <ul> <li><code>FROM gliderlabs/alpine:latest</code> - We&rsquo;re using alpine linux because it&rsquo;s a lightweight distro (&lt; 5MB) and gives us access to the <em>apk</em> package manager for installing dependencies.</li> <li><code>ENV HUGO_VERSION=0.20</code> - This specifies the default version of Hugo we want to download. You can override this while building the image, e.g. <code>$ docker build --build-arg HUGO_VERSION=0.XX .</code></li> </ul> <blockquote> <blockquote> <blockquote> <p>s3_website Image</p> </blockquote> </blockquote> </blockquote> <p><div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span><span style="color: #006699; font-weight: bold">FROM</span><span style="color: #CC3300"> anapsix/alpine-java:8_jdk</span> <span style="color: #006699; font-weight: bold">MAINTAINER</span><span style="color: #CC3300"> Ahmed Alani</span> <span style="color: #006699; font-weight: bold">RUN</span> apk add --update <span style="color: #CC3300; font-weight: bold">\</span> bash <span style="color: #CC3300; font-weight: bold">\</span> curl-dev <span style="color: #CC3300; font-weight: bold">\</span> ruby <span style="color: #CC3300; font-weight: bold">\</span> ruby-dev <span style="color: #CC3300; font-weight: bold">\</span> ruby-irb <span style="color: #CC3300; font-weight: bold">\</span> ruby-rdoc <span style="color: #CC3300; font-weight: bold">\</span> ruby-rake <span style="color: #CC3300; font-weight: bold">\</span> ruby-io-console <span style="color: #CC3300; font-weight: bold">\</span> ruby-bundler <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> gem install s3_website --no-ri --no-rdoc <span style="color: #555555">&amp;&amp;</span> <span style="color: #CC3300; font-weight: bold">\</span> s3_website install <span style="color: #006699; font-weight: bold">VOLUME</span><span style="color: #CC3300"> /src</span> <span style="color: #006699; font-weight: bold">WORKDIR</span><span style="color: #CC3300"> /src</span> <span style="color: #006699; font-weight: bold">ENTRYPOINT</span><span style="color: #CC3300"> [&quot;s3_website&quot;]</span> </pre></div> </p> <p>View the <a href="https://github.com" target="_blank">GitHub Source</a>. Quick notes:</p> <ul> <li><code>FROM anapsix/alpine-java:8_jdk</code> - We&rsquo;re still using alpine linux, but this comes with the Java 8 JDK pre-installed, which is a pre-requisite for s3_website.</li> <li><code>gem install s3_website</code> - We&rsquo;re not specifying the version for the s3_website gem in order to grab the latest by default. This isn&rsquo;t a good practice, but you can include that improvement in your own image.</li> </ul> So Recursive It Hurts http://www.ahmedalani.com/post/so-recursive-it-hurts/ Sat, 06 Feb 2016 11:53:00 -0500 http://www.ahmedalani.com/post/so-recursive-it-hurts/ <p><a href="https://gohugo.io/">Hugo</a> is the new kid on the block in the world of static site generators. <a href="https://www.docker.com/">Docker</a> has set the DevOps world ablaze by popularizing container technology. Naturally, I&rsquo;ll be perpetuating the hype train for both with my blog. Let&rsquo;s run Hugo in a Docker container, generate my website, and host it using Amazon S3.</p> <p></p> <blockquote> <blockquote> <blockquote> <p>Why Hugo?</p> </blockquote> </blockquote> </blockquote> <p>If I want to host a blog using a platform like Wordpress, I need to provision, maintain, and (most importantly) pay for a <a href="https://en.wikipedia.org/wiki/LAMP_(software_bundle)" target="_blank">LAMP stack</a> just to get started. Don&rsquo;t get me wrong, I like Wordpress and I&rsquo;ve hosted sites with it in the past. It comes with a rich set of features like post scheduling, WYSYWIG editors, extensive plugins, and so on. However, for my personal blog that&rsquo;s entirely too much work. Static site generators like Hugo follow a different model. I download a website template, write a post in Markdown, then Hugo renders my content into a full HTML. Now I can host my site anywhere and all I need to worry about are bandwidth fees.</p> <p><img src="http://www.ahmedalani.com/images/hugo.png"></p> <p>Hugo comes with <a href="https://gohugo.io/overview/installing/" target="_blank">several pre-built binaries</a> that allow you to run it out of the box on most OS&rsquo;s. Sadly, extra features like syntax highlighting requires <a href="https://gohugo.io/extras/highlighting/" target="_blank">installing additional dependencies</a> to get up and running. I&rsquo;m lazy, so memorizing OS-specific commands for a project is an immediate no-go. Which version of Python do I install? Which packages do I download with pip? Let&rsquo;s use Docker containers to run Hugo with batteries included.</p> <blockquote> <blockquote> <blockquote> <p>Docker to the rescue</p> </blockquote> </blockquote> </blockquote> <p>Using Docker, we can create a tiny sandboxed environment&mdash;a container&mdash;that bundles everything we need to run Hugo. Containers can include almost anything, such as a filesystem, OS binaries, the Hugo binary, and additional dependencies for extra features. This is basically static linking on steroids. I no longer have to remember custom install instructions per machine to get the full Hugo experience. I just download the Docker image and I’m ready to go. Note that Docker is supported by <a href="https://docs.docker.com/engine/installation/#supported-platforms" target="_blank">several operating systems</a>.</p> <p><img src="http://www.ahmedalani.com/images/dockerhugo.png"></p> <blockquote> <p>Run Hugo with Docker to build our site</p> </blockquote> <p>Images for containers can be shared, which means that once a Hugo image is defined, anyone with Docker installed can download and run it. Let&rsquo;s grab Hugo from the public image I <a href="https://hub.docker.com/r/alani/hugo/" target="_blank">uploaded to Docker Hub</a>.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ docker pull alani/hugo </pre></div> <p>Now that we have an image for Hugo, we can run a container with it and use Hugo to create a new site.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ mkdir ahmedalani.com $ <span style="color: #336666">cd</span> ahmedalani.com $ docker run --rm -v <span style="color: #CC3300">`</span><span style="color: #336666">pwd</span><span style="color: #CC3300">`</span>:/src alani/hugo new site . </pre></div> <p>Breaking down the command quickly:</p> <ul> <li><code>docker run --rm</code> - Run a temporary container. This means it will not exist after we exit.</li> <li><code>-v `pwd`:/src</code> - Mount the <em>/src</em> volume in the container to the current directory on my machine, <em>/ahmedalani.com</em>. The /src volume is the working directory in the container.</li> <li><code>alani/hugo new site .</code> - Run the container using the alani/hugo image we just downloaded. Because the container represents the Hugo process, pass it additional parameters <em>new site .</em> This will generate a Hugo site in the container&rsquo;s working directory, and subsequently, our host machine&rsquo;s directory.</li> </ul> <p>At this point if we <code>ls</code>, our directory should contain a Hugo site template.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>- ahmedalani.com ----- archetypes<span style="color: #CC3300; font-weight: bold">\</span> ----- config.yaml ----- content<span style="color: #CC3300; font-weight: bold">\</span> --------- posts<span style="color: #CC3300; font-weight: bold">\</span> ----- data<span style="color: #CC3300; font-weight: bold">\</span> ----- layouts<span style="color: #CC3300; font-weight: bold">\</span> ----- static<span style="color: #CC3300; font-weight: bold">\</span> ----- ... </pre></div> <p>Time to create out first post. Let&rsquo;s also make running the container easier by using the <code>alias</code> command.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ <span style="color: #336666">alias</span> <span style="color: #003333">hugo</span><span style="color: #555555">=</span><span style="color: #CC3300">&quot;docker run -it --rm -v `pwd`:/src -p 1313:1313 alani/hugo&quot;</span> $ hugo new post/so-recursive-it-hurts.md /src/content/post/so-recursive-it-hurts.md created </pre></div> <p>After adding some content (like this blog post), we can preview the blog locally using Hugo&rsquo;s built-in webserver.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ hugo server -w --baseUrl<span style="color: #555555">=</span><span style="color: #CC3300">&quot;http://localhost:1313&quot;</span> --bind<span style="color: #555555">=</span>0.0.0.0 <span style="color: #FF6600">0</span> of <span style="color: #FF6600">0</span> draft rendered <span style="color: #FF6600">0</span> future content <span style="color: #FF6600">1</span> pages created <span style="color: #FF6600">0</span> paginator pages created <span style="color: #FF6600">0</span> tags created <span style="color: #FF6600">0</span> categories created in <span style="color: #FF6600">34</span> ms Watching <span style="color: #006699; font-weight: bold">for</span> changes in /src/<span style="color: #555555">{</span>data,content,layouts,static,themes/hugo-incorporated<span style="color: #555555">}</span> Serving pages from /src/public Web Server is available at http://0.0.0.0:1313/ Press Ctrl+C to stop </pre></div> <p><img src="http://www.ahmedalani.com/images/morerecursion.png"></p> <p>We&rsquo;re up! Hugo will launch a web server in the container and serve up the site on port 1313. It will also watch the source code in the <em>/src</em> volume and regenerate + live reload the site if we make any edits.</p> <p>Quick side notes:</p> <ul> <li>In the <code>alias</code> command, I bound the container&rsquo;s port 1313 to my host port 1313, otherwise I wouldn&rsquo;t have been able to access the site from my browser.</li> <li><code>Ctrl+C</code> will cause the Hugo web server to stop and this will exit our container.</li> </ul> <blockquote> <p>Hosting on Amazon S3</p> </blockquote> <p>The final step is to host our static site somewhere. This is fairly trivial to do with Amazon S3 and gives us the benefit of Amazon&rsquo;s reliability and CDN. Follow <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html">this guide</a> to set up an S3 bucket with correct permissions.</p> <p>We can use the handy <a href="https://github.com/laurilehmijoki/s3_website">s3_website</a> tool to synchronize files with our S3 bucket when we make an update. And because I&rsquo;ve discovered a shiny new hammer, I&rsquo;ve created a Docker image that encapsulates s3_website and its dependencies as well.</p> <p>Pull down the image.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ docker pull alani/s3website </pre></div> <p>Create the S3 config and add your bucket information, including AWS credentials. <strong>Don&rsquo;t check s3config.yml into source control.</strong></p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ <span style="color: #336666">alias</span> <span style="color: #003333">s3website</span><span style="color: #555555">=</span><span style="color: #CC3300">&quot;docker run -i -t --rm -v `pwd`:/src alani/s3website&quot;</span> $ s3website cfg create <span style="color: #0099FF; font-style: italic"># Creates s3config.yml</span> $ open s3config.yml <span style="color: #0099FF; font-style: italic"># Add your S3 information &amp; credentials. Don&#39;t check in</span> </pre></div> <p>It&rsquo;s time to generate the final set of files and upload to S3.</p> <div class="highlight" style="background: #f0f3f3"><pre style="line-height: 125%"><span></span>$ hugo --baseUrl<span style="color: #555555">=</span><span style="color: #CC3300">&quot;http://ahmedalani.com&quot;</span> <span style="color: #FF6600">0</span> of <span style="color: #FF6600">0</span> drafts rendered <span style="color: #FF6600">0</span> future content <span style="color: #FF6600">1</span> pages created <span style="color: #FF6600">0</span> paginator pages created <span style="color: #FF6600">0</span> tags created <span style="color: #FF6600">0</span> categories created in <span style="color: #FF6600">107</span> ms $ s3website push --site public <span style="color: #555555">[</span>info<span style="color: #555555">]</span> Deploying public/* to ahmedalani.com <span style="color: #555555">[</span>succ<span style="color: #555555">]</span> Created index.xml <span style="color: #555555">(</span>application/rss+xml<span style="color: #555555">)</span> ... <span style="color: #555555">[</span>info<span style="color: #555555">]</span> Summary: Created <span style="color: #FF6600">39</span> files. Transferred 1.6 MB, 2.1 MB/s. <span style="color: #555555">[</span>info<span style="color: #555555">]</span> Successfully pushed the website to http://ahmedalani.com.s3-website-us-east-1.amazonaws.com </pre></div> <blockquote> <p>Final Thoughts</p> </blockquote> <p>I wrote this article to see if Docker can serve as a useful wrapper for everyday tools and so far I&rsquo;m pretty happy using it across multiple machines. As you&rsquo;ve probably deduced, dockerizing tools can get out of control if we start creating bloated containers for every single simple process. Try to build and use lean images and, at the end of the day, remember that this is just another tool in your belt.</p>