<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: t3chflicks</title>
    <description>The latest articles on DEV Community by t3chflicks (@t3chflicks).</description>
    <link>https://dev.to/t3chflicks</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F517087%2Fc471473c-a9d3-48b0-8360-1749afafd285.png</url>
      <title>DEV Community: t3chflicks</title>
      <link>https://dev.to/t3chflicks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/t3chflicks"/>
    <language>en</language>
    <item>
      <title>Emoji Search 🔮 Creating, Deploying and Evaluating a Machine Learning Service</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Wed, 14 Jul 2021 13:02:28 +0000</pubDate>
      <link>https://dev.to/t3chflicks/emoji-search-creating-deploying-and-evaluating-a-machine-learning-service-3c2f</link>
      <guid>https://dev.to/t3chflicks/emoji-search-creating-deploying-and-evaluating-a-machine-learning-service-3c2f</guid>
      <description>&lt;p&gt;Computers can model language in such a way that they can perform well on tasks such as text similarity. In this article, I attempt to create an Emoji suggestion tool which uses vector word embeddings to determine the relevant Emoji recommendation. I also deploy the machine learning model as a service on AWS and dive into cost estimations. A live example of the service can be found on the T3chFlicks site; all the code is open sourced.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/emoji-search/blob/master/Emoji_Text_Search_Notebook.ipynb"&gt;🔗 Find All the Emoji Search Code On Github📔&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Emoji 😂 ❤️
&lt;/h2&gt;

&lt;p&gt;Emojis are common-place in internet communication, the tiny intricate pictograms have opened the door to quick thumb tap summarisation.&lt;/p&gt;

&lt;p&gt;Computers represent Emojis using the &lt;a href="https://home.unicode.org/"&gt;Unicode Standard&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code: U+1F600 (Unicode)
      \xF0\x9F\x98\x83 (UTF-8 Bytes) 
Emoji: 😀           
Description: grinning face
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The popularity of Emojis differs massively across societies and cultures, but Unicode publish some stats on their proportional use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xk3an31I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/1%2AN0_wL0A3CIm-qInu9ioU2Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xk3an31I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/1%2AN0_wL0A3CIm-qInu9ioU2Q.png" alt="[https://home.unicode.org/emoji/emoji-frequency/](https://home.unicode.org/emoji/emoji-frequency/)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://home.unicode.org/emoji/emoji-frequency/"&gt;https://home.unicode.org/emoji/emoji-frequency/&lt;/a&gt;*&lt;/p&gt;

&lt;h2&gt;
  
  
  What do we want?
&lt;/h2&gt;

&lt;p&gt;During project conception we decided that we wanted the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To enter a search text and in response get a list of similar Emojis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To run a machine learning model in &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To use this service to improve the efficiency of adding Emojis to our articles.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build — Emoji Search
&lt;/h2&gt;

&lt;p&gt;To be able to search text and be suggested the most similar Emojis, we need to find the similarity of the search text with the Emojis.&lt;/p&gt;

&lt;p&gt;First we must try to compare apples with apples, and luckily Emojis in the Unicode Standard have a short description of the image, using these we can compare the two strings &lt;code&gt;search_text vs. emoji_description&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;One current technique for modelling language is using word embeddings — a vector space where the dimensions and relations represent meaning between words.&lt;/p&gt;

&lt;p&gt;We really recommend the following video for understanding the concept of embeddings:&lt;/p&gt;


&lt;center&gt;&lt;/center&gt;
&lt;h3&gt;
  
  
  Searching Emojis using Word Embeddings
&lt;/h3&gt;

&lt;p&gt;We’re using &lt;a href="https://code.google.com/archive/p/word2vec/"&gt;Google News Vectors&lt;/a&gt; from 2019 as a language model, this isn’t necessarily the best choice (newer models exist such as &lt;a href="https://github.com/google-research/bert"&gt;Bert&lt;/a&gt;), but it will definitely be up for the task. A condensed version of the model can be found &lt;a href="https://github.com/eyaler/word2vec-slim"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A simple way to investigate the learned representations is to find the closest words for a user-specified word. [&lt;a href="https://code.google.com/archive/p/word2vec/"&gt;source&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We first load the model in the &lt;a href="https://en.wikipedia.org/wiki/Word2vec"&gt;*word2vec&lt;/a&gt;* format:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import gensim
GOOGLE_NEWS = os.path.join(FIXTURES_FOLDER, "GoogleNews-vectors-negative300-SLIM.bin.gz")

model = gensim.models.KeyedVectors.load_word2vec_format(GOOGLE_NEWS, binary=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This model is used to process Emoji data. Luckily, all the information we need is found in a Python library called &lt;a href="https://pypi.org/project/emoji-data/"&gt;emoji_data&lt;/a&gt;. We start by extracting the Emoji text into a &lt;a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html"&gt;Pandas data frame&lt;/a&gt;. For simplicity, we only keep the standard yellow Emoji set.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from emoji_data import EmojiSequence
d = {'emoji': [], 'description': []}
for (emoji, emoji_meta) in EmojiSequence:
    d['emoji'].append(emoji)
    d['description'].append(emoji_meta.description)
df = pd.DataFrame(d)
df['description'] = df['description'].str.split(' skin tone').str[0].str.replace(':', '').str.replace(',', '')
df = df.drop_duplicates(subset=['description'])

---------------------------------

emoji   description
👨‍❤️‍👨      [couple with heart man man]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The vector of each word in the description text for the Emoji is averaged into a vector for that Emoji:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_words(text):
    words = text.split(" ")
    word_vectors = [ model.syn0norm[model.vocab[word].index] for word in words if word in model.vocab]
    if len(word_vectors) &amp;gt; 0:
        mean_vector = np.array(word_vectors).mean(axis=0)
        unit_vector = gensim.matutils.unitvec(mean_vector).astype(np.float32).tolist()
    else:
        unit_vector = np.zeros(model.vector_size, ).tolist()
    return unit_vector

df['vector'] = df['description'].apply(process_words)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For example, using the search text ‘dog’ and &lt;a href="https://en.wikipedia.org/wiki/Cosine_similarity"&gt;cosine similarity&lt;/a&gt; of &lt;code&gt;emoji_vector vs. search_vector&lt;/code&gt; we can establish the similarity of all the Emojis. We can then select the top two.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from sklearn.metrics.pairwise import cosine_similarity

def find_similarity_to_search(search_vector):
    def func(emoji_vector):
        b_emoji = np.array(emoji_vector)
        cos_sim = dot(search_vector, b_emoji) / (norm(search_vector) * norm(b_emoji))
        return cos_sim
    return func

search_text = "dog"
search_vector = np.array(process_words(search_text))
find_similarity_to_search = find_similarity_to_search(search_vector)
df['similarity'] = df['vector'].apply(find_similarity_to_search)
df.nlargest(2, 'similarity')

--------------------

emoji similarity description
🐕 1.000000 dog
🌭 0.775372 hot dog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Whilst that example is rather plain, here is another that made us chuckle and think the service works well:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;search: "giant obstacle"

-------------------
emoji similarity description
🦣 0.416749 mammoth
🤘🏽 0.352729 sign of the horns medium
🧗🏽‍♂️ 0.351723 man climbing medium
🐉 0.330775 dragon
🗺️ 0.330681 world map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Architecture of the Emoji Search API
&lt;/h3&gt;

&lt;p&gt;The Python script above can be run on the Cloud and made accessible via the internet as an API. We use AWS as our Cloud provider with &lt;a href="https://aws.amazon.com/cloudformation/"&gt;CloudFormation&lt;/a&gt; to write infrastructure as code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X_1n83eC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Al6HnXwBZSu0ALFpoPFEaIA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X_1n83eC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Al6HnXwBZSu0ALFpoPFEaIA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture uses an &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html"&gt;Application Load Balancer&lt;/a&gt; with &lt;a href="https://aws.amazon.com/lambda/"&gt;Lambda&lt;/a&gt; as an API, we have already discussed this in a previous article:&lt;br&gt;
&lt;a href="https://t3chflicks.medium.com/cheaper-than-api-gateway-alb-with-lambda-using-cloudformation-b32b126bbddc"&gt;&lt;strong&gt;Cheaper than API Gateway — ALB with Lambda using CloudFormation&lt;/strong&gt;&lt;br&gt;
*An alternative to API gateway is Application Load Balancer. ALB can be connected with Lambda to produce a highly…*t3chflicks.medium.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Lambda supports using &lt;a href="https://www.docker.com/resources/what-container"&gt;docker containers&lt;/a&gt;, this allows us to run the Lambda locally for development and testing. The script is packaged into the docker container with the Google News Vectors as this makes it accessible at runtime without requiring downloading from &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; or &lt;a href="https://aws.amazon.com/efs/"&gt;EFS&lt;/a&gt;. The Lambda required 1.5GB memory to run the model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deployment of the Service
&lt;/h3&gt;

&lt;p&gt;Deployment pipelines can be created on AWS using &lt;a href="https://aws.amazon.com/codepipeline/"&gt;CodePipeline&lt;/a&gt;. This process is triggered by commits to a Github repository. It follows a sequence of defined steps, beginning with provisioning the infrastructure for the service, then testing the code, building a docker container and finally updating the Lambda:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n-M-xTEd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3992/1%2AntZRjPJ1pM97PgDr8sJ3mg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n-M-xTEd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3992/1%2AntZRjPJ1pM97PgDr8sJ3mg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to the super developer &lt;a href="https://james-turner.medium.com/"&gt;James Turner&lt;/a&gt; for showing us how to create CI/CD pipelines on AWS that are triggered by Github commits:&lt;br&gt;
&lt;a href="https://aws.plainenglish.io/from-github-to-continuous-deployment-in-5-minutes-7f9c1c7702b1"&gt;&lt;strong&gt;From GitHub to Continuous Deployment in 5 Minutes&lt;/strong&gt;&lt;br&gt;
*How to go from having a Github repository to having a CD pipeline in AWS where you can run tests, and continually…*aws.plainenglish.io&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Interface
&lt;/h3&gt;

&lt;p&gt;We made a widget that allows users to easily play with the API and view the &lt;a href="https://json-schema.org/"&gt;JSON schemas&lt;/a&gt; which define the input and output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4geQGEtp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4620/1%2AUzyjbN5_1Xn7N4WUH9YzzA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4geQGEtp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4620/1%2AUzyjbN5_1Xn7N4WUH9YzzA.png" alt="This widget allows you to interface with the API. Visit [https://t3chflicks.org/services/emoji-search](https://t3chflicks.org/services/emoji-search) to play!"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;This widget allows you to interface with the API. Visit &lt;a href="https://t3chflicks.org/services/emoji-search"&gt;https://t3chflicks.org/services/emoji-search&lt;/a&gt; to play!*&lt;/p&gt;

&lt;p&gt;The programmatic use of the API is as follows:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url '[https://api.t3chflicks.org./emoji-search/recommendations'](https://api.t3chflicks.org./emoji-search/recommendations') \
  -H 'content-type: application/json' \
  --data-raw '{"key":"4aac6db6d8dfc3fa7693b8b5918b3d548e3c92ebd70832447bab247a1c0b0b477d9107ecfe05f8153ef6d3d848614de3cb8bcf42e256aac1444eb828be1f4083","search":"Dog","quantity":5}' \
  --compressed

&amp;gt;&amp;gt;&amp;gt; {"most_similar": [{"score": 0.6978708999734073, "emoji": "\ud83d\udc15", "description": "dog"}, {"score": 0.5574100960192504, "emoji": "\ud83c\udf2d", "description": "hot dog"}, {"score": 0.5498123048808492, "emoji": "\ud83e\uddae", "description": "guide dog"}, {"score": 0.5273177453464898, "emoji": "\ud83d\udc08", "description": "cat"}, {"score": 0.4822364083781162, "emoji": "\ud83d\udc15\u200d\ud83e\uddba", "description": "service dog"}]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://t3chflicks.org/services/emoji-search"&gt;🤖 The widget can be found on our site 🤖&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Cost Estimation
&lt;/h2&gt;

&lt;p&gt;Determining the cost of running the Emoji Search service is similar what we have done in a previous article:&lt;br&gt;
&lt;a href="https://t3chflicks.medium.com/giving-away-free-apis-without-going-broke-cd87a7dc78a5"&gt;&lt;strong&gt;Giving away free APIs without going broke.&lt;/strong&gt;&lt;br&gt;
*I like to write articles on Medium. I also want to share the same articles on our own site without having to rewrite…*t3chflicks.medium.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the architecture described the cost equation is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total cost = Application Load Balancer cost + Lambda cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will continue with the assumptions of &lt;code&gt;**4350 Requests/Month&lt;/code&gt;** from 100 users using their 100 free credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Cost
&lt;/h3&gt;

&lt;p&gt;The Emoji Search Lambda has the following response stats:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;542 ms on average @ 1536 MB memory (cold start 1362ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These numbers can be plugged into the &lt;a href="https://aws.amazon.com/lambda/pricing/"&gt;Lambda cost calculator&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XDGXFDGu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2832/1%2AwCJI6Dk2_mm-IKcuoqtisQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XDGXFDGu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2832/1%2AwCJI6Dk2_mm-IKcuoqtisQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A nearly negligible cost at &lt;code&gt;$0.06 / Month&lt;/code&gt;. However, when scaling into the millions of requests, the service does begin to become quite costly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yUoNC0bK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2816/1%2AAwWnHTMvvUk2q4HKUL9yBw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yUoNC0bK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2816/1%2AAwWnHTMvvUk2q4HKUL9yBw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the scenario that we did receive this sort of request rate, we would undoubtably have to rearchitect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Load Balancer Cost
&lt;/h3&gt;

&lt;p&gt;The Load Balancer will process requests of the following size:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processed bytes = (167B request + 172B response) * 4350 requests = 1,474,650B** = **1.475MB = 0.001475GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These numbers can be plugged into the &lt;a href="https://aws.amazon.com/elasticloadbalancing/pricing/"&gt;Application Load Balancer cost calculator&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--per4WKD_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3392/1%2AazOH5F39zOTAdn1y6mA_vw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--per4WKD_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3392/1%2AazOH5F39zOTAdn1y6mA_vw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total ALB Cost = (ALB/month) + (LCU/month)
Total ALB Cost = $0.23 +(730hours/month * $0.0252/hour) = $18.63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Total Cost
&lt;/h3&gt;

&lt;p&gt;At a request rate of 4,350 per month, the service can be estimated to cost:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Cost = Application Load Balancer cost + Lambda cost
Total Cost = `$18.63 + $0.06 = $18.69
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As many services can share the same ALB, their cost will be shared across projects. A cheaper alternative would be to use &lt;a href="https://aws.amazon.com/api-gateway/"&gt;API Gateway&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We do not expect this much traffic and simply want to expose the service for fun.&lt;/p&gt;

&lt;p&gt;We have created a &lt;a href="https://aws.amazon.com/getting-started/hands-on/control-your-costs-free-tier-budgets/"&gt;Cost Budget&lt;/a&gt; with alarms that trigger when the service is going to exceed the expected cost.&lt;/p&gt;

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

&lt;p&gt;At T3chFlicks &lt;strong&gt;we want to share&lt;/strong&gt;, this is why you’ll find the code that created this project is open sourced. We’d love to hear from experienced developers / business owners on how they managed to run a successful SaaS, please reach out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks For Reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.patreon.com/bePatron?u=14761480"&gt;Patreon&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Giving away free APIs without going broke.</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Fri, 11 Jun 2021 14:36:54 +0000</pubDate>
      <link>https://dev.to/t3chflicks/giving-away-free-apis-without-going-broke-38h3</link>
      <guid>https://dev.to/t3chflicks/giving-away-free-apis-without-going-broke-38h3</guid>
      <description>&lt;p&gt;I like to write articles on Medium. I also want to share the same articles on our own site without having to rewrite them in a different format. For this reason, I have created a service which runs something similar to the Medium-To-MarkDown library, a programme which converts Medium posts to MarkDown format. I have offered it to everyone on our site.&lt;/p&gt;

&lt;p&gt;To be clear, &lt;a href="https://t3chflicks.org/Services/medium-to-markdown" rel="noopener noreferrer"&gt;Medium-To-MarkDown&lt;/a&gt; is an example service whilst we work out how best to run an effective Software as a Service (SAAS).&lt;/p&gt;

&lt;p&gt;In this article, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Break down the hosting costs of the service&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Determine how feasible it really is to give away free APIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Discuss some tactics to avoid going broke&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4000%2F1%2Ah_mDgR5Iq0TXLy0zoofTMQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4000%2F1%2Ah_mDgR5Iq0TXLy0zoofTMQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What do we want?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To send an article URL and in response get the &lt;a href="https://www.markdownguide.org/" rel="noopener noreferrer"&gt;MarkDown&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To share this service with the world&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To avoid T3chFlicks going into financial meltdown&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want to use this service and render the MarkDown on our site for the posts we write. We typically write one article each month, so we can’t really justify the purpose of an API. Instead, a better solution might be to execute a script for the conversion when building our site.&lt;/p&gt;

&lt;p&gt;However, at T3chFlicks &lt;strong&gt;we want to share&lt;/strong&gt; - our only opposition is financial. This is a fairly simple service, running on AWS (the largest Cloud Hosting company). Surely this wouldn’t be too much for a UK-based tech company?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4200%2F1%2ArDHR5nwVHfdZvKgOy6X09A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4200%2F1%2ArDHR5nwVHfdZvKgOy6X09A.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture diagram above shows a user making a request to the &lt;code&gt;MediumToMarkDown/Convert&lt;/code&gt; endpoint on the load balancer. The &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html" rel="noopener noreferrer"&gt;Application Load Balancer&lt;/a&gt; (ALB) sends this event to the API &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt;, which responds immediately with a URL for the location of the MarkDown article result. The API Lambda also puts the Medium-To-MarkDown job into a &lt;a href="https://aws.amazon.com/sqs/" rel="noopener noreferrer"&gt;SQS&lt;/a&gt; queue to be consumed by the Processing Lambda.&lt;/p&gt;

&lt;p&gt;The Processing Lambda is where all the work is done, and the result is written to an &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;S3&lt;/a&gt; Bucket which is attached to a &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt; distribution. This has a lifecycle policy set to delete after one day.&lt;/p&gt;

&lt;p&gt;The Medium-To-MarkDown processing script is pretty simple and can be found &lt;a href="https://github.com/sk-t3ch/giving-away-free-apis-without-going-broke/blob/master/medium-to-markdown.py" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Interface
&lt;/h3&gt;

&lt;p&gt;We made a widget that allows users to easily play with the API and view the &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON schemas&lt;/a&gt; which define the input and output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F6348%2F1%2AkWO_ZW35SLGhj689nURGrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F6348%2F1%2AkWO_ZW35SLGhj689nURGrw.png" alt="T3chFlicks API-Play Widget"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;T3chFlicks API-Play Widget&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The programmatic use of the API is as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl --location --request POST 'https://api.t3chflicks.org/medium-to-markdown/convert' \
--header 'Content-Type: application/json' \
--data-raw '{
"mediumURL": "https://t3chflicks.medium.com/how-to-use-amplify-auth-in-nuxt-js-4dbf07da7033",
"key": "52d9204da6c6f8cefd13fb43384c365656dc091473634e3b701892e3cc29abef7af4a8de1f3913235bd4b56a409e09597400da67e358cd62e118d3922563791e"
}'
&amp;gt;&amp;gt;&amp;gt; {"location": "https://document-store.t3chflicks.org/medium-to-markdown/document-title"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Accessing the MarkDown document:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://document-store.t3chflicks.org/medium-to-markdown/document-title

&amp;gt;&amp;gt;&amp;gt; "# How to use Amplify Auth in Nuxt JS \nVue JS is a frontend framework often extended with Nuxt JS to get great SSO value out of the box. AWS Amplify allows you to add authentication to your Vue web app with ease as shown in a previous article. In this article, I demonstrate how to add Cognito authentication with Amplify to a Nuxt web app..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://t3chflicks.org/Services/medium-to-markdown" rel="noopener noreferrer"&gt;🤖 The widget can be found on our site 🤖&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Cost Estimation
&lt;/h2&gt;

&lt;p&gt;When thinking about what our limits are on giving away free services, we can approach the problem from two sides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;How much is the maximum number of requests going to cost?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How many requests can I get for $2/month?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the &lt;a href="https://t3chflicks.org" rel="noopener noreferrer"&gt;T3chFlicks&lt;/a&gt; site, we give users 100 free credits per week. At the time of of writing, we have 100 registered users. If we assume all of our users use all of their credits each week for a month on the Medium to Markdown service, then…&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Number of requests/month = (100 credits/week * 100 users * 4.35 weeks/month) = 43,500 Requests / Month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s a small number of requests, but will it incur a small AWS cost?&lt;/p&gt;

&lt;p&gt;The total cost of the service is the sum of the individual component costs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Cost = (ALB cost) + (Lambda cost) + (SQS cost) + (S3 cost) + (CloudFront cost)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These costs are dependent on the amount of data transferred. Apparently, there is an optimum length for a &lt;a href="https://www.lean-labs.com/blog/the-ideal-length-for-business-blog-posts-when-less-is-more#:~:text=Here's%20what%20recent%20studies%20have,between%202%2C350%20and%202%2C500%20words" rel="noopener noreferrer"&gt;Medium article&lt;/a&gt; ~ 1,600 words. At 4.79 characters per word on average in UTF-8 means:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Size of Article = 1600*4.79*1byte = 7,664 bytes ~7.7KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;An example &lt;a href="https://t3chflicks.medium.com/how-to-use-amplify-auth-in-nuxt-js-4dbf07da7033" rel="noopener noreferrer"&gt;T3chFlicks article&lt;/a&gt; has a size of &lt;strong&gt;5.39KB&lt;/strong&gt;, so this seems legit.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data transfer/ month = (number of requests / month) * (size of response)
*Data transfer / month = 7.7 KB * 4350 Requests per Month = 33,495 KB ~ 33.5MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Load Balancing cost
&lt;/h3&gt;

&lt;p&gt;The API is created by an ALB. The cost is broken down into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;$0.0252 per ALB-hour (or partial hour)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;$0.008 per Load balancing capacity unit - LCU hour (or partial hour)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LCUs have four usage dimensions and you are charged based on the highest dimension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Number of newly established connections per second (25/sec)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Number of active connections per minute (3,000/min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The number of bytes processed for requests and responses (1 GB /hour on EC2, and 0.4GB/hour Lambda)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Number of Rule evaluations (1,000/sec)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Determining this cost is quite difficult, so we made use of the calculator on the &lt;a href="https://aws.amazon.com/elasticloadbalancing/pricing/" rel="noopener noreferrer"&gt;ALB page&lt;/a&gt;. However, with our estimates for usage of this service, the cost seems to be negligible.&lt;/p&gt;

&lt;p&gt;The size of the request is tiny:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Processed bytes = (33Bytes request + 33Bytes response) * 4350 requests = 287,100B = 287KB = 0.287MB = 0.0003GB&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3428%2F1%2AlESibyAQqPPnoZHCFoZxiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3428%2F1%2AlESibyAQqPPnoZHCFoZxiw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The estimated &lt;strong&gt;$0.23&lt;/strong&gt; for LCU charges is very manageable for our goal of $2. Even at 26 million requests/month, the LCU charge only increases to $2.34.&lt;/p&gt;

&lt;p&gt;A cheaper alternative given the predicted rate of requests is the &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; service. However, many services can share the same ALB, and since we can share this across many of our projects, so will the cost, meaning we can assume it to be zero.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total ALB cost = (ALB/month) + (LCU/month)
Total ALB Cost = $0.23 +(730hours/month * $0.0252/hour) = $18.63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once the traffic has used &lt;strong&gt;~30 LCU&lt;/strong&gt;, then the cost of traffic will be equal to that of the unused ALB service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda cost
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;With AWS Lambda, you pay only for what you use. You are charged based on the number of requests for your functions and the duration, the time it takes for your code to execute — &lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;source&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The are two different Lambdas in the service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;API Lambda —&lt;/strong&gt; immediately returns a response of the result location&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;197.5 ms on average @ 256 MB memory&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2396%2F1%2Aj6U896kNZpxNQlHnHi5B0A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2396%2F1%2Aj6U896kNZpxNQlHnHi5B0A.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only when getting into the millions of requests does this service begin to cost:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2412%2F1%2AfnP1oUjL0Ty6cLSFsjqiEA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2412%2F1%2AfnP1oUjL0Ty6cLSFsjqiEA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Processing Lambda —&lt;/strong&gt; performs the transformation of the article and uploads the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This part of the service depends on the response from many third parties including Medium, YouTube, and Github. This means response times might vary wildly depending on the contents of the Medium blog post. A hard limit of 10 seconds was set on the Lambda runtime.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;650ms on average @ 256 MB memory for a standard length blog post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2428%2F1%2AB92P4eoaszqYDL6DdG2A-A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2428%2F1%2AB92P4eoaszqYDL6DdG2A-A.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  😱 But what if users want to process huge articles 😱
&lt;/h1&gt;
&lt;h1&gt;
  
  
  ✅ Set a Lambda timeout to avoid long running times ✅
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Queue cost
&lt;/h3&gt;

&lt;p&gt;This service does singular batches of items on the SQS queue, but could easily be extended to do larger batches. The pricing page shows that for fewer than a million requests, this service is free:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3868%2F1%2A9HGDSv8Sm_6TGOuUc5f7pA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3868%2F1%2A9HGDSv8Sm_6TGOuUc5f7pA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can therefore assume the SQS cost to be negligible as we do not expect a million requests per month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage cost
&lt;/h3&gt;

&lt;p&gt;The result of the process is stored in an S3 bucket and users are sent a URL of that object.&lt;/p&gt;

&lt;p&gt;At 4350 requests per month, ~135 requests per day, with a bucket lifecycle of one day, the maximum expected to be in the bucket at one point is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`200 articles, 7.7 KB each, 1,540KB = 1.5MB`

`$0.023 per GB storage costs on S3 for the first 50TB`

`1.5/1000 * 0.0023 = $0.00000345`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Again, this can be considered negligible, probably up until 10 million requests:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;7.7KB/1000/1000 * $0.0023/GB * 10,000,000R = $0.1771&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFront cost
&lt;/h3&gt;

&lt;p&gt;CloudFront is used in this architecture as a layer around the S3 bucket. The costs for CloudFront origin requests (first time accessing) are free for S3 bucket origins and $0.085 for the first 10TB transferred to users, meaning this cost is negligible up until millions of requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Total Cost&lt;/em&gt;
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For 4,350 requests
Total Cost = (ALB cost) + (Lambda cost) + (SQS cost) + (S3 cost) + (CloudFront cost)

Total = ( $18.396 + $0.23 )+ ($0.00 +$ 0.01) + ($0.00) + ($0.00 ) + ($0.00 )

Total = $18.396 + $0.23 + $0.01

Total = $18.636
Total minus standard ALB charge = $0.24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  🙌 I think we can afford 24 cents per month 🙌
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Capping Usage
&lt;/h3&gt;

&lt;p&gt;As we previously mentioned, the other way to look at this problem is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;em&gt;If we put a monthly cap of $2 on this service, how many requests could we support?&lt;/em&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;For 435,000 requests &lt;strong&gt;(100X more)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lambda costs — $1.27&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda costs — $0.45&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ALB costs — no change $0.23&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Storage costs — Negligible&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CloudFront costs — Negligible&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Costs = 1.27 + 0.45 + 0.23 = $1.95 ~ $2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;100 times more requests would mean 100 times more users:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;em&gt;🌎 We’re happy with spending $2 on 10,000 people 🤗&lt;/em&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Real costs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cost Tagging
&lt;/h3&gt;

&lt;p&gt;Tagging your resources makes it much easier to retrospectively understand costs, as the AWS billing console can effectively use these tags to split up the bill.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  😱 Resource costs are hard to pin down😱
&lt;/h1&gt;
&lt;h1&gt;
  
  
  ✅ Tag all resources and create a cost budget with project filters✅
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  AWS Budgets
&lt;/h3&gt;

&lt;p&gt;AWS offers &lt;a href="https://aws.amazon.com/getting-started/hands-on/control-your-costs-free-tier-budgets/" rel="noopener noreferrer"&gt;budgets&lt;/a&gt; to help solve the problem of capping usage. The service allows you to filter service costs into a group and place alarms on monetary metrics such as expected monthly forecast. You can use these to trigger an email message.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  😱 But what if we suddenly get lots of users? 😱
&lt;/h1&gt;
&lt;h1&gt;
  
  
  ✅ Put a budget alarm on the service✅
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have done this for the &lt;a href="https://medium-to-markdown.t3chflicks.org/" rel="noopener noreferrer"&gt;Medium-To-MarkDown&lt;/a&gt; service by tagging the service resources with a &lt;a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/activating-tags.html" rel="noopener noreferrer"&gt;user defined cost allocation tag&lt;/a&gt;: &lt;code&gt;Project&lt;/code&gt; , and creating a budget which filters using the same tag.&lt;/p&gt;

&lt;p&gt;A budget shows a detailed dashboard, which is rather empty at the moment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4928%2F1%2AkzOVl0O7IqPowwDarXS4-g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4928%2F1%2AkzOVl0O7IqPowwDarXS4-g.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;

&lt;p&gt;ALB does not allow for rate limiting. For that, you must add AWS WAF onto your API which costs $5/month + $1/month per million requests - quite expensive.&lt;/p&gt;

&lt;p&gt;How bad would it be if somebody all spent their credits at once?&lt;/p&gt;

&lt;p&gt;This could increase the LCU to a high level for a short period of time which could be bad but probably not awful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Status
&lt;/h3&gt;

&lt;p&gt;It is good practice to monitor your service for errors. You can monitor how many users are receiving &lt;a href="https://developer.att.com/video-optimizer/docs/best-practices/http-400-and-500-error-codes" rel="noopener noreferrer"&gt;4XX and 5XX responses&lt;/a&gt; from the ALB, and configure alarms which trigger email alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We used AWS &lt;a href="https://aws.amazon.com/codepipeline/" rel="noopener noreferrer"&gt;CodePipeline&lt;/a&gt; to orchestrate this entire service.&lt;/p&gt;

&lt;p&gt;Alarms and a deployment pipeline with tests mean that we minimise the likelihood of updating this service with a breaking change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;At T3chFlicks &lt;strong&gt;we want to share&lt;/strong&gt;, this is why you’ll find that our projects – whether they’re a &lt;a href="https://github.com/sk-t3ch/smart-buoy" rel="noopener noreferrer"&gt;Smart Buoy for ocean measurement&lt;/a&gt;, or a &lt;a href="https://github.com/sk-t3ch/AWS_Stripe-SaaS-quickstart" rel="noopener noreferrer"&gt;Software as a Service quickstart&lt;/a&gt; – are open sourced. This API is due to be followed by many other services and as long as we &lt;strong&gt;don’t go broke&lt;/strong&gt;, we will continue. We’d love to hear from experienced developers / business owners on how they managed to run a successful SaaS, please reach out!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2A3YDT7Yy33equvSJ4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2A3YDT7Yy33equvSJ4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks For Reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/" rel="noopener noreferrer"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks" rel="noopener noreferrer"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.patreon.com/bePatron?u=14761480" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architecture</category>
      <category>saas</category>
    </item>
    <item>
      <title>⚡ 🛋️ Wireless Charging Sofa</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Wed, 17 Mar 2021 18:00:50 +0000</pubDate>
      <link>https://dev.to/t3chflicks/wireless-charging-sofa-5a7o</link>
      <guid>https://dev.to/t3chflicks/wireless-charging-sofa-5a7o</guid>
      <description>&lt;p&gt;Fed up of the wires and hassle of plugging and unplugging your phone as you move around the house? So were we!&lt;/p&gt;

&lt;p&gt;We’ve made a wireless charging cover which fits snugly on your sofa arm and blends in seamlessly. This simple make is a great way to upgrade your sofa and is a step on the path to eternal laziness: charging your phone is as simple as putting it down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V_NhE_71--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/1%2Aq1pX7CZc9f6delcpqZ8Maw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V_NhE_71--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/1%2Aq1pX7CZc9f6delcpqZ8Maw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Supplies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Faux Leather (Fire retardant) &lt;a href="https://www.amazon.co.uk/gp/product/B0076LHS0G/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B0076LHS0G&amp;amp;linkId=23386df5d286ffdfe9fe69e872011755"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wireless Charge Receiver &lt;a href="https://www.amazon.co.uk/gp/product/B0761T1J19/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B0761T1J19&amp;amp;linkId=0a1cde458142c2867aa43f457c825f82"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wireless Charging Module &lt;a href="https://www.amazon.co.uk/MagiDeal-Wireless-Charger-Module-Circuit-Green/dp/B07FM8XXDM/ref=sr_1_5?ie=UTF8&amp;amp;qid=1550091090&amp;amp;sr=8-5&amp;amp;keywords=wireless+charging+module"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Micro-USB Wire &lt;a href="https://www.amazon.co.uk/gp/product/B01GJC4YMC/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks07-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B01GJC4YMC&amp;amp;linkId=651398f5085101c524e069e25ab23bb7"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2A USB Plug &lt;a href="https://www.amazon.co.uk/gp/product/B07MKQ32VT/ref=as_li_tl?ie=UTF8&amp;amp;camp=1634&amp;amp;creative=6738&amp;amp;creativeASIN=B07MKQ32VT&amp;amp;linkCode=as2&amp;amp;tag=t3chflicks07-21&amp;amp;linkId=682dc6a6d9b502b62834c4af38d321fd"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sewing Machine &lt;a href="https://www.amazon.co.uk/gp/product/B078MRMTWB/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks07-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B078MRMTWB&amp;amp;linkId=e7ba7ec9a36020914df2dbfc471a735b"&gt;Amazon&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/smart-buoy"&gt;🔗 Get The Wireless Charging Sofa Files On Github 📔&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tutorial 🤖
&lt;/h2&gt;


&lt;center&gt;&lt;/center&gt;
&lt;h3&gt;
  
  
  Measure Up
&lt;/h3&gt;

&lt;p&gt;Measure the arm of your sofa. You will make the cover in two pieces: one which covers the front of the arm and another piece which wraps around the top of the arm. First, measure the front piece by measuring the width of the arm (at its widest point on curved arms) and how far down the arm you want the cover to go — when doing this, bear in mind you probably want it to sit between the cushion and the arm.&lt;/p&gt;

&lt;p&gt;Once you have established how far down the arm you want the cover, measure the second piece which will go across the arm. Measure how deep you want the cover to be.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JtIHGBdV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4942/1%2Aq4YPJrzbiLNanw_HL0qDKw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JtIHGBdV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4942/1%2Aq4YPJrzbiLNanw_HL0qDKw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure Wireless Charger Pocket
&lt;/h3&gt;

&lt;p&gt;The wireless charging module will be housed within a pocket inside cover to stop it moving around. We made ours 15.0cm by 9.5cm — based on the size of our wireless charger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s6m1YwRV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6826/1%2Ayanh7uMwuosHJOLaMQ3N6A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s6m1YwRV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6826/1%2Ayanh7uMwuosHJOLaMQ3N6A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Draw Out Measurements
&lt;/h3&gt;

&lt;p&gt;Add approximately 1/2cm to your measurements (for stitching) and draw the rectangles on the back of your material — there should be 4 in total. Cut them out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AGEdXJez--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2160/1%2AGbjlNhYerCvTWrW1768TWg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AGEdXJez--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2160/1%2AGbjlNhYerCvTWrW1768TWg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sew the Pocket
&lt;/h3&gt;

&lt;p&gt;Make the pocket for the wireless charging module. To do so, take the two small rectangles and place them on top of each other so the front of the material is facing inwards on both pieces.&lt;/p&gt;

&lt;p&gt;Place the wireless charger between the two pieces and draw a line across the fabric where it ends. Sew along this line to the two pieces of fabric are joined in the middle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CCVBR_ZW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6870/1%2AXFEmi_gOXcteGB6UayytLA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CCVBR_ZW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6870/1%2AXFEmi_gOXcteGB6UayytLA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin the Cover
&lt;/h3&gt;

&lt;p&gt;Pin the arm cover ready for sewing. With the material face down, lay the rectangle across the top of the arm. Pin the front rectangle onto it, leaving at least 0.5cm seams.&lt;/p&gt;

&lt;p&gt;If you have a sofa with a rounded arm, you will need to gather more material where the arm curves to achieve the right shape.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DOKW0UAC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6894/1%2Aj5WSIzju1H8ixXlqRI0vcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DOKW0UAC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6894/1%2Aj5WSIzju1H8ixXlqRI0vcg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sew the Cover
&lt;/h3&gt;

&lt;p&gt;Sew the cover together while it is still inside out, removing the pins as you go. If you need guidance on how to hand stitch, or how to thread and use a sewing machine, check out these tutorials (&lt;a href="https://www.youtube.com/watch?v=xdHnrlrQ6RE&amp;amp;feature=youtu.be"&gt;1&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=kKnBUa4l2k4"&gt;2&lt;/a&gt;) from the wonderful world of YouTube!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9-RSVH-P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2164/1%2Aqgb9CMqyS4fSjlMCpJR-GQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9-RSVH-P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2164/1%2Aqgb9CMqyS4fSjlMCpJR-GQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test on Arm
&lt;/h3&gt;

&lt;p&gt;Once you’ve finished sewing the cover, turn it inside out and check you’re happy with how it fits on your sofa arm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QTkAfkYl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2136/1%2ATgisXjnKmVfsU0Iw_T7-aA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QTkAfkYl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2136/1%2ATgisXjnKmVfsU0Iw_T7-aA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hem the Edges
&lt;/h3&gt;

&lt;p&gt;Hem the edges of the cover by folding approximately 1/2cm of each edge backwards and pinning in place. Sew neatly to create a hemline which will give the cover a clean finish.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZoF8Yktw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4844/1%2AvigsbJndlNoRVAkeP6ASQw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZoF8Yktw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4844/1%2AvigsbJndlNoRVAkeP6ASQw.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin the Pocket on the Cover
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WiFFVdIc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2048/1%2AtQWS633x6TVo1_IUcHh2ZA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WiFFVdIc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2048/1%2AtQWS633x6TVo1_IUcHh2ZA.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Place the cover on the sofa arm and put the rectangles you sewed earlier to house the wireless charging module beneath it with the half which fits the wireless charging module facing towards the back of the sofa. The pocket should sit in the middle of the arm in a place you can balance your phone. When you’re happy with the location, pin in place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sew in the Pocket
&lt;/h3&gt;

&lt;p&gt;Sew the pocket along the front and two side edges. When sewing along the rear edge, only sew the top layer of the pocket onto the cover. This is so that the wireless charging module can be added or removed but also so that the rectangle shape on the top of the cover, which shows where you put your phone for it to charge properly, is a complete shape.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lfsYdGY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2048/1%2AzNOkkIGl00Pll8jMAGqHxQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lfsYdGY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2048/1%2AzNOkkIGl00Pll8jMAGqHxQ.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Plug in Wireless Charger
&lt;/h3&gt;

&lt;p&gt;Put the wireless charger inside the pocket and plug it in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enjoy!
&lt;/h3&gt;

&lt;p&gt;Put the cover your sofa arm you’re ready to go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wJymEbIk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2160/1%2A-AAVTYza1dqwQ6hWBb_obA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wJymEbIk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2160/1%2A-AAVTYza1dqwQ6hWBb_obA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for Reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🔪  The Ultimate Knife Block</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Wed, 17 Mar 2021 17:59:40 +0000</pubDate>
      <link>https://dev.to/t3chflicks/the-ultimate-knife-block-3g42</link>
      <guid>https://dev.to/t3chflicks/the-ultimate-knife-block-3g42</guid>
      <description>&lt;p&gt;We’ve all been there, chopping vegetables with a knife so blunt it would be more effective to use a teaspoon. In that moment, you reflect on how you got there: your knives were sharp as razors when you bought them but now, three years down the line, they’re thoroughly inadequate. “I should’ve sharpened my knives” you think to yourself. Shoulda, coulda, woulda but I didn’t.&lt;/p&gt;

&lt;p&gt;Most of us don’t bother to sharpen our knives. It’s an extra bit of effort and when you’re just trying to make dinner, faffing around with a sharpener is the last thing you want to do. But what if it wasn’t..?&lt;/p&gt;

&lt;p&gt;We decided to make a knife block which incorporates a mechanical knife sharpener. A sharpener right next to your knives — and solar powered so you don’t even need to bother charging it! This build is super straightforward and you end up with a great final product which would be a helpful addition to any kitchen!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5HOsMW6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Av4ITmKlkxhz_3udX3dChHg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5HOsMW6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Av4ITmKlkxhz_3udX3dChHg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Supplies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Rechargeable 18650 battery — &lt;a href="https://amzn.to/2XF7cno"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Charge controller TP4056- &lt;a href="https://amzn.to/2L6MU4Y"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push button — &lt;a href="https://amzn.to/2GKp5eZ"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Small motor — &lt;a href="https://amzn.to/2GN44k1"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Battery Holder — &lt;a href="https://amzn.to/2vwHNAx"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Glue Gun — &lt;a href="https://amzn.to/2UDKcDz"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Soldering Iron — &lt;a href="https://amzn.to/2GJ4XtR"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wire — &lt;a href="https://amzn.to/2GIzGa9"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sharpening stone — &lt;a href="https://amzn.to/2vlYheF"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3 packets of spaghetti — &lt;a href="https://amzn.to/2UHnavm"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Red PLA — &lt;a href="https://amzn.to/2XKszDV"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Grinding stone — &lt;a href="https://amzn.to/2vlYheF"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3X Screws 12mm m3 — &lt;a href="https://amzn.to/2VsZL5g"&gt;https://amzn.to/2XF7cno&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/ultimate-knife-block"&gt;🔗 Get The Ultimate Knife Block Files On Github 📔&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tutorial 🤖
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Knife Block Design
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1emJx-bC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2146/1%2AiUkpLT6kQng8V_bvMCo8Kg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1emJx-bC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2146/1%2AiUkpLT6kQng8V_bvMCo8Kg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The basic knife block design is a curvy cuboid with a detachable lid and a space for a solar panel in the front. The lid has slots for the knives. To figure out how big the block needed to be and how wide the knife slots would be, we measured the knives we wanted to put in and designed accordingly.&lt;/p&gt;

&lt;p&gt;To power the rotating sharpener, we decided to use a solar panel to keep the design cordless (you don’t want to plug &lt;em&gt;another&lt;/em&gt; thing in the kitchen) and remove the hassle of recharging batteries. Also, chances are that unless you’re a serial knife sharpener, a solar panel will provide plenty power.&lt;/p&gt;

&lt;p&gt;The electronics are pretty simple to put together. For power, you need one rechargeable battery — preferably 18650 lithium ion. To charge it, you’ll need a solar panel — we used 5V, 500mA because we had one spare, but a smaller one would be perfectly fine. You’ll also need a battery protection circuit and something to put the battery in.&lt;/p&gt;

&lt;p&gt;The whole thing will be controlled by a simple button which sits in the top of the knife block. To operate the sharpener, the button needs to be depressed. This is actually a pretty good safety mechanism because it means the sharpener will stop turning as soon as you let go of the button. On the end of the motor, there’s a small grinding stone which I found online.&lt;/p&gt;

&lt;h3&gt;
  
  
  Print the 3D Case
&lt;/h3&gt;

&lt;p&gt;Firstly, 3D print your knife block shell.&lt;/p&gt;

&lt;p&gt;We made the 3D design using Fusion360. To be honest, it was quite a fiddly and time consuming process. If you’d like a tutorial on how to do this, please let us know in the comments below. We’re still learning, too, so if anyone has any tips on the design or good places to learn more about 3D design, please share.&lt;/p&gt;

&lt;h3&gt;
  
  
  Electronics
&lt;/h3&gt;

&lt;p&gt;We are going to put together following circuit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyrbDWqR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2338/1%2APDaujNCcy0wKQ05Pi5G7ZA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyrbDWqR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2338/1%2APDaujNCcy0wKQ05Pi5G7ZA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Solder Wires Onto the Solar Panel
&lt;/h3&gt;

&lt;p&gt;Take two wires about 10cm long and solder one onto the positive and one onto the negative tab on the solar panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bi9n1l_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2144/1%2Av5D8XdEtbCl_9vw1QpvJnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bi9n1l_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2144/1%2Av5D8XdEtbCl_9vw1QpvJnw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the Battery
&lt;/h2&gt;

&lt;p&gt;Put the battery into the holder and solder the positive and negative wires to the B+ and B- inputs on the charge controller.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_LXBMLMe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AJ5FcBLTZMxUt62TjQ5m9dQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_LXBMLMe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AJ5FcBLTZMxUt62TjQ5m9dQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the Switch and Motor
&lt;/h2&gt;

&lt;p&gt;From the push button terminals, solder one wire from the positive output of the charge controller to the input of the push button. Solder another wire from the output of the push button to the positive of the motor. Solder a wire from the negative output of the charge controller to the negative of the battery.&lt;/p&gt;

&lt;p&gt;Check the connections work and note which way the motor turns — you want to put it in the case so it will rotate away from you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jy1Nlbmk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A-oZbpU8d0jVxaE9VsxeP5Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jy1Nlbmk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A-oZbpU8d0jVxaE9VsxeP5Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QdKhw0P4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2144/1%2AqKAPr50UP1R_TbMhsAjTZg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QdKhw0P4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2144/1%2AqKAPr50UP1R_TbMhsAjTZg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Put in the Motor
&lt;/h2&gt;

&lt;p&gt;Slot the motor into the hole in the knife block. To reduce vibration and help keep the motor in place, you could glue it using a glue gun — this is optional, though, as it fits snugly.&lt;/p&gt;

&lt;p&gt;Push the grinding stone onto the end of the motor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mL-cSFKU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2168/1%2AhLgae61OS7q_1MSZarCrbA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mL-cSFKU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2168/1%2AhLgae61OS7q_1MSZarCrbA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BZSQcW2H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2150/1%2Avbggr8Tlf1EjmVYGMV1z9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BZSQcW2H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2150/1%2Avbggr8Tlf1EjmVYGMV1z9w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Put on Push Button
&lt;/h2&gt;

&lt;p&gt;Put the push button through the hole in the lid and glue in place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Iz3CABJ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2114/1%2AnqT90IR4rF0zLL2MykEz0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Iz3CABJ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2114/1%2AnqT90IR4rF0zLL2MykEz0g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Put on the Solar Panel
&lt;/h2&gt;

&lt;p&gt;Solder the positive wire from the solar panel to the positive input on the charge controller. Solder the negative wire from the solar panel to the negative input on the charge controller.&lt;/p&gt;

&lt;p&gt;Glue around the perimeter of the case and push on the solar panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bDREDlZq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2084/1%2AJ3a60hdIQPgy-IadGzFpuQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bDREDlZq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2084/1%2AJ3a60hdIQPgy-IadGzFpuQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BnTCMt82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2128/1%2ARIgL4a6sBKm5sng0GnwNDw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BnTCMt82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2128/1%2ARIgL4a6sBKm5sng0GnwNDw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fill Up With Spaghetti
&lt;/h2&gt;

&lt;p&gt;Fill the large internal cavity with spaghetti. This might sound random, but it gives the block some weight so it doesn’t move around when sharpening and it also helps keep the knives in place.&lt;/p&gt;

&lt;p&gt;We used about 3 packs of spaghetti to fill the space. It was a bit long so we trimmed the ends so the lid would fit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4SdBQ-jW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2166/1%2AcMpFwdorFv6tm9FL1HS1sw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4SdBQ-jW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2166/1%2AcMpFwdorFv6tm9FL1HS1sw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Put on Lid
&lt;/h2&gt;

&lt;p&gt;Screw the lid in place to close the knife block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KQFhepEW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AjqaOpfrAAaO36alXhyHscA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KQFhepEW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AjqaOpfrAAaO36alXhyHscA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Put in Knives
&lt;/h2&gt;

&lt;p&gt;Put your knives into your block and swirl it round in the sun.&lt;/p&gt;

&lt;p&gt;We did think that adding little rubber feet would be a good way to reduce vibration, noise and slipperiness, but we didn’t quite get round to that. We also thought that we could add a bottle opener or an electric can opener to make this the ultimate kitchen gadget!&lt;/p&gt;

&lt;p&gt;If you have any suggestions for improvements or additions, please let us know in the comments below!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---LTMQIkC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2140/1%2AKrv2qVSGo7C49A1YELRYag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---LTMQIkC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2140/1%2AKrv2qVSGo7C49A1YELRYag.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚢 Home DevOps Pipeline: A junior engineer’s tale (4/4)</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:14:50 +0000</pubDate>
      <link>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-4-4-b59</link>
      <guid>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-4-4-b59</guid>
      <description>&lt;p&gt;In this series of articles, I will explain how I built my own home development environment using a couple of Raspberry Pis and a lot of software. The code referenced in these articles can be found &lt;a href="https://github.com/sk-t3ch/home-repo"&gt;here&lt;/a&gt;. In this article I will cover the monitoring of my pipeline, as well as discuss a few gotchas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;My initial thought for monitoring uptime of my development pipeline was &lt;a href="https://uptimerobot.com"&gt;UpTimeRobot&lt;/a&gt;, a free service which pings periodically and if failures alerts via email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HxmfCAeN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2800/1%2AP3Fo0Q2f0Lj6tdMc2jlp-g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HxmfCAeN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2800/1%2AP3Fo0Q2f0Lj6tdMc2jlp-g.png" alt="Example email"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Example email*&lt;/p&gt;

&lt;p&gt;However, being able to monitor and repair your development pipeline in a web app is just great and &lt;a href="https://www.portainer.io"&gt;portainer&lt;/a&gt; have really aced this one.&lt;/p&gt;

&lt;p&gt;This application is great and many more features than I want to explain. But it allows for inspection of container logs, ssh into containers, and even deployment of new images — controlling and monitoring multiple hosts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qf4CO4YW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6668/1%2Ak-xqPs9IjWgKjlfXLq1pxQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qf4CO4YW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6668/1%2Ak-xqPs9IjWgKjlfXLq1pxQ.png" alt="Portainer running"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Portainer running*&lt;/p&gt;

&lt;p&gt;To get portainer running I added this to my code docker-compose template:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;portainer:&lt;br&gt;
  image: portainer/portainer&lt;br&gt;
  command: -H unix:///var/run/docker.sock&lt;br&gt;
  restart: always&lt;br&gt;
  networks:

&lt;ul&gt;
&lt;li&gt;homereponet
ports:&lt;/li&gt;
&lt;li&gt;9000:9000&lt;/li&gt;
&lt;li&gt;8000:9000
volumes:&lt;/li&gt;
&lt;li&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/li&gt;
&lt;li&gt;/mnt/hdd/portainer:/data
labels:&lt;/li&gt;
&lt;li&gt;"traefik.enable=true"&lt;/li&gt;
&lt;li&gt;traefik.backend=portainer&lt;/li&gt;
&lt;li&gt;traefik.portainer.frontend.rule=Host:portainer.&amp;lt;domain&amp;gt;&lt;/li&gt;
&lt;li&gt;traefik.docker.network=homereponet&lt;/li&gt;
&lt;li&gt;traefik.portainer.port=9000
&lt;/li&gt;
&lt;/ul&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;


The End
&lt;/h3&gt;


&lt;p&gt;This is where this story ends for now. I think that this is a great starter set up for anyone looking to have a industry style development pipeline. As a recap this final setup is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitea.io/"&gt;Gitea&lt;/a&gt; Repository for storing code, using &lt;a href="https://git-lfs.github.com"&gt;git LFS&lt;/a&gt; for large media files. &lt;a href="https://docs.drone.io/"&gt;Drone CI&lt;/a&gt; to build containers, store them in &lt;a href="https://docs.docker.com/registry/deploying/"&gt;Docker Registry&lt;/a&gt;, and deploy using an &lt;a href="http://plugins.drone.io/drone-plugins/drone-ansible/"&gt;Ansible&lt;/a&gt; plugin to a local set of machines as well as &lt;a href="https://hub.docker.com/r/t3chflicks/aws-cfn"&gt;cloudformation plugin &lt;/a&gt;for AWS deployment. &lt;a href="https://www.portainer.io/"&gt;Portainer&lt;/a&gt; for monitoring and control over containers in a UI. &lt;a href="https://docs.traefik.io/"&gt;Traefik&lt;/a&gt; for a reverse proxy with &lt;a href="https://letsencrypt.org/docs/"&gt;let’s encrypt&lt;/a&gt; free https certificate on a free &lt;a href="https://www.duckdns.org/"&gt;duckdns&lt;/a&gt; domain. All storage is done on a hard drive and there are periodic backups to &lt;a href="https://aws.amazon.com/s3/"&gt;AWS s3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZQckk60c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3840/0%2AGvyXyJ4Z3xJ4vIcN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZQckk60c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3840/0%2AGvyXyJ4Z3xJ4vIcN.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this series of articles. I also make many more projects including other hardware, 3D printing, smart-home applications, and machine learning in video form over at &lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w?view_as=subscriber"&gt;T3chFlicks&lt;/a&gt; — check us out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix
&lt;/h3&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/sk-t3ch/home-repo"&gt;https://github.com/sk-t3ch/home-repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Random Things I learned along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker-compose has to be controlled inside the same directory as it was started&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;policy creation for aws s3 is needed because you only want the pi and the root aws user to be able to access the bucket also add encryption&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;need to look more into backup techniques&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;s3 backups to glacier after 10 days meaning storage cost stays low&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Raspberry Pi 4 has two usb 3.0 ports and two 2.0 ones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using an EXFAT drive instead of HFS+ resulted in failures for the Postgres container&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ssh on Gitea cannot use port 22 due to permissions problem that i am yet to solve. to work around this i changed the router forward port to allow for an ssh port in the accepted range (1065+)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Contents: &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-1-4-336ed07a6ec0"&gt;1&lt;/a&gt;, &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-2-4-7be3e3c292c"&gt;2&lt;/a&gt;, &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-3-4-5f61c5245934"&gt;3&lt;/a&gt;, (&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-4-4-5db7c1610e3e"&gt;4&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/home-devops-pipeline"&gt;T3chFlicks.org&lt;/a&gt; for more tech-focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚢 Home DevOps Pipeline: A junior engineer’s tale (3/4)</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:14:16 +0000</pubDate>
      <link>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-3-4-3261</link>
      <guid>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-3-4-3261</guid>
      <description>&lt;p&gt;In this series of articles, I will explain how I built my own home development environment using a couple of Raspberry Pis and a lot of software. The code referenced in these articles can be found &lt;a href="https://github.com/sk-t3ch/home-repo"&gt;here&lt;/a&gt;. In this article I will cover the deployment section of the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ansible
&lt;/h3&gt;

&lt;p&gt;Another great thing about Drone is the &lt;a href="http://plugins.drone.io"&gt;plugins available&lt;/a&gt; — we will use the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/basic_concepts.html"&gt;Ansible&lt;/a&gt; plugin as a tool to run ssh commands on multiple machines. We will as well make use of &lt;a href="https://docs.drone.io/secret/repository/"&gt;Drone secrets&lt;/a&gt; as a method of securely deploying certain configuration values.&lt;/p&gt;

&lt;p&gt;The general methods of deployment I have seen usually consist of publishing to AWS and then magic happens using cloud formation templating and your stack is created.&lt;/p&gt;

&lt;p&gt;However, we want to keep the home in home repo. To do this, we want to deploy to specific machines on our local network. For me, this is a load of other Raspberry Pis which do many things, from control my 3D printer, to work as a network of CCTV cameras.&lt;/p&gt;

&lt;p&gt;To do this we will be using Ansible like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    kind: pipeline
    name: commitPipeline

    trigger:
      event:
         - push

    platform:
      os: linux
      arch: arm

    steps:
      - name: trigger-rebuild-of-image
        image: plugins/ansible:1
        environment:
          repo_username:
            from_secret: repo_username
          repo_password:
            from_secret: repo_password
        settings:
          private_key:
            from_secret: ansible_private_key
          playbook: ansible/playbook.yml
          inventory: ansible/inventory.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actions Ansible performs are found in the &lt;code&gt;playbook.yml&lt;/code&gt; and the machines we want to perform on are described in &lt;br&gt;
&lt;code&gt;inventory.yml&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
    - hosts: cameras

    tasks:

    - name: .ssh directory
      file:
        path: ~/.ssh
        owner: pi
        group: pi
        state: directory

    - name: put key in
      copy:
        content: "{{ lookup('env','ansible_private_key') }}"
        dest: ~/.ssh/deploy_key
        owner: pi
        group: pi
        mode: 0600
        become: pi

    - name: clone the repo
      git:  
        repo: ssh://git@git.&amp;lt;domain&amp;gt;/&amp;lt;username&amp;gt;/cctv.git
        dest: /home/pi/cctv
        key_file: ~/.ssh/deploy_key
        accept_hostkey: yes
        force: yes

    - name: Rebuild docker image
       shell:
       cmd: docker-compose up --remove-orphans --force-recreate --build -d
      chdir: /home/pi/cctv

    - name: remove private key
       shell:
       cmd: rm ~/.ssh/deploy_key

;

    all:
      hosts:
        drone.&amp;lt;domain&amp;gt;:
      children:
        cameras:
          hosts:
            192.168.0.41:
            192.168.0.42:
          vars:
            ansible_ssh_user: pi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to ansible my deployment process for updates to my CCTV cameras has been greatly simplified.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying to AWS
&lt;/h3&gt;

&lt;p&gt;Deploying at home is fun and great n all, but the training wheels must come off. Developing in the real world involves the cloud, and at the company I work for, this means AWS.&lt;/p&gt;

&lt;p&gt;Deploying your service to an AWS EC2 machine should have a testing step too, however the most common architecture (x86) is very different to the arm architecture on Raspberry Pi. We shouldn’t test our builds using an arm machine if they will be deployed to an x86 machine. We must deploy an x86 Drone runner.&lt;/p&gt;

&lt;p&gt;However, the plot twists again and in order to deploy a drone x86 runner using our environment, we need to use the Drone/cloud formation image which is only built for x86, not arm. But we can rebuild it ourselves like I have demonstrated in my &lt;a href="https://github.com/sk-t3ch/home-repo"&gt;code&lt;/a&gt;. Like a good boy, I published the &lt;a href="https://hub.docker.com/r/t3chflicks/aws-cfn"&gt;image&lt;/a&gt; on dockerhub for others to use, too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v5Nsw9li--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/10368/0%2AMvIcm-Vt6cAwJ__y" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v5Nsw9li--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/10368/0%2AMvIcm-Vt6cAwJ__y" alt="Photo by [Andreas Wagner](https://unsplash.com/@waguluz_?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@waguluz_?utm_source=medium&amp;amp;utm_medium=referral"&gt;Andreas Wagner&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Using this we can now deploy an AWS machine like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    kind: pipeline
    name: commitPipeline

    trigger:
      event:
        - push

    platform:
      os: linux
      arch: arm

    steps:
      - name: validate-seeder-infrastructure
        image: registry.&amp;lt;domain&amp;gt;/aws-cfn
        settings:
          mode: validate
          template: infrastructure.yml
        environment:
          AWS_ACCESS_KEY_ID:
            from_secret: aws_id
          AWS_SECRET_ACCESS_KEY:
            from_secret: aws_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this is deployed, we can run x86 builds with it by either specifying the architecture in the .drone.yml or by removing any architecture declaration because x86 is the default.&lt;/p&gt;

&lt;p&gt;I have also rebuilt an AWS-CLI image &lt;a href="https://hub.docker.com/r/t3chflicks/aws-cli"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We have now covered the deployment part of the pipeline and in the next article in this series we will cover the monitoring methods and discuss a few gotchas. Check it out &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-4-4-5db7c1610e3e"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Contents: &lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-1-4-336ed07a6ec0"&gt;1&lt;/a&gt;, &lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-2-4-7be3e3c292c"&gt;2&lt;/a&gt;,&lt;br&gt;
(&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-3-4-5f61c5245934"&gt;3&lt;/a&gt;), &lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-4-4-5db7c1610e3e"&gt;4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/home-devops-pipeline"&gt;T3chFlicks.org&lt;/a&gt; for more tech-focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚢 Home DevOps Pipeline: A junior engineer’s tale (2/4)</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:12:06 +0000</pubDate>
      <link>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-2-4-51lk</link>
      <guid>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-2-4-51lk</guid>
      <description>&lt;p&gt;In this series of articles, I will explain how I built my own home development environment using a couple of Raspberry Pis and a lot of software. In this article I will cover the development / CI part of the pipeline, starting with the Git Repository. The code referenced in these articles can be found &lt;a href="https://github.com/sk-t3ch/home-repo"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gitea
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q7VMg0z7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2400/1%2AJiRexLOf79xRWQXBz62Pog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q7VMg0z7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2400/1%2AJiRexLOf79xRWQXBz62Pog.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitea.io/"&gt;Gitea&lt;/a&gt; is a git repository application that can be installed on a machine. This means that the storage of the repositories is done on the machine itself as standard. However, as mistakes &lt;strong&gt;do&lt;/strong&gt; happen, I have upgraded this to an external hard drive with routine backups to AWS S3.&lt;/p&gt;

&lt;p&gt;There are major benefits to hosting your own repo at home. If like me, you often edit videos or batches of large images, then downloading stuff from the internet with cheap WiFi is slow (10mb/s) and uploading is just plain daft (500kb/s). Local network storage, however, allows much higher transfer speeds (to be measured).&lt;/p&gt;

&lt;p&gt;You may be thinking large files and git doesn’t make any sense. You are absolutely right. Any edits involving large binaries cause your git repo to explode in size. Enter stage left to solve all git/large file problems: the &lt;a href="https://git-lfs.github.com"&gt;Git LFS&lt;/a&gt; (Large File Storage) — a git plugin that stores specified files in a sensible way.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Git Large File Storage (LFS) replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Git LFS means I have all the benefits of using the beloved git with my necessary large files and enjoy those high speed transfers of local network! Hooray!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dUtqFX9l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/12048/0%2A7GalwN7QnHbmcq5Q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dUtqFX9l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/12048/0%2A7GalwN7QnHbmcq5Q" alt="Photo by [Alexander Sinn](https://unsplash.com/@swimstaralex?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@swimstaralex?utm_source=medium&amp;amp;utm_medium=referral"&gt;Alexander Sinn&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;I’ve introduced Gitea’s use case without giving you much of an explanation of what it is and offers. For that, you should take a look at &lt;a href="https://docs.gitea.io/en-us/comparison/"&gt;this&lt;/a&gt; comparison between Gitea and other Git repository providers.&lt;/p&gt;

&lt;p&gt;To create your own gitea application, use &lt;code&gt;docker-compose&lt;/code&gt; to run this template which has a gitea and postgres image (production ready db):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: “2”

networks:
  appnet:
    external: false

volumes:
  gitea-app:
  gitea-db:

services:

gitea-app:
    image: webhippie/gitea
    env_file:
      - gitea.env
    container_name: gitea-app
    restart: always
    networks
      - appnet
    volumes:
      - ./volumes/gitea_data:/data
      - ./custom:/data/gitea
    ports:
      - "222:22"
      - "3000:3000"
     depends_on:
      - gitea-db

gitea-db:
    image: postgres:alpine
    container_name: gitea-db
    ports:
      - 5432:5432
    restart: always
    volumes:
      - ./volumes/gitea_db:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gite
    networks:
      - appnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this file, the gitea service references a file &lt;code&gt;gitea.env&lt;/code&gt;. This is the configuration file for gitea, which allows us to set up this home repo in such a way that we don’t need to any set up inside the application itself — meaning we can run straight from a backup.&lt;/p&gt;

&lt;p&gt;After running &lt;code&gt;docker-compose up&lt;/code&gt; , you should be able to access this on your local network under &lt;code&gt;raspberrypi.local:3000&lt;/code&gt; and use it. Happy days.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7jgqKDQI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/9184/0%2A5MOqu-ArI9QekHos" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7jgqKDQI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/9184/0%2A5MOqu-ArI9QekHos" alt="Photo by [Harrison Broadbent](https://unsplash.com/@hbtography?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@hbtography?utm_source=medium&amp;amp;utm_medium=referral"&gt;Harrison Broadbent&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;h3&gt;
  
  
  External Hard Drive
&lt;/h3&gt;

&lt;p&gt;With gitea up and running, I want to add a larger storage device than the Micro SD card. To do so, we need only mount a hard drive like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mount /dev/sda1 /media/hdd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once mounted, you can reference the location &lt;code&gt;/media/hdd&lt;/code&gt; just like you would any other folder. Now we want to be able to routinely backup the contents of this drive. Handily, someone has made a docker image which does just that &lt;a href="https://github.com/istepanov/docker-backup-to-s3"&gt;https://github.com/istepanov/docker-backup-to-s3&lt;/a&gt;. However, as expected this image isn’t for ARM architecture so I rebuilt the image using a Raspbian base on my Pi.&lt;/p&gt;

&lt;p&gt;Adding this to our current &lt;code&gt;docker-compose.yml&lt;/code&gt; like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  backup:
    image: t3chflicks/backuper
    environment:
      - ACCESS_KEY=
      - SECRET_KEY=
      - S3_PATH=
      - CRON_SCHEDULE=0 12 * * *
    volumes:
      - /media/hd:/data:ro
    container_name: backup
    restart: always

gitea-app:
    image: webhippie/gitea
    env_file:
      - gitea.env
    container_name: gitea-app
    restart: always
    networks
      - appnet
    volumes:
      - /media/hd/gitea_data:/data
    ports:
      - "222:22"
      - "3000:3000"
    depends_on:
      - gitea-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After enabling Git LFS in your Gitea Config, we can also install the git plugin on our repo as simple as &lt;code&gt;git lfs install&lt;/code&gt; and subsequently tagging files like this: &lt;code&gt;git lfs track "*.mp4"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you can use your git repo to work with large media files and that is awesome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traefik
&lt;/h3&gt;

&lt;p&gt;Having a home repository is great. But, since I am not a complete hermit and want to collaborate with others, I then went on to add external access to my repo. I did this using &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt;, the reverse proxy which gives automatic SSL encryption with certificates from &lt;a href="http://letsencrypt.org"&gt;Let’s Encrypt&lt;/a&gt; and I used a free domain from &lt;a href="https://www.duckdns.org/"&gt;duckdns&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I found the Traefik docs a little bit confusing but it turned out to be super simple to add into the current docker-compose.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: “2”
networks:
  appnet:
    external: false
  homereponet:
    external: true

volumes:
  gitea-app:
  gitea-db:
  traefik:

services:
  gitea-app:
    image: webhippie/gitea
    env_file:
      - gitea.env
    container_name: gitea-app
    restart: always
    networks
      - appnet
    volumes:
      - /media/hdd/gitea_data:/data
    ports:
      - "222:22"
      - "3000:3000"
    depends_on:
      - gitea-db
  depends_on:
    - gitea-db
    - traefik
  labels:
    - "traefik.enable=true"
    - traefik.backend=gitea-app
    - traefik.git.frontend.rule=Host:git.&amp;lt;your_domain&amp;gt;
    - traefik.docker.network=homereponet
    - traefik.git.port=3000

gitea-db:
    image: postgres:alpine
    container_name: gitea-db
    ports:
      - 5432:5432
    restart: always
    volumes:
      - /media/hdd/gitea_db:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    networks:
     - appnet

  traefik:
    image: traefik:1.7-alpine
    container_name: “traefik”
    ports:
      - “80:80”
      - “443:443”
    networks:
      - homereponet
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik/traefik.toml:/traefik.toml 
      - ./traefik/acme:/etc/traefik/acme
    restart: always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And this requires some extra steps of creating the acme folder and editing permissions as well as adding this traefik.toml file&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;debug = false
logLevel = “ERROR”
defaultEntryPoints = [“https”,”http”]

[entryPoints]

[entryPoints.http]
address = “:80”
[entryPoints.http.redirect]
entryPoint = “https”
[entryPoints.https]
address = “:443”
[entryPoints.https.tls]

[retry]

[docker]
endpoint = “unix:///var/run/docker.sock”
domain = “&amp;lt;your domain&amp;gt;”
watch = true
exposedByDefault = false

[acme]
email = “&amp;lt;your email&amp;gt;”
caServer = “https://acme-v02.api.letsencrypt.org/directory"
storage = “acme.json”
entryPoint = “https”
onHostRule = true

[acme.httpChallenge]
entryPoint = “http”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But now as long as you have exposed port 443 and forwarded the traffic to your Pi, you should be able to access your home repository via your duckdns address and it will encrypted on &lt;em&gt;https&lt;/em&gt;. How great!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zOrC5cTv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6720/1%2ATPubvAkPg-kKi150QLzkGg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zOrC5cTv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6720/1%2ATPubvAkPg-kKi150QLzkGg.png" alt="Alt Tea Theme (Dark Mode)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Alt Tea Theme (Dark Mode)*&lt;/p&gt;

&lt;h3&gt;
  
  
  Drone
&lt;/h3&gt;

&lt;p&gt;We now have our own private Git repo setup in a Docker environment with Postgres, Git LFS, external hard drive storage and external SSL encrypted access. This is great: we can work happily on our project, knowing that we can easily work with large files and work with git from anywhere.&lt;/p&gt;

&lt;p&gt;However, this isn’t exactly DevOps. Using git provides you with the continuous integration of your code via pull requests etc., but it does not provide continuous delivery. For that, we look to a software called &lt;a href="https://drone.io/"&gt;Drone&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NEMlQdA_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/8064/0%2AhPTZq_bDHmlLIBLr" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NEMlQdA_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/8064/0%2AhPTZq_bDHmlLIBLr" alt="Photo by [Iewek Gnos](https://unsplash.com/@imkirk?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@imkirk?utm_source=medium&amp;amp;utm_medium=referral"&gt;Iewek Gnos&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;We’ll be using Drone to manage pipelines in which we build Docker containers, test and deploy to machines.&lt;/p&gt;

&lt;p&gt;Like all the other services discussed so far, Drone has an &lt;a href="https://hub.docker.com/r/drone/drone"&gt;image&lt;/a&gt; on DockerHub. To add this to our current setup, we need to add the following to our &lt;em&gt;docker-compose&lt;/em&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  drone:
    image: drone/drone:1-linux-arm
    container_name: drone
    volumes:
      - /media/hdd/drone:/var/lib/drone/
    restart: always
    depends_on:
      - gitea-app
    environment:
      - DRONE_OPEN=true
      - DRONE_GITEA_CLIENT_ID=&amp;lt;gitea_auth_id&amp;gt;
      - DRONE_GITEA_CLIENT_SECRET=&amp;lt;gitea_auth_secret&amp;gt;
      - DRONE_GITEA_SERVER=http://git.&amp;lt;your_domain&amp;gt;
      - DRONE_SERVER_HOST=drone.&amp;lt;your_domain&amp;gt;
      - DRONE_SERVER_PROTO=https
      - DRONE_TLS_AUTOCERT=false
      - DRONE_RPC_SECRET=&amp;lt;drone_runner_secret&amp;gt;
      - DRONE_AGENTS_ENABLED=true
    networks:
      - homereponet
      - appnet
    labels:
      - “traefik.enable=true”
      - traefik.backend=drone
      - traefik.drone.frontend.rule=Host:drone.&amp;lt;your_domain&amp;gt;
      - traefik.drone.port=80
      - traefik.docker.network=homereponet

runner:
    container_name: runner
    image: drone/drone-runner-docker:1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - drone
    environment:
      - DRONE_RPC_HOST=drone.&amp;lt;your_domain&amp;gt;
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_SECRET=&amp;lt;drone_runner_secret&amp;gt;
    restart: always
    ports:
      - “3000:3000”
    networks:
      - appnet
      - homereponet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, when starting up the system you and visiting &lt;code&gt;https://drone.&amp;lt;domain&amp;gt;&lt;/code&gt;you will get an error when trying to login. This is because you need to have Gitea running and create the OAuth application first and then rebuild your containers, setting &lt;code&gt;DRONE_GITEA_CLIENT_SECRET&lt;/code&gt; to the new value — this is described &lt;a href="https://docs.drone.io/installation/providers/gitea/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will now be able to login to Drone using your Gitea credentials. Following this, you should sync the repositories of your Gitea by clicking the big button. The next thing you’ll want to try is doing a build, for this you should add a &lt;code&gt;.drone.yml&lt;/code&gt; file to your repo and on push, it should trigger a build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hkv8xEWp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/1%2A-QEZBWRVw9FbsdTsuetzIQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hkv8xEWp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/1%2A-QEZBWRVw9FbsdTsuetzIQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each step is a build in an isolated environment and executed on the Drone-runner. Drone-runners build for one architecture so we will only build Raspberry Pi (arm v7) images on a Pi runner but for x86 builds we will need to deploy an EC2 on AWS (more about that later). My example drone file looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: pipeline
name: commitPipeline

trigger:
  event:
    - push

platform:
  os: linux
  arch: arm

steps:
  - name: buildsomething
    image: python:alpine
    commands:
      - echo ‘hello world’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Voila, you are now the proud owner of a home repo! We’ll ignore the fact that it probably still can’t do as much as Gitlab out of the box, but it is yours (woo, capitalism!).&lt;/p&gt;

&lt;h3&gt;
  
  
  Registry
&lt;/h3&gt;

&lt;p&gt;DockerHub is great, just like Github is great. This is because they provide a place for people to share their work. However, just as the argument for the private repo is not wanting to share and for fast local speed, the same applies to Docker registries.&lt;/p&gt;

&lt;p&gt;We will be creating our own place to store the images we’ve made using Docker’s own registry image. To add this into our existing Docker-compose file, we just need to add this extra service:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;registry:
  image: registry:2
  container_name: registry
  restart: always
  networks:
    - homerepone
  volumes:
    - ./volumes/registry:/var/lib/registr
    - ./auth:/auth
  labels:
    - “traefik.enable=true”
    - traefik.registry.frontend.rule=Host:registry.&amp;lt;your_domain&amp;gt;
    - traefik.backend=registry
    - traefik.docker.network=homereponet
    - traefik.registry.port=5000
  environment:
    - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/data
    - REGISTRY_AUTH=htpasswd
    - REGISTRY_AUTH_HTPASSWD_REALM=Registry
    - REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This references an &lt;code&gt;auth/registry.password&lt;/code&gt; file which we create using apache2-utils bcrypt hash password like described &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-18-04"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Running our new &lt;code&gt;docker-compose.yml&lt;/code&gt; means you can access the registry by logging in using the docker cli using &lt;code&gt;docker login [https://registry.&amp;lt;](https://registry.homerepo4.duckdns.org)your_domain&amp;gt;&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;We have now covered the development part of the pipeline and in the next article in this series we will cover the deployment methods. Check it out &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-3-4-5f61c5245934"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Contents: &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-1-4-336ed07a6ec0"&gt;1&lt;/a&gt;, (&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-2-4-7be3e3c292c"&gt;2&lt;/a&gt;), &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-3-4-5f61c5245934"&gt;3&lt;/a&gt;, &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-4-4-5db7c1610e3e"&gt;4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/home-devops-pipeline"&gt;T3chFlicks.org&lt;/a&gt; for more tech-focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚢 Home DevOps Pipeline: A junior engineer’s tale (1/4)</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:11:28 +0000</pubDate>
      <link>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-1-4-3ioo</link>
      <guid>https://dev.to/t3chflicks/home-devops-pipeline-a-junior-engineer-s-tale-1-4-3ioo</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HS1bYRda--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3840/1%2AbCBDSt_m0q5Cgirh4SjaaQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HS1bYRda--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3840/1%2AbCBDSt_m0q5Cgirh4SjaaQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been working as a software engineer for just over a year whilst simultaneously working on many of my own side projects such as &lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w?view_as=subscriber"&gt;T3chFlicks&lt;/a&gt; (an edutainment channel). During this period, I have picked up many different technologies &amp;amp; skills, however one role I managed to stay away from until the past couple of months is DevOps.&lt;/p&gt;

&lt;p&gt;In this series of articles, I will explain how I applied this new-found knowledge by building my own home development environment using a couple of Raspberry Pis.&lt;/p&gt;

&lt;p&gt;This environment allows me to do things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Host my own reliable Git Repo (&lt;a href="http://gitea.io"&gt;Gitea&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Effortlessly work with large media files using git (&lt;a href="https://git-lfs.github.com"&gt;LFS&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access my Git Repo remotely with SSL (&lt;a href="https://git-lfs.github.com"&gt;Traefik&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Work with &lt;a href="https://www.docker.com"&gt;docker&lt;/a&gt; containers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build, test and deploy with a git push (&lt;a href="https://drone.io"&gt;Drone&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitor all parts of the system (&lt;a href="https://www.portainer.io"&gt;Portainer&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy builds to groups of devices locally and on the internet (&lt;a href="https://www.ansible.com"&gt;Ansible&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am still inexperienced with this tech and am more big ideas than skill, but I’ve come a long way since I first began and I want to share my journey and process. &lt;strong&gt;I do this to learn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The code referenced in these articles can be found &lt;a href="https://github.com/sk-t3ch/home-repo"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Home Repo
&lt;/h2&gt;

&lt;p&gt;To follow the rest of these blog posts, you must have a basic knowledge of Docker and Git, so let’s take a brief detour…&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;p&gt;My introduction to DevOps was after I built a service which was going to be deployed on an AWS ECS (&lt;a href="https://aws.amazon.com/ecs/"&gt;Elastic Container Service&lt;/a&gt;) cluster. This service needed to be “dockerised”. This means it needed to be created within a reliably recreatable environment known as a container.&lt;/p&gt;

&lt;p&gt;Docker is the containerisation software which enables us to do the above by building environments with a YAML template known as a &lt;code&gt;Dockerfile&lt;/code&gt;. We can build our environments on top of base images such as Ubuntu e.g.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**FROM** ubuntu:18.04
**RUN** apk update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and you can run it like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`docker build -t myFirstDockerContainer . &amp;amp;&amp;amp; docker run myFirstDockerContainer`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Docker CLI enables you to control containers and push and pull images from registries such as &lt;a href="https://hub.docker.com"&gt;DockerHub&lt;/a&gt;. However, you can use it to control a lot more, including networks, volumes, and even collections of containers working together as part of a swarm.&lt;/p&gt;

&lt;p&gt;In my opinion, Docker is great. The main reason for this is that I spend less time debugging… I hate debugging. Software engineering to me is all about creating and I shouldn’t spend my time being a software mechanic.&lt;/p&gt;

&lt;p&gt;Go ahead and explore &lt;a href="https://hub.docker.com"&gt;DockerHub&lt;/a&gt;. If there isn’t an image for your favourite software, why not make it and publish it (preferably using alpine — the smallest base image).&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker-Compose
&lt;/h3&gt;

&lt;p&gt;After you’ve explored Docker, you’ll soon want to connect your separate services. For example, if you want to run a web app with a database. To set this up in a single file there exists &lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3"

networks:
  someNetwork:

volumes:
  someVolume:

services:
  webapp:
    image: someWebAppImage

database:
    image: someDatabaseImage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Docker-compose is elegant, simple and a big upgrade to my developing practice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b6jA47VL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7982/0%2AAKkE9RlpAu233-IT" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b6jA47VL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7982/0%2AAKkE9RlpAu233-IT" alt="Photo by [chuttersnap](https://unsplash.com/@chuttersnap?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@chuttersnap?utm_source=medium&amp;amp;utm_medium=referral"&gt;chuttersnap&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;For me, git is a must, even when you are working on your own projects at home. It’s just so damn useful. I can’t tell you how many times I have gone through git log to find that magical piece of code that actually works. Hammer! Hammer! Hammer! (one day i will get good enough at TDD).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O28csnfe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/10944/0%2AlN-f7HFa7bDae2zI" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O28csnfe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/10944/0%2AlN-f7HFa7bDae2zI" alt="Photo by [Sean Stratton](https://unsplash.com/@seanstratton?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@seanstratton?utm_source=medium&amp;amp;utm_medium=referral"&gt;Sean Stratton&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;The way you fell in love with git is probably by putting your code online onto a website such as Github and collaborating with others on it. You probably then went on to think that what you made was so amazing that you thought someone else might come along and steal it and you’d never get to be the next Facebook. So you decided to make it a private repository.&lt;/p&gt;

&lt;p&gt;Gitlab is an open source alternative to Github. It’s also pretty great and offers loads of awesome features for free. An alternative to a managed service is to host your own git repository server, such as the one &lt;a href="https://docs.gitlab.com/ee/install/README.html"&gt;Gitlab&lt;/a&gt; provide (the software requires 4GB of RAM for 1000 concurrent users), which the Raspberry Pi 4 contains, but I’d rather run smaller and separate services. Instead, I opted for &lt;a href="http://gitea.io"&gt;Gitea&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Right. Slight detour over, now onto the proper stuff — check out the second article in this series &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-2-4-7be3e3c292c"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Contents: (&lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-1-4-336ed07a6ec0"&gt;1&lt;/a&gt;), &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-2-4-7be3e3c292c"&gt;2&lt;/a&gt;, &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-3-4-5f61c5245934"&gt;3&lt;/a&gt;, &lt;a href="https://medium.com/@t3chflicks/home-devops-pipeline-a-junior-engineers-tale-4-4-5db7c1610e3e"&gt;4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/home-devops-pipeline"&gt;T3chFlicks.org&lt;/a&gt; for more tech-focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>☁️ 🚀 Cheaper than API Gateway — ALB with Lambda using CloudFormation</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:07:03 +0000</pubDate>
      <link>https://dev.to/t3chflicks/cheaper-than-api-gateway-alb-with-lambda-using-cloudformation-10pj</link>
      <guid>https://dev.to/t3chflicks/cheaper-than-api-gateway-alb-with-lambda-using-cloudformation-10pj</guid>
      <description>&lt;p&gt;An alternative to API gateway is Application Load Balancer. ALB can be connected with Lambda to produce a highly performant, cost effective API. In this article, I demonstrate how to create an API using CloudFormation and Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway vs. Application Load Balancer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html"&gt;API Gateway&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html"&gt;ALB&lt;/a&gt; are two &lt;strong&gt;different&lt;/strong&gt; AWS services, however they can both be used to achieve the same thing: send network requests for a service to the service.&lt;/p&gt;

&lt;p&gt;API Gateway is pay per request whereas ALBs have an hourly rate, therefore deciding which to use depends on traffic volume. For an in-depth price and feature comparison, I recommend this &lt;a href="https://serverless-training.com/articles/save-money-by-replacing-api-gateway-with-application-load-balancer/"&gt;article&lt;/a&gt;, which shares this shocking statistic:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I expect to pay around $166 per month for ALB, whereas I’m paying $4,163 per month for the exact same service from API Gateway&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a massive saving! That said, the cost difference isn’t unmerited and you do get extra features from API gateway. Dougal Ballantyne, the Head of Product for Amazon API Gateway tweeted:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you are building an API and want to leverage AuthN/Z, request validation, rate limiting, SDK generation, direct AWS service backend, use &lt;a href="https://twitter.com/hashtag/APIGateway?src=hash"&gt;#APIGateway&lt;/a&gt;. If you want to add Lambda to an existing web app behind ALB you can now just add it to the needed route&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, that’s exactly what we’re gonna do!&lt;/p&gt;

&lt;h2&gt;
  
  
  API with ALB and Lambda
&lt;/h2&gt;

&lt;p&gt;I am going to build the following the system:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dHz787H4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ay7xiCXLyQi7iZ_nxQUorxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dHz787H4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ay7xiCXLyQi7iZ_nxQUorxg.png" alt="Architecture Diagram"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Architecture Diagram*&lt;/p&gt;

&lt;p&gt;The complete CloudFormation templates can be found &lt;a href="https://github.com/sk-t3ch/AWS-API-With-ALB-And-Lambda"&gt;here&lt;/a&gt;, split into two templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;vpc.yml&lt;/code&gt; — Configures the VPC. For simplicity, the VPC only contains two public subnets. You can read about a more complex VPC in our &lt;a href="https://medium.com/@t3chflicks/virtual-private-cloud-on-aws-quickstart-with-cloudformation-4583109b2433"&gt;previous article&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;service.yml&lt;/code&gt;— Configures both the ALB and Lambda function.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Lambda
&lt;/h3&gt;

&lt;p&gt;To create a Lambda using CloudFormation, it is necessary to define a Lambda Function, a Role to run the Lambda and a Permission for the ALB to invoke the Lambda:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lambda:
    Type: AWS::Lambda::Function
    Properties:
    Code:
        ZipFile: |
        def handler(event, context):
        response = {
            'isBase64Encoded': False,
            'statusCode': 200,
            'body': 'HELLO WORLD!'
        }
        return response
    Handler: lambda_function.handler
    Role: !GetAtt LambdaRole.Arn
    Runtime: python3.7

LambdaRole:
    Type: AWS::IAM::Role
    Properties:
    AssumeRolePolicyDocument:
        Statement:
        - Action: ['sts:AssumeRole']
            Effect: Allow
            Principal:
            Service: ['lambda.amazonaws.com']
    Path: /
    ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

LambdaFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
    FunctionName: !GetAtt Lambda.Arn
    Action: 'lambda:InvokeFunction'
    Principal: elasticloadbalancing.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Application Load Balancer
&lt;/h3&gt;

&lt;p&gt;The Load Balancer is placed across public subnets as it needs to be accessible &lt;strong&gt;from&lt;/strong&gt; the internet. The ALB is configured to listen to HTTP traffic on port 80 and forward it to the Lambda:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoadBalancerSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
    GroupDescription: Load balance allow port 80 traffic
    VpcId: !ImportValue VPCID
    SecurityGroupIngress:
        CidrIp: 0.0.0.0/0
        FromPort: 80
        IpProtocol: TCP
        ToPort: 80

LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
    SecurityGroups:
        - !Ref LoadBalancerSecGroup
    Subnets:
        - !ImportValue PublicSubnetA
        - !ImportValue PublicSubnetB

LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
    Protocol: HTTP
    LoadBalancerArn: !Ref LoadBalancer
    DefaultActions:
        - Type: forward
        TargetGroupArn: !Ref LoadBalancerTargetGroup
    Port: 80

LoadBalancerTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
    TargetType: lambda
    Targets:
        - AvailabilityZone: all
        Id: !GetAtt Lambda.Arn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The complete code can be accessed &lt;a href="https://github.com/sk-t3ch/AWS-API-With-ALB-And-Lambda-Quickstart"&gt;here&lt;/a&gt; and can be deployed using the AWS CLI:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws cloudformation create-stack --stack-name service --template-body file://template.yml --capabilities CAPABILITY_NAMED_IAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After a successful deployment, the DNS name of the ALB can be found in the EC2 section of the AWS console. It should look something like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loadb-LoadB-R7RVQD09YC9O-1401336014.eu-west-1.elb.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now it is possible to make a request to this URL and get a response:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl loadb-LoadB-R7RVQD09YC9O-1401336014.eu-west-1.elb.amazonaws.com'

HELLO WORLD!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BCN2ANIq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/9504/0%2A2xL0e82Smd6by2-d" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BCN2ANIq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/9504/0%2A2xL0e82Smd6by2-d" alt="Photo by [Nghia Le](https://unsplash.com/@lephunghia?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@lephunghia?utm_source=medium&amp;amp;utm_medium=referral"&gt;Nghia Le&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross Origin Response Sharing
&lt;/h3&gt;

&lt;p&gt;In order to access this service from a browser webpage on a different domain, CORS must be enabled. This is done by setting headers:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lambda:
    Type: AWS::Lambda::Function
    Properties:
    Code:
        ZipFile: |
        def handler(event, context):
        response = {
            'isBase64Encoded': False,
            'statusCode': 200,
            'body': 'HELLO WORLD!',
            'headers': {
                'access-control-allow-methods': 'GET',
                'access-control-allow-origin': '*',
                'access-control-allow-headers': 'Content-Type, Access-Control-Allow-Headers'
            }
        }
        return response
    Handler: lambda_function.handler
    Role: !GetAtt LambdaRole.Arn
    Runtime: python3.7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Use a Domain Name
&lt;/h3&gt;

&lt;p&gt;AWS provides an ugly Load Balancer address such as:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loadb-LoadB-R7RVQD09YC9O-1401336014.eu-west-1.elb.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But it’s quite simple to use a custom domain using AWS. Firstly, transfer your DNS management to &lt;a href="https://aws.amazon.com/route53/"&gt;Route 53&lt;/a&gt; and then create a new record set aliased to the load balancer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thanks For Reading
&lt;/h3&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/aws-quickstart-series"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://serverless-training.com/articles/save-money-by-replacing-api-gateway-with-application-load-balancer/"&gt;https://serverless-training.com/articles/save-money-by-replacing-api-gateway-with-application-load-balancer/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/python-package.html"&gt;https://docs.aws.amazon.com/lambda/latest/dg/python-package.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html"&gt;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html"&gt;https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>🚫 Users Only: Quickstart for creating a SaaS pt. 1 — User Management</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:06:36 +0000</pubDate>
      <link>https://dev.to/t3chflicks/users-only-quickstart-for-creating-a-saas-pt-1-user-management-57jc</link>
      <guid>https://dev.to/t3chflicks/users-only-quickstart-for-creating-a-saas-pt-1-user-management-57jc</guid>
      <description>&lt;p&gt;Managing users and API keys is a necessary task for creating a Software as a Service (SaaS). In this article, I demonstrate how to create a simple, cost effective serverless SaaS user management application. The frontend is created using Vue JS and the Amplify plugin. The backend uses Cognito for authentication of the user management API, and a key system created with DynamoDB which authorises users to access a test API created with Application Load Balancer. Check out the live demo 💽.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cqxqkbhg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/1%2A9RH_PC5EM5l0fM9wnEE97Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cqxqkbhg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/1%2A9RH_PC5EM5l0fM9wnEE97Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not everything is intended for everyone. In a scenario where restricting access is necessary, user management must become a component of the architecture. The AWS solution to user management is the &lt;a href="https://aws.amazon.com/cognito/"&gt;Cognito&lt;/a&gt; service. This integrates simply with &lt;a href="https://aws.amazon.com/api-gateway/"&gt;API Gateway&lt;/a&gt; — but as described in the previous article, API gateway can get pretty expensive, and for high load the more cost effective alternative is &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html"&gt;Application Load Balancer&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/cheaper-than-api-gateway-alb-with-lambda-using-cloudformation-b32b126bbddc"&gt;&lt;strong&gt;Cheaper than API Gateway — ALB with Lambda using CloudFormation&lt;/strong&gt;&lt;br&gt;
*An alternative to API gateway is Application Load Balancer. ALB can be connected with Lambda to produce a highly…*medium.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture for this user management application (see below) makes use of both API gateway and ALB for their respective benefits. API gateway is chosen for the User API for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the requests are likely to be low in volume meaning costs will be low&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the requests are direct from the frontend with authentication using the Amplify package with the Vue JS framework&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ALB is chosen for the example service Test API as it is expected to experience a high volume of requests which might rack up a hefty bill on API Gateway. Also, the requests are likely to be from other servers meaning API keys are preferable. Users are generated an API key on sign up which is used to authenticate the Test API. Usage of the API is monitored by incrementing a count on the User table each time a request is made to the Test API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CU81bLvK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AX5y3CnFO29vNlm9E65W8Aw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CU81bLvK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AX5y3CnFO29vNlm9E65W8Aw.png" alt="User Manager Quickstart Architecture Diagram"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;User Manager Quickstart Architecture Diagram*&lt;/p&gt;

&lt;p&gt;This architecture is cost effective for high-load APIs. A quote from this &lt;a href="https://serverless-training.com/articles/save-money-by-replacing-api-gateway-with-application-load-balancer/"&gt;article&lt;/a&gt; might help you decide if your service falls into that category:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  $15 (ALB’s cost) worth of API Gateway calls will net you around 4.3 million API calls in the month (well, 5.3 million if you count the free tier). So, if your API is small enough to fit under that number of calls, stick with API Gateway — it’s simpler to use. But, 5.3 million API calls is only around two requests per second. So, if your API is used very much at all — or even if you have DNS health checks enabled — you could easily end up paying more for API Gateway than you would for Application Load Balancer.
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;I presume this article will not bring in more than two reads per second, meaning an ALB would not be suitable for running the demo of the service. Instead, the test API seen in the diagram has been replaced with another API Gateway. You can see a demonstration of the app in use below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/AWS-user-manager-app-quickstart"&gt;The full code for this project can be found here ☁️&lt;/a&gt;
&lt;/h1&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://um-app.t3chflicks.org"&gt;The live demo can be found here 💽&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V-GIDMLI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ad0DiGLhhjM9U6nh80BX2-A.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V-GIDMLI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ad0DiGLhhjM9U6nh80BX2-A.gif" alt="Video of Use Manager App"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Video of Use Manager App*&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Build! 🔩
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;The infrastructure for this system is written as code using the &lt;a href="https://aws.amazon.com/cloudformation/"&gt;CloudFormation&lt;/a&gt; framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC
&lt;/h3&gt;

&lt;p&gt;ALBs must be placed within a Virtual Private Cloud on AWS. The service uses a stripped down VPC consisting of only two public subnets and an Internet Gateway. A more complete VPC is described in a previous article:&lt;br&gt;
&lt;a href="https://medium.com/swlh/virtual-private-cloud-on-aws-quickstart-with-cloudformation-4583109b2433"&gt;&lt;strong&gt;Virtual Private Cloud on AWS — Quickstart with CloudFormation&lt;/strong&gt;&lt;br&gt;
*A Virtual Private Cloud is the foundation from which to build a new system. In this article, I demonstrate how to…*medium.com&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  User Management with Cognito
&lt;/h3&gt;

&lt;p&gt;The AWS Cognito service is used to manage users. Users are stored in user pools, and Clients (mobile or web apps) can interact with them using an API. I configured the Cognito user pool to send an email with a verification code on sign up.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
    ClientName: CognitoUserPoolClient
    UserPoolId: !Ref CognitoUserPool
    AllowedOAuthFlows:
    - implicit
    AllowedOAuthFlowsUserPoolClient: true
    AllowedOAuthScopes:
    - email
    - openid
    LogoutURLs:
    - !Sub 
        - https://um-app.${ Domain }
        - Domain: !ImportValue UserManagerApp-RootDomain
    CallbackURLs:
    - !Sub 
        - https://um-app.${ Domain }
        - Domain: !ImportValue UserManagerApp-RootDomain
    SupportedIdentityProviders:
    - COGNITO  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You must also define a client; this allows for OAuth on our frontend client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NB: Setting the URLs to &lt;code&gt;localhost&lt;/code&gt; allows for local development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
    ClientName: CognitoUserPoolClient
    UserPoolId: !Ref CognitoUserPool
    AllowedOAuthFlows:
    - implicit
    AllowedOAuthFlowsUserPoolClient: true
    AllowedOAuthScopes:
    - email
    - openid
    LogoutURLs:
    - !Sub 
        - https://um-app.${ Domain }
        - Domain: !ImportValue UserManagerApp-RootDomain
    CallbackURLs:
    - !Sub 
        - https://um-app.${ Domain }
        - Domain: !ImportValue UserManagerApp-RootDomain
    SupportedIdentityProviders:
    - COGNITO 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cognito user pools offer useful event tiggers such as sign up confirmation. I have used the &lt;code&gt;post-confirmation&lt;/code&gt; event to trigger a Lambda function which writes the user’s username, a newly generated API key, and a zero initialised counter to a DynamoDB table. The table has a Global Secondary Index (GSI) on the &lt;code&gt;Key&lt;/code&gt; attribute which allows for a user lookup with just the &lt;code&gt;Key&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserTable: 
    Type: AWS::DynamoDB::Table
    Properties: 
    AttributeDefinitions: 
        - AttributeName: "Id"
        AttributeType: "S"
        - AttributeName: "Key"
        AttributeType: "S"
    KeySchema: 
        - AttributeName: "Id"
        KeyType: "HASH"
    ProvisionedThroughput: 
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
    GlobalSecondaryIndexes: 
        - IndexName: "KeyLookup"
        KeySchema: 
            - AttributeName: "Key"
            KeyType: "HASH"
        Projection: 
            ProjectionType: "ALL"
        ProvisionedThroughput: 
            ReadCapacityUnits: 1
            WriteCapacityUnits: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;PostConfirmation&lt;/code&gt; Lambda function template:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostConfirmationLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
    CodeUri: ./PostConfirmation
    Handler: main.handler
    MemorySize: 128
    Runtime: python3.8
    Timeout: 60
    Role: !GetAtt PostConfirmationLambdaRole.Arn
    Environment:
        Variables:
        TABLE_NAME: !Ref UserTable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;PostConfirmation&lt;/code&gt; Lambda is a Python function which creates an API key and stores it in the DynamoDB table:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import os

dynamodb = boto3.resource('dynamodb')
TABLE_NAME = os.environ['TABLE_NAME']
table = dynamodb.Table(TABLE_NAME)

def handler(event, context):
try:
    username = event["userName"]
    table.put_item(
        Item={
                'Id': username,
                'Key': os.urandom(64).hex(),
                'Count': 0
        }
        )
except Exception as err:
    print("ERR: ", err)
return event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  User API
&lt;/h3&gt;

&lt;p&gt;Endpoints created for users to access their profile and generate an API key are authenticated for the logged in user. Authentication is easily added to API gateway with Cognito (CORS are set open for development purposes).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
    Body:
        info:
        version: '1.0'
        title: !Ref 'AWS::StackName'
        paths:
        /user:
            options:
            x-amazon-apigateway-integration:
                type: mock
                requestTemplates:
                application/json: |
                    {
                    "statusCode" : 200
                    }
                responses:
                default:
                    statusCode: '200'
                    responseTemplates:
                    application/json: |
                        {}
                    responseParameters:
                    method.response.header.Access-Control-Allow-Origin: '''*'''
                    method.response.header.Access-Control-Allow-Methods: '''*'''
                    method.response.header.Access-Control-Allow-Headers: '''*'''
            consumes:
                - application/json
            summary: CORS support
            responses:
                '200':
                headers:
                    Access-Control-Allow-Origin:
                    type: string
                    Access-Control-Allow-Headers:
                    type: string
                    Access-Control-Allow-Methods:
                    type: string
                description: Default response for CORS method
            produces:
                - application/json
            get:
            x-amazon-apigateway-integration:
                httpMethod: GET
                type: aws_proxy
                uri: !Sub &amp;gt;-
                arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetUserFunction.Arn}/invocations
            security:
                - CognitoUserPoolAuthorizer: []
            responses: {}
        swagger: '2.0'
        securityDefinitions:
        CognitoUserPoolAuthorizer:
            in: header
            type: apiKey
            name: Authorization
            x-amazon-apigateway-authorizer:
            providerARNs:
                - !ImportValue UserManagerApp-CognitoUserPoolArn
            type: cognito_user_pools
            x-amazon-apigateway-authtype: cognito_user_pools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The code for getting and generating keys is very similar to the post-confirmation Lambda code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service API — Test
&lt;/h3&gt;

&lt;p&gt;The API I’ve created with ALB is for the high traffic volume endpoints which are authenticated using an API key. This API key is sent in a POST request to the service.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
    SecurityGroups:
        - !Ref LoadBalancerSecGroup
    Subnets:
        - !ImportValue UserManagerApp-PublicSubnetA
        - !ImportValue UserManagerApp-PublicSubnetB

LoadBalancerTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
    TargetType: lambda
    Targets:
        - AvailabilityZone: all
        Id: !GetAtt Lambda.Arn

LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn:
    - LambdaFunctionPermission
    Properties:
    LoadBalancerArn: !Ref LoadBalancer
    DefaultActions:
        - Type: forward
        TargetGroupArn: !Ref LoadBalancerTargetGroup
    Port: 443
    Certificates:
        - CertificateArn: !ImportValue UserManagerApp-RegionalCertArn
    Protocol: HTTPS

Lambda:
    Type: AWS::Lambda::Function
    Properties:
    Code:
        S3Bucket: !ImportValue UserManagerApp-CodeBucketName
        S3Key: TestKey.zip
    Description: Test Service function
    Handler: main.handler
    MemorySize: 256
    Role: !GetAtt LambdaRole.Arn
    Runtime: python3.8
    Timeout: 60
    Environment:
        Variables:
        TABLE_NAME: !ImportValue  UserManagerApp-UserTableName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The service uses the user’s API key for a query on a GSI of the user table to get the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;Count&lt;/code&gt;, then increment the &lt;code&gt;Count&lt;/code&gt; and return the updated &lt;code&gt;Count&lt;/code&gt;. Again, I have added open CORS whilst developing. For security, these should been locked down.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
from boto3.dynamodb.conditions import Key
import os
import time
import json
from decimal import Decimal


dynamodb = boto3.resource('dynamodb')
TABLE_NAME = os.environ['TABLE_NAME']
table = dynamodb.Table(TABLE_NAME)

def handler(event, context):
response = {
    "isBase64Encoded": False,
    "headers": {
        'access-control-allow-methods': 'POST, OPTIONS',
        'access-control-allow-origin': '*',
        'access-control-allow-headers': 'Content-Type, Access-Control-Allow-Headers'
    }
}

try:
    if event['httpMethod'] == "POST":
    count = "UNKNOWN"
    key = json.loads(event["body"]).get("key")
    keySearchResp = table.query(
        IndexName='KeyLookup',
        KeyConditionExpression=Key('Key').eq(key)
    )
    item = keySearchResp["Items"][0]
    id = item["Id"]

    keyUpdateResp = table.update_item(
        Key={
            'Id': id,
        },
        UpdateExpression="set #c = #c +:num",
        ExpressionAttributeNames={
            "#c": "Count"
        },
        ExpressionAttributeValues={
            ':num': Decimal(1),
        },
        ReturnValues="UPDATED_NEW"
    )

    count =  keyUpdateResp["Attributes"]["Count"]
    response["body"] = json.dumps({ "COUNT": int(count)})
    response["statusCode"] = 200

    elif event['httpMethod'] == "OPTIONS":
    response["statusCode"] = 200

    else:
    raise Exception('Method not accepted')

except Exception as err:
    print("ERR: ", err)
    response["statusCode"] = 500

return response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Vue JS
&lt;/h3&gt;

&lt;p&gt;My choice of JavaScript framework is &lt;a href="https://vuejs.org/"&gt;Vue JS&lt;/a&gt; and I make quite a lot of use of &lt;a href="https://vuetifyjs.com/en/components/api-explorer/"&gt;Vuetify&lt;/a&gt;, a &lt;a href="https://material.io/design/"&gt;Material Design&lt;/a&gt; styling framework. After installing, it’s as simple as:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vue create frontend
---step through configurations and use defaults
vue add vuetify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Amplify
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.amplify.aws/start"&gt;AWS Amplify&lt;/a&gt; is a JS plugin which consists of some useful functions for serverless Auth and API, as well as some frontend components for Vue JS, enabling user authentication flows.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Vue from 'vue'
import App from './App.vue'
import '@aws-amplify/ui-vue';
import Amplify from 'aws-amplify';
import  { Auth } from 'aws-amplify';

import store from './store'
import router from './router'
import vuetify from './plugins/vuetify';

const ROOT_DOMAIN = 't3chflicks.org';

Amplify.configure({
    Auth: {
        region: '',
        userPoolId: '',
        userPoolWebClientId: '',
        mandatorySignIn: false,
        oauth: {
            scope: [ 'email', 'openid'],
            redirectSignIn: `https://um-app.${ROOT_DOMAIN}/`,
            redirectSignOut: `https://um-app.${ROOT_DOMAIN}/`,
            responseType: 'code'
        }
    },
    API: {
        endpoints: [
            {
                name: "UserAPI",
                endpoint: `https://um-user.${ROOT_DOMAIN}`,
                custom_header: async () =&amp;gt; { 
                    return { Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}` }                  
                }
            },
            {
                name: "TestAPIKey",
                endpoint: `https://um-test.${ROOT_DOMAIN}`,
            }
        ]
    }
});

Vue.prototype.$Amplify = Amplify;
new Vue({
    store,
    router,
    vuetify,
    render: h =&amp;gt; h(App)
}).$mount('#app')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the Vue App, I use Amplify’s Authenticator UI components for user authentication flows.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
&amp;lt;v-container fluid&amp;gt;
    &amp;lt;v-row align="center"&amp;gt;
        &amp;lt;v-spacer/&amp;gt;
        &amp;lt;v-col cols="10"&amp;gt;
            &amp;lt;amplify-authenticator username-alias="email" initial-auth-state="signup" v-if="!signedIn"&amp;gt;
            &amp;lt;amplify-sign-up username-alias="email"  :form-fields.prop="formFields" slot="sign-up"&amp;gt;&amp;lt;/amplify-sign-up&amp;gt;
            &amp;lt;/amplify-authenticator&amp;gt;
            &amp;lt;div v-if="signedIn "&amp;gt;
            &amp;lt;user-info class="my-2" /&amp;gt;
            &amp;lt;amplify-sign-out /&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/v-col&amp;gt;
        &amp;lt;v-spacer/&amp;gt;
    &amp;lt;/v-row&amp;gt;
&amp;lt;/v-container&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is what an authenticated user sees:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MY7LWeLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6720/1%2ANm-uo8rlpu3dK2sCR12ZwQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MY7LWeLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6720/1%2ANm-uo8rlpu3dK2sCR12ZwQ.png" alt="Unauthenticated User Page"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Unauthenticated User Page*&lt;/p&gt;

&lt;p&gt;With the API configured in &lt;code&gt;main.js&lt;/code&gt;, calling an API inside a Vue component is pretty simple.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;methods: {
  async testKey() {
    if(this.testAPIKey !== ""){
      const myInit = { 
          body: {
            key: this.testAPIKey
          },
      };
      const response = await API.post('TestAPIKey', '/', myInit);
      this.response = JSON.stringify(response);
    }
    else {
      alert("enter your key")
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The authorisation header is set for the current logged in user. The code for accessing the User API is just as clean.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;methods: {
    async getUserKey(){
    const resp = await API.get('UserAPI', '/user');
    this.APIKey = resp.KEY;
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/AWS-user-manager-app-quickstart"&gt;full code ☁️ &lt;/a&gt;. . . . . . . . . . . . . . . . . . . . . . . . . . .&lt;a href="https://um-app.t3chflicks.org"&gt;live demo 💽&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;p&gt;Using CloudFormation templates mean it is simple to deploy this infrastructure using the AWS CLI.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws cloudformation deploy --template-name ./00-infra.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Vue JS builds a static site which is uploaded to S3 and deployed using CloudFront.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build;
aws s3 cp ./dist s3://&amp;lt;&amp;lt; your bucket name &amp;gt;&amp;gt; --recursive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V-GIDMLI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ad0DiGLhhjM9U6nh80BX2-A.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V-GIDMLI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ad0DiGLhhjM9U6nh80BX2-A.gif" alt="Video of Use Manager App"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Video of Use Manager App*&lt;/p&gt;

&lt;h3&gt;
  
  
  After Thoughts
&lt;/h3&gt;

&lt;p&gt;This Architecture is the basis for a SaaS, the next step is to link to a payment service such as Stripe to handle subscriptions, which is done in part 2:&lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/pay-me-quickstart-for-creating-a-saas-pt-2-stripe-payments-44bc4bb8388e"&gt;**💸 Pay Me: Quickstart for creating a SaaS pt.2 — Stripe Payments&lt;br&gt;
**C*reating a SaaS solution is fun, but creating a payment system can be a minefield. In this article, I demonstrate how…m*edium.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some popular APIs from &lt;a href="https://rapidapi.com/blog/mos"&gt;Rapid API Top 100&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rapidapi.com/BigLobster/api/url-shortener-service"&gt;URL Shortener Service&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#url-shortener-service"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/eec19846/api/investors-exchange-iex-trading"&gt;Investors Exchange (IEX) Trading&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#investors-exchange-iex-trading"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/jgentes/api/crime-data"&gt;Crime Data&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#crime-data"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/CoolGuruji/api/youtube-to-mp3-download"&gt;Youtube To Mp3 Download&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#youtube-to-mp3-download"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/contextualwebsearch/api/web-search"&gt;Web Search&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#web-search"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/Sv443/api/jokeapi"&gt;JokeAPI&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#jokeapi"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/brianiswu/api/genius"&gt;Genius&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#genius"&gt;Learn More&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rapidapi.com/BraveNewCoin/api/crypto-asset-tickers"&gt;Crypto Asset Tickers&lt;/a&gt; — &lt;a href="https://rapidapi.com/blog/most-popular-api/#crypto-asset-tickers"&gt;Learn More&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/aws-saas-quickstart"&gt;T3chFlicks.org&lt;/a&gt; for more tech-focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/AWS-user-manager-app-quickstart"&gt;The full code for this project can be found here ☁️&lt;/a&gt;
&lt;/h1&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://um-app.t3chflicks.org"&gt;The live demo can be found here 💽&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://chaosgears.com/lets-start-developing-using-vue-and-amplify/"&gt;https://chaosgears.com/lets-start-developing-using-vue-and-amplify/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.amplify.aws/lib/auth/getting-started/q/platform/js"&gt;https://docs.amplify.aws/lib/auth/getting-started/q/platform/js&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification.html"&gt;https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=7dQZLY9-wL0"&gt;https://www.youtube.com/watch?v=7dQZLY9-wL0&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>☁️ 🚀 Virtual Private Cloud on AWS — Quickstart with CloudFormation</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:04:08 +0000</pubDate>
      <link>https://dev.to/t3chflicks/virtual-private-cloud-on-aws-quickstart-with-cloudformation-pog</link>
      <guid>https://dev.to/t3chflicks/virtual-private-cloud-on-aws-quickstart-with-cloudformation-pog</guid>
      <description>&lt;p&gt;A Virtual Private Cloud is the foundation from which to build a new system. In this article, I demonstrate how to create one using CloudFormation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZM-T45Xg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7008/0%2AkmVpoS3oecZICPjc" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZM-T45Xg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7008/0%2AkmVpoS3oecZICPjc" alt="Photo by [Pero Kalimero](https://unsplash.com/@pericakalimerica?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@pericakalimerica?utm_source=medium&amp;amp;utm_medium=referral"&gt;Pero Kalimero&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;*&lt;/p&gt;

&lt;p&gt;Virtual Private Clouds (VPCs) are networks in the cloud. A VPC contains a range of IP addresses that can be subdivided into smaller ranges of IPs known as subnets. Communication across these subnets can be controlled using route tables, security groups and access control lists. Communication to and from the internet is enabled by two types of gateway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NAT Gateways which enable access &lt;strong&gt;to&lt;/strong&gt; the internet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Internet Gateways which enable access &lt;strong&gt;to&lt;/strong&gt; and &lt;strong&gt;from&lt;/strong&gt; the internet.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A subnet is considered private if it uses a NAT instead of an Internet Gateway. Route tables determine where network traffic from your subnet or gateway is directed. A route is required from the subnet to the gateway to enable internet access.&lt;/p&gt;

&lt;p&gt;VPCs are important because they are environments which can be configured to reduce security risks and improve networks speed between components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;For this example, I am going to create a place in Amazon’s Cloud. The VPC comprises three public and three private (hybrid) subnets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kzC5BjMM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A0xJI-GnE10TDeDZvQGvQrQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kzC5BjMM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A0xJI-GnE10TDeDZvQGvQrQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;To deploy this system, I am using the AWS CLI with CloudFormation. It is as simple as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`aws cloudformation create-stack --stack-name service --template-body file://template.yml --capabilities CAPABILITY_NAMED_IAM`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The full template can be found &lt;a href="https://github.com/sk-t3ch/AWS-VPC"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC
&lt;/h3&gt;

&lt;p&gt;To begin, the simplest VPC:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC:
    Type: AWS::EC2::VPC
    Properties:
    CidrBlock: "10.0.0.0/16"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The only property we are required to provide is the &lt;code&gt;CidrBlock&lt;/code&gt;, a range of IP addresses that you declare available for use in the network.&lt;/p&gt;

&lt;p&gt;The next step is to add subnets to the VPC. These occupy sections of the range of IPs in the VPC. You are required to configure &lt;code&gt;CidrBlock&lt;/code&gt; which in this case is &lt;code&gt;10.0.1.0/24&lt;/code&gt; meaning that we can use the addresses in the range &lt;code&gt;10.0.1.0 — 10.0.1.255&lt;/code&gt; .&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subnet:
    Type: AWS::EC2::Subnet
    Properties:
    VpcId: !Ref VPC
    CidrBlock: "10.0.1.0/24"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The component required to enable access from a subnet to the internet is an Internet Gateway. This sits at the edge of the VPC and orchestrates any traffic routed from or to them with the internet.&lt;/p&gt;

&lt;p&gt;The following template configures a public subnet:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PublicSubA:
    Type: AWS::EC2::Subnet
    Properties:
    VpcId: !Ref VPC
    CidrBlock: "10.0.1.0/24"
    AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region

InternetGateway:
    Type: AWS::EC2::InternetGateway

GatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
    VpcId: !Ref VPC
    InternetGatewayId: !Ref InternetGateway

RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
    VpcId: !Ref VPC

RoutePublic:
    Type: AWS::EC2::Route
    Properties:
    DestinationCidrBlock: "0.0.0.0/0"
    GatewayId: !Ref InternetGateway
    RouteTableId: !Ref RouteTablePublic

RouteTableAssocSubnetPublicA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
    RouteTableId: !Ref RouteTablePublic
    SubnetId: !Ref PublicSubA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A private subnet is not directly accessible from the internet. However, it is possible to access the internet from a private subnet using a NAT Gateway. An Elastic IP is required because the NatGateway is not automatically assigned a public IP.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PrivateSubA:
    Type: AWS::EC2::Subnet
    Properties:
    VpcId: !Ref VPC
    CidrBlock: "10.0.4.0/24"
    AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region

ElasticIPNatGatewayPrivateA:
    Type: AWS::EC2::EIP

NatGatewayPrivateA:
    Type: AWS::EC2::NatGateway
    Properties:
    SubnetId: !Ref PrivateSubA
    AllocationId: !Ref ElasticIPNatGatewayPrivateA

RouteTablePrivateA:
    Type: AWS::EC2::RouteTable
    Properties:
    VpcId: !Ref VPC

RoutePrivate:
    Type: AWS::EC2::Route
    Properties:
    DestinationCidrBlock: "0.0.0.0/0"
    InstanceId: !Ref NatGatewayPrivateA
    RouteTableId: !Ref RouteTablePrivateA

RouteTableAssocSubnetPrivateA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
    RouteTableId: !Ref RouteTablePrivateA
    SubnetId: !Ref PrivateSubA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Availability Zones (AZs) are groups of data centres inside a Region that are isolated from the failure of another AZ. Each subnet created in a VPC must be placed in a single AZ and a system can be protected from an AZ failure by utilising multiple AZ architecture — you’re recommended to have three public and three private subnets spanning different AZ zones. A VPC template for this can be found &lt;a href="https://github.com/sk-t3ch/AWS-VPC"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A VPC is pretty useless without any services running inside it. For that, check out the following articles:&lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/cheaper-than-api-gateway-alb-with-lambda-using-cloudformation-b32b126bbddc"&gt;&lt;strong&gt;Cheaper than API Gateway — ALB with Lambda using CloudFormation&lt;/strong&gt;&lt;br&gt;
*An alternative to API gateway is Application Load Balancer. ALB can be connected with Lambda to produce a highly…*medium.com&lt;/a&gt;&lt;br&gt;
&lt;a href="https://medium.com/@t3chflicks/aws-auto-scaling-spot-fleet-cluster-quickstart-with-cloudformation-6504a61f7aab"&gt;&lt;strong&gt;AWS Auto Scaling Spot Fleet Cluster — Quickstart with CloudFormation&lt;/strong&gt;&lt;br&gt;
*Running applications on separate machines quickly becomes an inefficient use of compute and therefore money. In this…*medium.com&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Thanks For Reading
&lt;/h3&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/Projects/aws-quickstart-series"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html"&gt;https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>✨ Motion Sensing Under Bed Lighting</title>
      <dc:creator>t3chflicks</dc:creator>
      <pubDate>Mon, 15 Mar 2021 15:03:17 +0000</pubDate>
      <link>https://dev.to/t3chflicks/motion-sensing-under-bed-lighting-28g0</link>
      <guid>https://dev.to/t3chflicks/motion-sensing-under-bed-lighting-28g0</guid>
      <description>&lt;p&gt;Ever tried to get out of bed quietly at night only to trip over something and wake up the whole house?&lt;/p&gt;

&lt;p&gt;Motion sensing night lights installed discreetly under your bed provide low-level light bright enough to guide you around obstacles, but dim enough so you’re not woken up fully. As well as sensing motion, it’s also possible to programme the lights to a colour of your choice for a fixed (or indefinite) length of time, adding a cool glow and ambiance to any bedroom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tEm7lD7g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2160/1%2Avgc4_0EJY-zjtEIEg-5yTQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tEm7lD7g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2160/1%2Avgc4_0EJY-zjtEIEg-5yTQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With some pretty basic kit, you can install these lights with relative ease in a couple of hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supplies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Power supply (5V 6A) &lt;a href="https://www.amazon.co.uk/gp/product/B01D8FLRQ4/ref=as_li_tl?ie=UTF8&amp;amp;camp=1634&amp;amp;creative=6738&amp;amp;creativeASIN=B01D8FLRQ4&amp;amp;linkCode=as2&amp;amp;tag=t3chflicks07-21&amp;amp;linkId=48504560b11eff32a3dfb611f7436d3f"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Addressable LED strip &lt;a href="https://www.amazon.co.uk/gp/product/B07439RXD3/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks07-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B07439RXD3&amp;amp;linkId=528cbedaefa1222a704f3427926114a1"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Arduino Nano &lt;a href="https://www.amazon.co.uk/gp/product/B0097AU5OU/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks07-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B0097AU5OU&amp;amp;linkId=6b2f81cd5af685300bcb5b3d3401fb95"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wire clips &lt;a href="https://www.amazon.co.uk/gp/product/B001GT4QUE/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B001GT4QUE&amp;amp;linkId=d4a06ec09964ff627ab8634d8f9aa27e"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Motion Sensors &lt;a href="https://www.amazon.co.uk/gp/product/B00R2U8LLG/ref=as_li_tl?ie=UTF8&amp;amp;camp=1634&amp;amp;creative=6738&amp;amp;creativeASIN=B00R2U8LLG&amp;amp;linkCode=as2&amp;amp;tag=t3chflicks-21&amp;amp;linkId=23534259a56c3ec6b27f14dc821edc95"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rocker switch &lt;a href="https://www.amazon.co.uk/gp/product/B07253LWHS/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B07253LWHS&amp;amp;linkId=9a0e1aee17eb95b83bdb58fd2348f682"&gt;Amazon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AC Plug&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wire&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/t3chflicks-night-light-leds"&gt;🔗 Get The Motion Sensing Under Bed Lighting Code On Github 📔&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tll08i3e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2120/1%2A52rGrAVJCBlaDiIOKtb_VA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tll08i3e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2120/1%2A52rGrAVJCBlaDiIOKtb_VA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;


&lt;center&gt;&lt;/center&gt;
&lt;h2&gt;
  
  
  Build 🛠️
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Measure the Bed
&lt;/h3&gt;

&lt;p&gt;Turn the bed onto its side so the base is readily accessible. Find an appropriate location for the control box - we chose the slightly higher area near the head of the bed (see diagram). Measure the perimeter of your bed and its length and width (see diagram). Note down your measurements.&lt;/p&gt;

&lt;p&gt;Determine a location for the three sensors. You want one facing each of the three sides of the bed which are not against the wall. We chose locations which were close to the edge of the bed, but not visible. Measure the distance from the sensor location to the control box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DKSt25B6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4426/1%2Ae1kLjZf6rX7elodBANoG9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DKSt25B6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4426/1%2Ae1kLjZf6rX7elodBANoG9w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cut the Wires and LED Strip
&lt;/h3&gt;

&lt;p&gt;Cut the LED strip to the length of the bed perimeter.&lt;/p&gt;

&lt;p&gt;Next, cut the wires: you will need three for each sensor and three for the LED strip, each leading back to the control box, so 12 in total. Taking three lengths of differently coloured wire, cut to size. We used yellow, green and orange — the accepted convention is red for power, black for ground and another (bold) colour for signal. It doesn’t really matter which colours you use as long as you know which is which.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solder Cables to Motion Sensor
&lt;/h3&gt;

&lt;p&gt;We housed our motion sensors in 3D-printed cases (you can find the file link below). It’s not completely necessary to have these, but they make the sensors tidier and easier to put on the bottom of your bed.&lt;/p&gt;

&lt;p&gt;If you are using the 3D-printed case, start by threading the three differently coloured wires through the lid. The motion sensors have three different pins: ground (GND), power (VCC), and signal (S) (shown below). When holding the sensor as shown in the diagram above (i.e. with the pins on the bottom edge of the module), attach the three different coloured wires their respective pins and solder them into place. Then, cover the wires using heat shrink. Repeat this for the wires cut for each of the three sensors.&lt;/p&gt;

&lt;p&gt;Push the dome of the motion sensor through the hole in the main case. It should click into place. Close the case, leaving the three coloured wires trailing through the rear hole.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U3VFBayy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3000/1%2AwE7gEYQerwBg3wOaTb30Eg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U3VFBayy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3000/1%2AwE7gEYQerwBg3wOaTb30Eg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sfRBDlti--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4376/1%2ApXQ0eWDp31z1EEUEZrUMxA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sfRBDlti--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4376/1%2ApXQ0eWDp31z1EEUEZrUMxA.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--svWilNRq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AwIHtujcpKJsQpLHRgrWlSA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--svWilNRq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AwIHtujcpKJsQpLHRgrWlSA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Wire the LED Strip
&lt;/h3&gt;

&lt;p&gt;The LED strip has the three same connections: power, signal and ground — except the signal pin is an input.&lt;/p&gt;

&lt;p&gt;These LEDs take instructions from the Arduino, each one of them addressable. We can change colour (RGB) and brightness. Solder three coloured wires to the LED strip, these will be used to connect to the Arduino later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iyE3vdWL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2200/1%2ANOkAelNUhJUNwxhOWkFPZg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iyE3vdWL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2200/1%2ANOkAelNUhJUNwxhOWkFPZg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Power Switch
&lt;/h3&gt;

&lt;p&gt;If you’re using the 3D-printed control box, you will need to install the power switch and connect it up to the wires.&lt;/p&gt;

&lt;p&gt;Firstly, ensure there’s nothing on the end of your plug, if there is, cut it off. Thread the wire through the hole in the front of the box and out again through the hole for the switch immediately next to it. Strip the outer covering of the AC wire so that 10cm of the three inner wires (Live, Neutral and Earth) can be seen.&lt;/p&gt;

&lt;p&gt;Then, cut and remove 8cm of the live (red) and neutral (blue) wires and set aside for later. Using the remaining 2cm of the end of the AC plug wire, solder the live (red) and neutral (blue) wires to the switch on the bottom two prongs (as shown in the diagram).&lt;/p&gt;

&lt;p&gt;Next, take the 8cm pieces of live (red) and neutral (blue) wires you cut earlier and solder them onto the two prongs on the top of the switch (as shown in the diagram) — these wires will attach to the power box within the control box. Pulling the wires through first, push the switch into its hole in the box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SL5hfoQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ax0Xha8JUJk8rZVpfpKyZ0Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SL5hfoQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ax0Xha8JUJk8rZVpfpKyZ0Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YI-QlrO0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3100/1%2AHyGFI3-aL8uhHCyYdEx3Ug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YI-QlrO0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3100/1%2AHyGFI3-aL8uhHCyYdEx3Ug.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect Power Supply
&lt;/h3&gt;

&lt;p&gt;Put the power supply in the box so the wire connection points face the switch.&lt;/p&gt;

&lt;p&gt;Connect the live (red) and neutral (blue) wires from the switch to the live and neutral connection point (marked l and n respectively) on the power supply. The connection points on the power supply are screws, ensure these are done up tightly once the wires are in place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--71VYHtZC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2118/1%2A26XjQMSffCpsOsyqTur5WQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--71VYHtZC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2118/1%2A26XjQMSffCpsOsyqTur5WQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect the Arduino
&lt;/h3&gt;

&lt;p&gt;The power supply has output connections for 5V and ground (see diagram). Take the Arduino and cut a power wire (conventionally red, but whatever colour you are using) approximately 8cm long.&lt;/p&gt;

&lt;p&gt;Connect the Arduino to the power supply by screwing one end of the power wire into the ‘5V’ connection point and soldering the other end to the ‘VIn’ on the Arduino.&lt;/p&gt;

&lt;p&gt;Repeat the process with a ground (black, or whatever colour you have chosen) wire, connecting ‘GND’ on the power supply and Arduino.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S0osiFef--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A5WyZ480xjrP_Yptn62YXyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S0osiFef--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A5WyZ480xjrP_Yptn62YXyg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r2pfFvQQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ApJp197gJuEYW9QSMR5Xmvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r2pfFvQQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ApJp197gJuEYW9QSMR5Xmvw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect LED Strip to Power Supply and Arduino
&lt;/h3&gt;

&lt;p&gt;Thread the LED strip’s wires through the remaining empty hole in the box.&lt;/p&gt;

&lt;p&gt;Strip the LED strip’s power and ground wires. Connect the power (red) wire to the power supply’s ‘5V’ connection point (the Arduino is already attached to this) and the ground (black) wire to the power supply ‘GND’ connection point (the Arduino is already attached to this, too).&lt;/p&gt;

&lt;p&gt;Solder the LED strip’s signal wire to the digital pin 9 of the Arduino.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect Motion Sensors to Arduino
&lt;/h3&gt;

&lt;p&gt;Thread the motion sensors’ wires (9 in total) through the hole where the LED strip’s wires are.&lt;/p&gt;

&lt;p&gt;Solder the three power wires to the +5V of the Arduino, the ground wires to the gnd of the Arduino and solder the individual signal wires to Arduino pins 10, 11 and 12.&lt;/p&gt;

&lt;h3&gt;
  
  
  Programme the Arduino
&lt;/h3&gt;

&lt;p&gt;Download the code below called ‘motion_sensing_lights.ino’. Then, using the Arduino software downloadable form &lt;a href="https://www.arduino.cc/en/Main/Software"&gt;here&lt;/a&gt;, upload the code to your Arduino module.&lt;/p&gt;

&lt;p&gt;If you are unsure about how to do this, find out more &lt;a href="https://www.youtube.com/watch?v=R102xfcx75I"&gt;here&lt;/a&gt;. You will also need to download the FastLED library from &lt;a href="https://github.com/FastLED/FastLED"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code is pretty simple: it continuously checks if the motion sensors have outputted a signal and if so, starts a timer and turns on the LED strip to glow up, stay on a minute and then glow down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Put on Bed
&lt;/h3&gt;

&lt;p&gt;Shut the control box — the only things outside it should be the LED strip and the AC plug.&lt;/p&gt;

&lt;p&gt;Stick the box to the bottom of the bed in your chosen location — we did this using strong double sided tape.&lt;/p&gt;

&lt;p&gt;Then, attach the motion sensors to the bottom of the bed using double sided tape. The motion sensors should face outwards along the three sides of the bed which are not along the wall. Next, mount the LED strip around the perimeter of the bed.&lt;/p&gt;

&lt;p&gt;Although the LED strip has a sticky back, this is not strong enough to hold its weight. Therefore, we held it in place using plastic wire clips which we hammered into the bottom of the bed. Plug in and turn on the control box and turn the bed the right way up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ukaNnu2x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2070/1%2AnVtHCyqJ-Cfu24YLsrYoKQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ukaNnu2x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2070/1%2AnVtHCyqJ-Cfu24YLsrYoKQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yRxa8-Hv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AIv_fKKbFlnbRKJRe-nfSsA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yRxa8-Hv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AIv_fKKbFlnbRKJRe-nfSsA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adjust, Test and Admire
&lt;/h3&gt;

&lt;p&gt;Test out your motion-sensing under bed lighting. You can adjust the motion sensor sensitivity by putting a screwdriver through the top hole of the case and twisting the sensitivity resistor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XWFfZIYJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A0u5uadCI-6KaxyAKl2Q2uQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XWFfZIYJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A0u5uadCI-6KaxyAKl2Q2uQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Taking It Further
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X35NvM1b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2162/1%2AfJEJu68QxDP8-aebIQ9gJg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X35NvM1b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2162/1%2AfJEJu68QxDP8-aebIQ9gJg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using an ESP8266 module (&lt;a href="https://www.amazon.co.uk/gp/product/B0791FJB62/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=t3chflicks07-21&amp;amp;creative=6738&amp;amp;linkCode=as2&amp;amp;creativeASIN=B0791FJB62&amp;amp;linkId=13c7d0877d5fe80a032e3da162b9a82e"&gt;Amazon&lt;/a&gt;) instead of the Arduino, it is possible to control the LED strip with your phone or with Alexa by linking it to the open source home automation platform &lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt;. There’s already been a great tutorial on how to do this, and you can find it &lt;a href="https://www.youtube.com/watch?v=9KI36GTgwuQ"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;a href="https://github.com/sk-t3ch/t3chflicks-night-light-leds"&gt;🔗 Get The Motion Sensing Under Bed Lights Code On Github 📔&lt;/a&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Thanks for Reading
&lt;/h2&gt;

&lt;p&gt;I hope you have enjoyed this article. If you like the style, check out &lt;a href="https://t3chflicks.org/"&gt;T3chFlicks.org&lt;/a&gt; for more tech focused educational content (&lt;a href="https://www.youtube.com/channel/UC0eSD-tdiJMI5GQTkMmZ-6w"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/t3chflicks/"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.facebook.com/t3chflicks"&gt;Facebook&lt;/a&gt;, &lt;a href="https://twitter.com/t3chflicks"&gt;Twitter&lt;/a&gt;).&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
