<?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: Flying Nobita</title>
    <description>The latest articles on DEV Community by Flying Nobita (@flyingnobita).</description>
    <link>https://dev.to/flyingnobita</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%2F352718%2Fc9dba3d9-04d8-492a-afc3-d122ae9b0676.png</url>
      <title>DEV Community: Flying Nobita</title>
      <link>https://dev.to/flyingnobita</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/flyingnobita"/>
    <language>en</language>
    <item>
      <title>Building an app for cafés</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Thu, 13 Aug 2020 09:22:51 +0000</pubDate>
      <link>https://dev.to/flyingnobita/building-an-app-for-cafes-3jef</link>
      <guid>https://dev.to/flyingnobita/building-an-app-for-cafes-3jef</guid>
      <description>&lt;p&gt;With the need for a caffe app &lt;a href="https://dev.to%20post_url%202020-01-21-cafe_companion_app%20"&gt;established&lt;/a&gt;, it's time to build one.&lt;/p&gt;

&lt;p&gt;Let's start with how the final app looks like, then I'll go through each page in more detail.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/0s_31iom_As"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Backend
&lt;/h1&gt;

&lt;p&gt;I used &lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt; for the backend. For those unfamiliar, Firebase is Google's application development platform, aka &lt;a href="https://en.wikipedia.org/wiki/Mobile_backend_as_a_service"&gt;Backend-As-A-Service (BAAS)&lt;/a&gt;. It has all the backend services that an app needs under-the-sun. Glancing at their list of services, which includes authentication and analytic, reminds me of the never-ending list of services available in AWS. AWS has its own BAAS, called &lt;a href="https://aws.amazon.com/amplify/"&gt;AWS Amplify&lt;/a&gt;. I'll focus on Firebase in this article.&lt;/p&gt;

&lt;p&gt;The Firebase services I used are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products/auth/"&gt;Authentication&lt;/a&gt; - allows a plethora of sign-in options, ranging from basic email address and phone numbers, to Facebook and Twitter by leveraging OAuth&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products/firestore/"&gt;Cloud Firestore&lt;/a&gt; - NoSQL document database for storing user profiles, menus, and store locations. Any information that I don't want to hard code in my application can be stored here&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products/storage/"&gt;Cloud Storage&lt;/a&gt; - object storage for storing all binary files such as images and videos&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products/functions/"&gt;Cloud Functions&lt;/a&gt; - a &lt;a href="https://dev.to%20post_url%202019-03-18-serverless_deployment_of_fastai_on_azure%20"&gt;FAAS&lt;/a&gt; running a few basic JavaScript functions required by Braintree. I like to keep this app serverless!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products/crashlytics/"&gt;Crashlytics&lt;/a&gt; - automatically generates a report when the app crashes which allows for quick fixes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products/analytics/"&gt;Google Analytics&lt;/a&gt; - collect usage statistics to better understand user behaviour that translates into a better-designed app&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  App Organization
&lt;/h1&gt;

&lt;p&gt;The app is into organized into 5 screens:&lt;/p&gt;

&lt;h2&gt;
  
  
  News (Home)
&lt;/h2&gt;

&lt;p&gt;The News screen shows any announcements that the café wants its customers to see when they first open the app (i.e. app Home screen).&lt;/p&gt;

&lt;p&gt;Each news item is on a card that can be tapped for further details, and buttons at the bottom for quick action e.g. add the drink to cart, share to friends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Menu
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/uPmKemqyJi8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The Menu screen has two tabs, drinks and food. Tabbing on an item will give you options for customization. Customers can add an item to the cart when ready, at which a "Review Order" button appears that brings the customer to the Orders screen for final review before placing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Order
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---MiCZZ78--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_256/https://flyingnobita.com/assets/images/posts/building_cafe_companion__previous_order.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---MiCZZ78--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_256/https://flyingnobita.com/assets/images/posts/building_cafe_companion__previous_order.png" alt="alt Previous Order Screen" width="256" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are three tabs, first is the Order tab where customer can view all items that have been added to cart but not yet ordered.&lt;/p&gt;

&lt;p&gt;The second tab is the Previous tab where customers can view all the orders that they have placed before.&lt;/p&gt;

&lt;p&gt;The last tab is the Favourite tab. This is for viewing all items that have been favourited so that the customer can add these items (along with any customizations that were chosen) to their existing order in one click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stores
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/LnanmD-cLus"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This contains information about all locations for this cafe. Clicking on the phone number and address will take you to the phone and map apps accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profile
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/f4CdQjVJTTg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here the customer can register or log in if it hasn't been done already. Customers can also change the details of the profile here.&lt;/p&gt;

&lt;p&gt;Besides the name, phone number and email, it also shows a QR code that contains the customer ID. This is for the store manager to scan (on a different app that I also created) for identifying the customer to add loyalty points.&lt;/p&gt;

&lt;h1&gt;
  
  
  State Management
&lt;/h1&gt;

&lt;p&gt;State management is always a hot topic in mobile and web development. This is because these apps are heavily UI driven (compare this to a script or a calculation process that works on a database). Thus an important task for the developer is to make sure the UI reflects the app's state correctly. Given how apps can have a large number of UI elements (e.g. game), making sure all elements are displaying the correct state can be a daunting task.&lt;/p&gt;

&lt;p&gt;Since most popular state management libraries have been ported to Dart, developers who have extensive experience in React or React Native can just continue and use whatever libraries they have been using (e.g. &lt;a href="https://mobx.pub/"&gt;MobX&lt;/a&gt;, &lt;a href="https://github.com/brianegan/flutter_redux"&gt;Redux&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  BLoC
&lt;/h2&gt;

&lt;p&gt;Fortunately, I don't have that prior extensive experience or bias. Thus I explored what is the &lt;a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/options"&gt;best solution for Flutter&lt;/a&gt;. After trying different methods such as &lt;a href="https://www.didierboelens.com/2018/06/widget---state---context---inheritedwidget/"&gt;InheritedWidget&lt;/a&gt; and &lt;a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple"&gt;providers&lt;/a&gt;, I've settled on using &lt;a href="https://github.com/felangel/bloc/"&gt;Felix Angelov's implementation&lt;/a&gt; of the &lt;a href="https://medium.com/flutterpub/architecting-your-flutter-project-bd04e144a8f1"&gt;BLoC pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I won't be going into detail on how the library works as the &lt;a href="https://bloclibrary.dev"&gt;official docs&lt;/a&gt; are well written and clear, but I'll try to give a summary here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KpYTkM59--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/building_cafe_companion__bloc_architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KpYTkM59--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/building_cafe_companion__bloc_architecture.png" alt="alt BLoC Architecture" width="768" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The central theme of this pattern is the idea of a bloc component (Business Logic Component). It sits between the UI and the data. The Bloc component handles any events that come from the UI and makes asynchronous requests to the data component, which can be an interface that talks to the database. Once the data is ready, the bloc component will receive an asynchronous response from the data component, process the data in any way needed, and send the data back to the UI in the new state. The UI component is notified of the new state and it will update the display to show the new data accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;To better illustrate this, I'll use the example of when a user taps a button to request information on a drink:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;an event is generated by the UI component when the button is tapped&lt;/li&gt;
&lt;li&gt;bloc component sees the event and sends an asynchronous request to the data component&lt;/li&gt;
&lt;li&gt;data component, an interface that talks to a database, makes a request to the database for the requested drinks information&lt;/li&gt;
&lt;li&gt;bloc component sends a "waiting state" to the UI while the database is fetching the data&lt;/li&gt;
&lt;li&gt;UI component responds to the "waiting state" by updating the display to show a "spinning circular arrow" to indicate to the user that the data requested is being fetched&lt;/li&gt;
&lt;li&gt;data component receives the requested drinks information from the database and sends it back to the bloc component in an asynchronous response&lt;/li&gt;
&lt;li&gt;bloc component sees the response and does some light processing on the information&lt;/li&gt;
&lt;li&gt;bloc component sends a new state that includes the requested drink information to the UI component&lt;/li&gt;
&lt;li&gt;UI component updates the screen with a Dialog box to show the drink information to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Library Usage
&lt;/h1&gt;

&lt;p&gt;The main idea of this library is to enforce separation of concerns within your application. Each component, whether they belong to the UI, bloc or data layer, will be responsible for only one thing. This will encourage you to write smaller components that make reusing and testing your code easier.&lt;/p&gt;

&lt;p&gt;When I find my UI components talk directly to the data components, then I know I haven't implemented this pattern correctly. An easy method that I use to prevent myself from making this mistake is to ensure I only import bloc components in my UI components and data components.&lt;/p&gt;

&lt;p&gt;Furthermore, the bloc is an actively maintained repo and Felix has been responsive in answering the communities' questions. I started using the repo when it was still in version 1. It has gone through a few release cycles since and now it's at version 4. The library gets easier to use and the docs get better with each release. Though keep in mind when upgrading as it's common that each major version upgrade brings some breaking changes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Payment
&lt;/h1&gt;

&lt;p&gt;Two payment gateways stand out in the mobile app space, Stripe and Braintree. Although neither has an official implementation in Flutter, I chose the latter because it has a better third-party &lt;a href="https://pub.dev/packages/braintree_payment"&gt;package&lt;/a&gt; (as of the time of development).&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Writing an app that satisfies an immediate business need is very motivating. Since much of what the app does falls into a common use case, it made a good entry point into the Flutter ecosystem.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>cafes</category>
      <category>bloc</category>
    </item>
    <item>
      <title>Claiming My Free OpenVPN clients</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Mon, 10 Aug 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/claiming-my-free-openvpn-clients-5864</link>
      <guid>https://dev.to/flyingnobita/claiming-my-free-openvpn-clients-5864</guid>
      <description>&lt;p&gt;My NordVPN subscription allows up to 6 devices connected simultaneously. This isn’t enough and has caused some of my devices to drop their VPN connections when new devices come online.&lt;/p&gt;

&lt;p&gt;Luckily there’s a trick I can use on my home router to overcome this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Installation&lt;/li&gt;
&lt;li&gt;Not What I Expected…&lt;/li&gt;
&lt;li&gt;Broly To The Rescue&lt;/li&gt;
&lt;li&gt;
Reinstalling and Configuring OpenVPN

&lt;ol&gt;
&lt;li&gt;Split Tunneling&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion &amp;amp; Caveats&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most VPN services give you gives you several methods of connecting to their VPNs. Besides native apps for Windows, macOS, iOS, etc., most also allow you to connect with OpenVPN.&lt;/p&gt;

&lt;p&gt;You can check your router setup to see if it supports OpenVPN &lt;em&gt;Client&lt;/em&gt; (OpenVPN &lt;em&gt;Server&lt;/em&gt; is for hosting your VPN which doesn’t apply here).&lt;/p&gt;

&lt;p&gt;Although the new D-Link DIR-882 router that I bought &lt;a href="https://dev.to/posts/2020/06/06/home_server_2"&gt;recently&lt;/a&gt; doesn’t natively support OpenVPN clients, DD-WRT does!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dd-wrt.com"&gt;DD-WRT&lt;/a&gt; is a popular custom firmware that supports &lt;a href="https://dd-wrt.com/support/router-database"&gt;a large collection of routers&lt;/a&gt;. It adds a lot of functionality to your router without any additional costs. If you don’t like it, you can always reinstall the stock firmware (useful if you need to return the router to the manufacturer for repair/warranty purposes as they probably don’t accept your router with custom firmware installed).&lt;/p&gt;

&lt;p&gt;One of the many features that DD-WRT supports is OpenVPN Clients with split tunnelling (which I will discuss later on in this post).&lt;/p&gt;

&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;

&lt;p&gt;After seeing a &lt;a href="https://sushantbhosale.com/install-dd-wrt-d-link-dir-882/"&gt;success story&lt;/a&gt; and more people posting on the DD-WRT forum &lt;a href="http://forums.dlink.com/index.php?topic=73607.0"&gt;here&lt;/a&gt; from other people with the same router model as mine, I was confident and followed the instructions. Some caveat while installing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;when booting the router into crash recovery mode, the LEDs blink red in a few seconds and is ready&lt;/li&gt;
&lt;li&gt;plug the ethernet cable from the computer to a LAN port on the router during crash recover mode&lt;/li&gt;
&lt;li&gt;as factory-to-ddwrt.bin and the dlink-dir882-a1-webflash.bin are both on the latest version, after uploading factory-to-ddwrt.bin, there is no need to upgrade the firmware with dlink-dir882-a1-webflash.bin&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Not What I Expected…
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2fT-mm5y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__ddwrt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2fT-mm5y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__ddwrt.png" alt="DD-WRT Screen" width="768" height="736"&gt;&lt;/a&gt;&lt;p&gt;DD-WRT Screen&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;I was pretty excited to see DD-WRT up and running with very little efforts and hiccups. Unfortunately, the smile on my face disappeared just as quickly.&lt;/p&gt;

&lt;p&gt;The first thing I did was to set up the SSID for wireless access. This was a little painful as the setup page would randomly freeze up. Worse still, my devices have trouble finding the new SSIDs and staying connected to it. I tried changing some of the &lt;em&gt;Advanced Settings&lt;/em&gt; like &lt;em&gt;TX Power&lt;/em&gt; and &lt;em&gt;Gain&lt;/em&gt; but it didn’t work and I had no idea what I was doing.&lt;/p&gt;

&lt;p&gt;On the verge of reverting to stock firmware and running the thoughts of buying an additional router that supports OpenVPN in stock, I searched online for others with similar issues.&lt;/p&gt;

&lt;h1&gt;
  
  
  Broly To The Rescue
&lt;/h1&gt;

&lt;p&gt;Although I couldn’t find anyone experiencing the same problems as me, I did find another version of the firmware based on DD-WRT specifically for my router model. This &lt;a href="https://forum.dd-wrt.com/phpBB2/viewtopic.php?p=1095476"&gt;“Broly” version&lt;/a&gt; was last updated on Aug 3, 2020, whereas the “official” DD-WRT version was updated on Aug 6, 2019! The Broly version has a lot of features added in and existing ones updated, with a lot of people giving positive comments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E5xUgoFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__broly_rants.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E5xUgoFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__broly_rants.png" alt="Broly's 1st Post" width="768" height="432"&gt;&lt;/a&gt;&lt;p&gt;Broly’s 1st Post&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ODXa1MqM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__broly_changelog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ODXa1MqM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__broly_changelog.png" alt="Broly's Changelog" width="768" height="394"&gt;&lt;/a&gt;&lt;p&gt;Broly’s Changelog&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Initially, I was a bit baffled from the “rants” on OP’s first post, but seeing the frequent updates in the changelogs was reassuring.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reinstalling and Configuring OpenVPN
&lt;/h1&gt;

&lt;p&gt;Installing the Broly firmware uses the exact method as installing DD-DRT.&lt;/p&gt;

&lt;p&gt;Upon installation, I was greeted with a similar layout as the original DD-WRT but I noticed that the fields and options are more updated. I was no longer experiencing any crashes and my devices were able to connect to the new SSID. Compare to the stock D-Link firmware, there were a lot more tabs filled with new features.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XmEcIjE2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__ddwrt_openvpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XmEcIjE2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/installing_ddwrt__ddwrt_openvpn.png" alt="DD-WRT OpenVPN Client Configuration" width="768" height="794"&gt;&lt;/a&gt;&lt;p&gt;DD-WRT OpenVPN Client Configuration&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;I found the OpenVPN page and configured the settings accordingly. All the devices connected were having its connection routed through the VPN. Even devices that I wasn’t sure would work over the VPN, like my smart TV, worked flawlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split Tunneling
&lt;/h2&gt;

&lt;p&gt;With traffic from all my devices going through the VPN, I can no longer access the services that I exposed from outside my home. I need a way for the machine that hosts my exposed services to skip the VPN.&lt;/p&gt;

&lt;p&gt;This is where split tunnelling comes in. In the OpenVPN Client section of DD-WRT, there is a field called &lt;em&gt;Policy Based Routing&lt;/em&gt;. You can enter a CIDR IP range here and these IPs will have their traffic bypass the VPN. Simple.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion &amp;amp; Caveats
&lt;/h1&gt;

&lt;p&gt;Now that I have reduced the VPN connections of all my home network devices to 1, I have 5 connections available for my mobile devices.&lt;/p&gt;

&lt;p&gt;Overall I was fairly pleased with the overall installation and configuration process. It was fairly easy and straight forward.&lt;/p&gt;

&lt;p&gt;However, there’re still some things that I need to work out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The internet speed according to internet speed test websites (e.g. Speednet.net, fast.com) has dropped dramatically. It went from ~700Mbps to ~20Mbps. That’s &lt;strong&gt;~97% speed reduction&lt;/strong&gt;! Interestingly, I haven’t noticed too much of a speed difference in everyday surfing. And I am still able to stream 4K videos from YouTube.&lt;/li&gt;
&lt;li&gt;I can’t access the USB drive that I plugged into the router. I don’t know if it’s because it was windows formatted or if I need to do further settings in DD-WRT (I’ve already got it to auto-mount)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>homelab</category>
      <category>networking</category>
    </item>
    <item>
      <title>Rebuilding My Home Server for 2020 (Part 2)</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Sat, 06 Jun 2020 19:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/rebuilding-my-home-server-for-2020-part-2-1ei8</link>
      <guid>https://dev.to/flyingnobita/rebuilding-my-home-server-for-2020-part-2-1ei8</guid>
      <description>&lt;p&gt;It wasn’t &lt;a href="///homelab/selfhosted/2020/04/15/home_server.html"&gt;long&lt;/a&gt; ago when I finished building my first iteration of my Home Server. I continued tweaking it for the following weeks. This is a long overdue post on the changes that I’ve made to the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
New Router

&lt;ol&gt;
&lt;li&gt;To 6 Or Not To 6?&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;New Home for Adguard Home&lt;/li&gt;
&lt;li&gt;More (free) Storage Space&lt;/li&gt;
&lt;li&gt;New Apps&lt;/li&gt;
&lt;li&gt;Others&lt;/li&gt;
&lt;li&gt;
Conclusion &amp;amp; Future Ideas

&lt;ol&gt;
&lt;li&gt;1) Sophisticated Wireless Network&lt;/li&gt;
&lt;li&gt;2) Home Automation&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  New Router
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DaYPwhoE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__asus-rt-n16.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DaYPwhoE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__asus-rt-n16.jpg" alt="Asus RT-N16; Old router in room died" width="500" height="500"&gt;&lt;/a&gt;&lt;p&gt;Asus RT-N16; Old router in room died&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;The near decade-old Asus RT-N16 router I had in my room finally died. Although its Wi-Fi stopped working for over a year, I was still able to use it as a switch. About 3 months ago, the router suddenly stopped turning on. It turns out that it is common for this router to have a &lt;a href="https://blog.jseaber.com/2014/09/21/fixing-asus-rt-n16-shuts-down-after-boot/"&gt;blown-out capacitor&lt;/a&gt; and it is a simple case of replacing the capacitor to get it working again. But since I wasn’t sure whether this would fix the Wi-Fi issue, I decided it was time to get a new router.&lt;/p&gt;

&lt;h2&gt;
  
  
  To 6 Or Not To 6?
&lt;/h2&gt;

&lt;p&gt;I am currently using a Linksys EA7500 (AC1900) as my main router. And I have been wanting to get an upgrade for a while, one which can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;allow higher Wi-Fi speed (i.e. &amp;gt;AC1900)&lt;/li&gt;
&lt;li&gt;support band steering (so I don’t need to worry 2 SSIDs, 1 for 2.4Ghz and 1 for 5Ghz)&lt;/li&gt;
&lt;li&gt;MESH! (even better if I can add routers and expand the Mesh as I go, like Asus AiMesh)&lt;/li&gt;
&lt;li&gt;act as an OpenVPN client so it can connect to a paid VPN service&lt;/li&gt;
&lt;li&gt;create separate VLANs for IoT devices (through ethernet and Wi-Fi)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It turns out that now is an awkward time to get a router. Although laptops and phones released this year are Wi-Fi 6 capable (I don’t have any yet), the Wi-Fi 6 routers released so far are &lt;a href="https://dongknows.com/best-wi-fi-6-routers/"&gt;very expensive and they don’t necessarily give you the speed boost&lt;/a&gt; over Wi-Fi 5 routers (especially for me when I don’t have any Wi-Fi 6 clients).&lt;/p&gt;

&lt;p&gt;For example, a popular Wi-Fi 5 router that satisfies my above requirements would be the &lt;a href="https://www.asus.com/Networking/RT-AC86U/"&gt;Asus RT-AC86U&lt;/a&gt;, with the &lt;a href="https://www.asuswrt-merlin.net/about"&gt;Asuswrt-Merlin&lt;/a&gt; custom firmware installed for an all-capable full-blown experience. This more than 2 years old router can be had for about USD150.&lt;/p&gt;

&lt;p&gt;An equivalent Wi-Fi 6 router, RT-AX88U retails for about USD350! I understand that the RT-AX88U is a class higher than the RT-AC86U, but it’s the best comparison at the time being. (at least until the &lt;a href="https://www.asus.com/Networking/RT-AX86U/"&gt;RT-AX86U becomes available!&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;After considering all factors, I found a good deal on a &lt;a href="https://www.dlink.com/en/products/dir-882-exo-ac2600-mu-mimo-wi-fi-router"&gt;D-Link DIR-882&lt;/a&gt; router that supports 1) AC2600 and 2) beam steering, that was selling for USD95. Although it doesn’t support 3) - 5), I figured that it is cheap enough to work as a switch even if I intent to upgrade all the routers in my home with Wi-Fi 6 at a later date. The &lt;a href="https://www.ui.com/unifi/unifi-ap/"&gt;Ubiquiti UniFi line up&lt;/a&gt; when it supports Wi-Fi 6 would be VERY attractive as it supports 3) - 5), plus a whole lot more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r3pJhQYq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__dlink-dir882.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r3pJhQYq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__dlink-dir882.jpg" alt="New router: D-Link DIR-882" width="768" height="432"&gt;&lt;/a&gt;&lt;p&gt;New router: D-Link DIR-882&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;I place the D-Link router in my living room as my main router and moved the Linksys router to my room as a switch and secondary access point.&lt;/p&gt;

&lt;h1&gt;
  
  
  New Home for Adguard Home
&lt;/h1&gt;

&lt;p&gt;Some keen-eyed readers noticed that I said I installed Pi-Hole on “3” different devices in the first part of my home server post, yet I only talked about 2 laptops in the whole article.&lt;/p&gt;

&lt;p&gt;This is because I never mentioned about a failed experiment I did, which was to install Pi-Hole, and subsequently, Adguard Home, on an old Sony Xperia Z1 phone. I will leave the details on how I did this to another post, but I basically installed Ubuntu on the phone and ran the ad-blockers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nzT1Zv4G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__xperia_z1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nzT1Zv4G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__xperia_z1.jpg" alt="Adguard Home running on Sony Xperia Z1" width="281" height="500"&gt;&lt;/a&gt;&lt;p&gt;Adguard Home running on Sony Xperia Z1&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;This worked out nicely for a while as the phone has decent processing power (Snapdragon 800 with 2GB of RAM) and draws very little power (3-7 watt-hours). Adding the fact that this was a piece of electronic junk sitting in my drawer, and you realized you don’t need to buy any Raspberry Pis anymore.&lt;/p&gt;

&lt;p&gt;Unfortunately, after 2 weeks, the already worn-out battery finally died and the phone no longer powered on. I was a bit upset as I couldn’t find a cheap source of new batteries.&lt;/p&gt;

&lt;p&gt;Luckily, when I was going through my drawer, I found a new Raspberry Pi 0 that I totally forgot about. It was given to me by a friend a year ago and when I found out that it doesn’t have Wi-Fi support, I put it away as I didn’t think it would be useful to me.&lt;/p&gt;

&lt;p&gt;Then it suddenly dawned on me that that it supports ethernet with a USB-to-ethernet adapter!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FnQQTtzW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__rp0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FnQQTtzW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__rp0.jpg" alt="Adguard Home running on Raspberry Pi Zero" width="768" height="432"&gt;&lt;/a&gt;&lt;p&gt;Adguard Home running on Raspberry Pi Zero&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Getting Adguard Home running on the Pi 0 was such a breeze, at least when compared with running it on the Xperia Z1. I’ve had some occasional lags where it seems the Pi was somehow stuck with processing DNS requests, causing my internet to stop working. It usually fixes itself when I log in to the Pi 0. Perhaps this problem will go away when I upgrade the Pi to a more powerful one in the future.&lt;/p&gt;

&lt;h1&gt;
  
  
  More (free) Storage Space
&lt;/h1&gt;

&lt;p&gt;As I was going through my tech junk drawer, I also found a bunch of old USB external hard drives. Namely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;250GB x 1&lt;/li&gt;
&lt;li&gt;500GB x 3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;which gives me an extra 1.75GB in total.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--scOCxmjY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__usb_hdd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--scOCxmjY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__usb_hdd.jpg" alt="4 USB HDDs" width="500" height="281"&gt;&lt;/a&gt;&lt;p&gt;4 USB HDDs&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;These drivers are old, slow and use USB 2.0. Nonetheless, they’re good enough for my usage.&lt;/p&gt;

&lt;p&gt;Previously, I connected external HDDs to my laptops, and I find that both laptops would randomly disconnect the USB HDDs. I was suspecting that this might be some kind of power-saving mechanism in Ubuntu, but I couldn’t find the root cause. So instead, I need to find another way of making them accessible on the network.&lt;/p&gt;

&lt;p&gt;Both of my routers have USB ports, and I thought of connecting them directly to the router. Unfortunately, the Linksys router has 2 USB ports, while the D-Link has 1. Adding the 2TB external HDD I have connecting to my IdeaPad, I have 5 USB devices.&lt;/p&gt;

&lt;p&gt;I tried using a USB hub I have lying around but it didn’t work because the USB HDDs need to draw power from the USB ports. I needed a USB hub that supports a power adapter.&lt;/p&gt;

&lt;p&gt;But how much power is needed? Time to buy a USB power adapter!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P8qqDfKa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__usb_power_meter.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P8qqDfKa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__usb_power_meter.jpg" alt="USB power meter" width="768" height="432"&gt;&lt;/a&gt;&lt;p&gt;USB power meter&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;This USD2.5 is a cool little device that gives me the voltage and current usage of attached USB devices.&lt;/p&gt;

&lt;p&gt;Attaching a USB HDD to it gives me:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Voltage (V)&lt;/th&gt;
&lt;th&gt;Current (A)&lt;/th&gt;
&lt;th&gt;Power (W)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle&lt;/td&gt;
&lt;td&gt;5.17&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;td&gt;1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load&lt;/td&gt;
&lt;td&gt;5.17&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A typical &lt;a href="https://www.amazon.com/AmazonBasics-Port-2-5A-power-adapter/dp/B00DQFGH80"&gt;USB hub&lt;/a&gt; supports a power adapter of 5V and 2.5A supply. This should be enough to connect 4 USB HDDs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qtQpZGRc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__usb_accessories.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qtQpZGRc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__usb_accessories.jpg" alt="USB hub &amp;amp; 2 USB ethernet dongles" width="768" height="432"&gt;&lt;/a&gt;&lt;p&gt;USB hub &amp;amp; 2 USB ethernet dongles&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;With the new USB hub I got, I attached all 4 USB HDDs to it and plugged the UBS hub to the Linksys router. Plugging in the 3.5” USB HDD to the other USB port.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m8dZmO_y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__file_explorer_external_hdds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m8dZmO_y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__file_explorer_external_hdds.png" alt="All USB HDDs in File Explorer" width="302" height="207"&gt;&lt;/a&gt;&lt;p&gt;All USB HDDs in File Explorer&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;The randomly named directory is from 3.5” USB HDD that has an extra “recovery partition” created by the OS.&lt;/p&gt;

&lt;h1&gt;
  
  
  New Apps
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qhBf64Qf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__heimdal_tools.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qhBf64Qf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__heimdal_tools.png" alt="Heimdal page for tools" width="768" height="342"&gt;&lt;/a&gt;&lt;p&gt;Heimdal page for tools&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;I’ve added a bunch of new tools to help me manage my 2 laptop servers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oDyxHArn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__heimdal_media.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oDyxHArn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server_2__heimdal_media.png" alt="Heimdal page for books and media" width="768" height="337"&gt;&lt;/a&gt;&lt;p&gt;Heimdal page for books and media&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;And added a bunch of apps to manage my books and media library.&lt;/p&gt;

&lt;h1&gt;
  
  
  Others
&lt;/h1&gt;

&lt;p&gt;With the extra ethernet ports on the new router, I switched the laptops from Wi-Fi to ethernet and saw a noticeable difference in the response time and stability of the laptops. Wire still rules!&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion &amp;amp; Future Ideas
&lt;/h1&gt;

&lt;p&gt;After almost 3 months of building and tweaking, my home server has finally reached a satisfactory state. It is very stable and I’m fairly happy with it. The most often used services are watching movies on Plex and reading books from the Calibre Content Server on my phones. Not exactly life &amp;amp; death missions but don’t underestimate the joy it brings either.&lt;/p&gt;

&lt;p&gt;There are certain aspects of the setup that I think would be interested to explore:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Sophisticated Wireless Network
&lt;/h3&gt;

&lt;p&gt;A setup that would tick all the boxes I mentioned earlier, an appropriate time to do this would be when Wi-Fi 6 truly becomes mainstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Home Automation
&lt;/h3&gt;

&lt;p&gt;I have been reading up this topic and exploring Home Assistant. The biggest hurdle here is to find the appropriate electronics for my home, from doorbells.&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Rebuilding My Home Server for 2020</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Wed, 15 Apr 2020 19:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/rebuilding-my-home-server-for-2020-5beb</link>
      <guid>https://dev.to/flyingnobita/rebuilding-my-home-server-for-2020-5beb</guid>
      <description>&lt;p&gt;These days, everyone has been getting a tremendous amount of home time because of the coronavirus. I am no different. While others are complaining about home prison, I think this is the perfect time to reprioritize some of the house choirs that I’ve been putting off for a long time.&lt;/p&gt;

&lt;p&gt;One of them is to set up my home server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Built From A Decade Ago&lt;/li&gt;
&lt;li&gt;Wait… a laptop as a &lt;em&gt;server&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Backup&lt;/li&gt;
&lt;li&gt;Power&lt;/li&gt;
&lt;li&gt;
App Organization

&lt;ol&gt;
&lt;li&gt;Apps Behind VPN&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Media&lt;/li&gt;
&lt;li&gt;Administration&lt;/li&gt;
&lt;li&gt;Apps With Direct Internet Access&lt;/li&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
External Access

&lt;ol&gt;
&lt;li&gt;Reverse Proxy&lt;/li&gt;
&lt;li&gt;Wireguard&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
Things I Learned

&lt;ol&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;Network-Wide Ad-Blocker Are Awesome&lt;/li&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;li&gt;Deeper Understanding of Ubuntu&lt;/li&gt;
&lt;li&gt;1 - Screen Sharing with VNC&lt;/li&gt;
&lt;li&gt;2 - Power Management in Linux&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;li&gt;
Future Ideas

&lt;ol&gt;
&lt;li&gt;Proxmox VE&lt;/li&gt;
&lt;li&gt;File System With Spanning &amp;amp; Recovery&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Built From A Decade Ago
&lt;/h1&gt;

&lt;p&gt;I had an HTPC in the past that uses an HDMI cable to directly connect to the TV. I built this near 2010 and installed XBMC (now &lt;a href="https://kodi.tv/"&gt;KODI&lt;/a&gt;), one of the first apps for providing a beautiful &lt;a href="https://en.wikipedia.org/wiki/10-foot_user_interface"&gt;10-foot interface&lt;/a&gt; to access my movies and TV shows. After years ago, I got a new Samsung TV. The new TV didn’t play nice with the HTPC and the PC was having trouble detecting the TV at boot. It was also experiencing random hangs, showing it’s age. Besides, this decade-old HTPC, albeit beautiful, was also too big to fit in my new TV cabinet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iUQlMTkf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__silverstone.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iUQlMTkf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__silverstone.jpg" alt="Beautiful SilverStone GD04s - a bit big for today's HTPC standards" width="768" height="439"&gt;&lt;/a&gt;&lt;p&gt;Beautiful SilverStone GD04s - a bit big for today’s HTPC standards&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Instead, I ended up installing Plex Media Server on my desktop PC and streamed to the Plex app on my Samsung TV. While this worked, my PC (Dell T1700) was using too much power to leave on 24/7, not exactly achieving “entertainment-on-demand”.&lt;/p&gt;

&lt;p&gt;After pondering on several purchasing options that ranged from SFF PCs to the Raspberry Pi, I decided to repurpose the old laptops that I have lying around.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It always starts with Plex.”&lt;/p&gt;

reddit.com/r/selfhosted/
&lt;/blockquote&gt;

&lt;p&gt;After installing a bunch of apps on top of &lt;a href="https://elementary.io/"&gt;Elementary OS 5.1 Hera&lt;/a&gt; on an old IBM ThinkPad X1 Carbon Gen 1, it started to hang. I think I was probably throwing too much at the 4GB of RAM and the i5-3337U that it had.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Br1wn6ok--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__thinkpad.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Br1wn6ok--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__thinkpad.jpg" alt="ThinkPad with Elementary OS 5.1 Hera and a USB HDD" width="768" height="960"&gt;&lt;/a&gt;&lt;p&gt;ThinkPad with Elementary OS 5.1 Hera and a USB HDD&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;So I moved everything installed already to the other old laptop, a Lenovo IdeaPad U330 Touch equipped with 8GB of ram and an i5-4200U, and &lt;a href="https://ubuntubudgie.org/"&gt;Ubuntu Budgie 19.10&lt;/a&gt;. This was super easy and quick thanks to docker (more about it below). Although the system was running smoother, I was getting port clashes between different apps. In the end, I decided to use both of my laptops.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3KdGDZcQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__ideapad.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3KdGDZcQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__ideapad.jpg" alt="IdeaPad with Ubuntu Budgie 19.10&amp;lt;br&amp;gt;
" width="768" height="960"&gt;&lt;/a&gt;&lt;p&gt;IdeaPad with Ubuntu Budgie 19.10 and a USB HDD&lt;/p&gt;
&lt;br&gt;
&lt;/p&gt;
&lt;h1&gt;
  
  
  Wait… a laptop as a &lt;em&gt;server&lt;/em&gt;?
&lt;/h1&gt;

&lt;p&gt;Some of you might be wondering why I am using a &lt;em&gt;laptop&lt;/em&gt; as a server.&lt;/p&gt;

&lt;p&gt;I know this might sound counterintuitive at first, but I think for many people’s home servers, old laptops makes a great choice because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they are built to be small and quiet&lt;/li&gt;
&lt;li&gt;they are built to be very energy efficient and still more powerful than a Raspberry Pi&lt;/li&gt;
&lt;li&gt;they have a built-in monitor which makes it easy to set up and work with compared with a headless machine&lt;/li&gt;
&lt;li&gt;they have a built-in battery and WIFI which makes them portable so they can be moved around the house if needed&lt;/li&gt;
&lt;li&gt;they are lying around and are also free&lt;/li&gt;
&lt;li&gt;it is good for the environment to repurpose hardware and generate less &lt;a href="https://www.theguardian.com/global-development-professionals-network/gallery/2016/oct/18/the-e-waste-reduce-waste-old-technology-mountains-in-pictures"&gt;e-waste&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am a big fan of reusing old hardware and for this project, I won’t be buying anything new.&lt;/p&gt;
&lt;h1&gt;
  
  
  Storage
&lt;/h1&gt;

&lt;p&gt;This server’s current main purpose is to serve media 24/7. My media collection is small at around 2TB, limited by the drives available.&lt;/p&gt;

&lt;p&gt;There are 2 drives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;2TB 3.5” SATA drive inside a USB external case connected to the IdeaPad&lt;/li&gt;
&lt;li&gt;60GB 3.5” PATA drive (it was lying around!) inside a USB external case connected to the ThinkPad&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both drives are packed to the brim and I’ll be adding more drives to the system soon.&lt;/p&gt;
&lt;h1&gt;
  
  
  Backup
&lt;/h1&gt;

&lt;p&gt;I put all the application states and configs on the external drives. This way I can move the external drive to any computer and run the apps there, thereby making the apps and the machine that runs them completely decoupled. The application states and configs are backed up regularly to the laptop’s hard drive, so I can refer to them in case something happens to the drives.&lt;/p&gt;

&lt;p&gt;Besides, I check in the Docker Compose file to GitHub to better track the changes that I make.&lt;/p&gt;

&lt;p&gt;The more important media files are synced to the cloud. Since I wouldn’t be too sad if the data on these drives suddenly disappeared, the current setup is sufficient.&lt;/p&gt;
&lt;h1&gt;
  
  
  Power
&lt;/h1&gt;

&lt;p&gt;Having a power-efficient system is one of the most important aspects of building this server. In considering the laptops against a Raspberry Pi, I want to ensure the laptops are energy efficient enough, given the computing power they provide, to justify their increased power usage. Since I currently have very minimal application requirements, a Raspberry Pi 4 is probably sufficient to meet all my needs.&lt;/p&gt;

&lt;p&gt;From the pictures of the laptops, you can see that I measured the wattage the laptops are using with a power meter. I made some rough estimation and this is what I found:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Idle (Watts)&lt;/th&gt;
&lt;th&gt;Load (Watts)&lt;/th&gt;
&lt;th&gt;Annual Costs (US$) [4][5]&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IdeaPad + 1 SATA USB HDD [1]&lt;/td&gt;
&lt;td&gt;14 - 25&lt;/td&gt;
&lt;td&gt;28 - 38&lt;/td&gt;
&lt;td&gt;15-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThinkPad + 1 PATA USB HDD [2]&lt;/td&gt;
&lt;td&gt;18 - 28&lt;/td&gt;
&lt;td&gt;33 - 40&lt;/td&gt;
&lt;td&gt;18-28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raspberry Pi 4b [3] + 1 SATA USB HDD&lt;/td&gt;
&lt;td&gt;8 - 13&lt;/td&gt;
&lt;td&gt;8 - 13&lt;/td&gt;
&lt;td&gt;8-8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop PC&lt;/td&gt;
&lt;td&gt;65 - 80&lt;/td&gt;
&lt;td&gt;80 - 120&lt;/td&gt;
&lt;td&gt;56-69&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;[1] SATA USB HDD uses about 5-10V at idle and under load&lt;/p&gt;

&lt;p&gt;[2] PATA USB HDD uses about 8-14V at idle and under load&lt;/p&gt;

&lt;p&gt;[3]&lt;a href="https://raspi.tv/2019/how-much-power-does-the-pi4b-use-power-measurements"&gt;source&lt;/a&gt; - I used the numbers from “Idling” and “Watching a 1080p video on the µSD card with omxplayer”&lt;/p&gt;

&lt;p&gt;[4] Lower end is the average of Idle, upper end is the average of Load&lt;/p&gt;

&lt;p&gt;[5] Cost of electricity at 24 hours a day is around US$0.77 per watt per year&lt;/p&gt;

&lt;p&gt;A large inefficiency in the setup is due to the old USB drives, and they will be the next items for me to replace. The laptops themselves are quite power-efficient, especially when their screens are off. Another advantage with the laptops is they have a lot of room to accommodate more apps down the road than the Raspberry Pi.&lt;/p&gt;
&lt;h1&gt;
  
  
  App Organization
&lt;/h1&gt;

&lt;p&gt;Here’s what I installed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EMwy0ZNr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__heimdall.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EMwy0ZNr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__heimdall.jpg" alt="Heimdall - links to all the apps installed" width="768" height="495"&gt;&lt;/a&gt;&lt;p&gt;Heimdall - links to all the apps installed&lt;/p&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;The apps are separated into two groups, ones that need direct access to the internet, and ones that are behind NordVPN, my preferred method of accessing the internet.&lt;/p&gt;
&lt;h2&gt;
  
  
  Apps Behind VPN
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Dashboard
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://heimdall.site/"&gt;Heimdall&lt;/a&gt;&lt;/strong&gt; - the dashboard app that you see which makes it easy to navigate to all the apps without memorizing all their different port number. Yes it’s just a glorified bookmark manager, but it’s a cool looking one nonetheless&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Media
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.plex.tv/"&gt;Plex&lt;/a&gt;&lt;/strong&gt; - media server that can stream and transcode movies, TV shows &amp;amp; music to any devices that Plex has a client app, including Windows, Macs, Android, iOS, even TVs. It can also play on web browsers and support DLNA. I don’t access my media outside of the home but I can easily give Plex direct internet access if I do&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://airsonic.github.io/"&gt;Airsonic&lt;/a&gt;&lt;/strong&gt; - a server that streams music to any subsonic compatible clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://booksonic.org/"&gt;Booksonic&lt;/a&gt;&lt;/strong&gt; - a fork of Airsonic that specifically targets audiobooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://deluge-torrent.org/"&gt;Deluge&lt;/a&gt;&lt;/strong&gt; - torrent downloader&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Administration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.portainer.io/"&gt;Portainer&lt;/a&gt;&lt;/strong&gt; - a GUI for managing docker containers. Although I mainly use CLI (mainly docker-compose), it’s nice to click on a GUI now and then&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://cockpit-project.org/"&gt;Cockpit&lt;/a&gt;&lt;/strong&gt; - a dashboard for monitoring the statistics and status of the laptops. It uses the OS’s logins and even has a terminal. Zero configuration required. After using other resource monitoring apps such as Stacer and htop, I can’t recommend Cockpit high enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://guacamole.apache.org/"&gt;Guacamole&lt;/a&gt;&lt;/strong&gt; - a remote desktop gateway that supports RDP (for Windows) and VNC (Linux)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.duplicati.com/"&gt;Duplicati&lt;/a&gt;&lt;/strong&gt; - a &lt;a href="https://storagepipe.com/blog/block-level-vs-file-level-incremental-backup/"&gt;block-level&lt;/a&gt; incremental backup application that is used for backing up the application configs and states from the external USB HDD to the laptop SSD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://rclone.org/"&gt;Rclone&lt;/a&gt;&lt;/strong&gt; - a file synchronization application that supports 50+ cloud storage providers. This helps sync some of my media with the cloud, acting as a “pseudo-backup” and also allows me to access my media in case I can’t access my server&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Apps With Direct Internet Access
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Networking
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.duckdns.org/"&gt;DuckDNS&lt;/a&gt;&lt;/strong&gt; - an app that updates the ever-changing IP address that ISP gives us to the dynamic DNS registry, Duck DNS, which gives us a free &lt;em&gt;XXXXX.duckdns.org&lt;/em&gt; domain name that points it back to our IP. This simple app has no GUI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://adguard.com/en/adguard-home/overview.html"&gt;AdGuard Home&lt;/a&gt;&lt;/strong&gt; - a network-wide ad-blocker that creates a &lt;a href="https://en.wikipedia.org/wiki/DNS_sinkhole"&gt;DNS sinkhole&lt;/a&gt; for blocking ads on devices connected to your LAN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.wireguard.com/"&gt;WireGuard&lt;/a&gt;&lt;/strong&gt; - a VPN server for accessing my network away from home (more on this below). I haven’t found a nice web GUI so it’s not on Heimdall at the moment&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  External Access
&lt;/h1&gt;

&lt;p&gt;I used to just do simple port forwards on my router for apps that I need access to when I’m on the road. That includes port 80 for a test webserver, 22 for SSH, 3389 for RDP, and the list goes on.&lt;/p&gt;

&lt;p&gt;But one day I started to log the login attempts at these ports, and I saw first hand how frequently they were being attacked. I suggest you try this, or set up a honeypot, and see for yourself. Sit back and have a towel ready to wipe your sweat.&lt;/p&gt;

&lt;p&gt;So for this new server, I want it to be a bit more secure. I tried two methods, reverse proxy and VPN.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.imperva.com/learn/performance/reverse-proxy/"&gt;reverse proxy&lt;/a&gt; is the first thing I tried. Perhaps due to my docker setup is a little bit more specific (I use docker-compose and have 2 docker projects in separate networks), I spent a long time getting Traefik to work with each app (their v2 is lacking in terms of documentation, so much for touting a zero-config setup). Then I started to wonder if there’s a better way.&lt;/p&gt;

&lt;p&gt;In addition to the webserver, an SSL certificate is also needed for the &lt;em&gt;”s”&lt;/em&gt; in https to work. Let’s Encrypt has made free and super easy. Using this &lt;a href="https://hub.docker.com/r/linuxserver/letsencrypt"&gt;docker image&lt;/a&gt; will also give you Nginx for free.&lt;/p&gt;

&lt;p&gt;But rather than going through the whole setup again, that’s when I decide to do away with the open https port on my router. I can save this port for running a website. Besides, a reverse proxy isn’t originally meant for secured external access anyways.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wireguard
&lt;/h2&gt;

&lt;p&gt;Wireguard has been getting a lot of attention recently and it is looking likely to &lt;a href="https://arstechnica.com/gadgets/2020/03/wireguard-vpn-makes-it-to-1-0-0-and-into-the-next-linux-kernel/"&gt;go mainstream&lt;/a&gt; in the coming years. It is an alternative to open-source VPN solutions such as &lt;a href="https://hub.docker.com/r/linuxserver/openvpn-as/"&gt;OpenVPN&lt;/a&gt; and &lt;a href="https://www.zerotier.com/"&gt;ZeroTier&lt;/a&gt;. Its main selling point is that it uses updated cryptography with a simple design and implementation of only 4000 lines of code, reducing the attack surface.&lt;/p&gt;

&lt;p&gt;Setting it up was unimaginably simple. Install the package, generate the key pairs, setup the IP addresses, repeat for each client, and you’re done. Compare to getting all the paths right for a reverse proxy, this is incredible (albeit the two aren’t equal, but for the current purpose of providing external access, it is).&lt;/p&gt;
&lt;h1&gt;
  
  
  Things I Learned
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;

&lt;p&gt;Docker has made putting this server together so much cleaner. With &lt;a href="https://docs.docker.com/compose/"&gt;Docker Compose&lt;/a&gt;, spinning up a new service is simply editing the docker-compose config YAML file, and calling &lt;code&gt;docker-compose up -d&lt;/code&gt;. All the configs and application states are saved in volumes that are mapped to a local folder. To move the app to another machine, you just need to move the local folders and call &lt;code&gt;docker-compose up -d&lt;/code&gt; on the new machine. Did I mention many docker images have support for multiple CPU architectures? So not only are these docker images are cross-OS (works on Windows, Linux, Mac), but will also work on ARM devices like Raspberry Pi, as well as your PC.&lt;/p&gt;

&lt;p&gt;While building this server, I always choose to install the docker version of an app first. &lt;a href="https://fleet.linuxserver.io/"&gt;LinuxServer.io&lt;/a&gt; has a repository of docker images on Docker Hub that they actively maintain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Network-Wide Ad-Blocker Are Awesome
&lt;/h2&gt;

&lt;p&gt;I have been aware of &lt;a href="https://pi-hole.net/"&gt;Pi-Hole&lt;/a&gt; for a while and one motivation for setting up this server is to try running it. Initially, I installed it on the laptop with NordVPN and realized Pi-Hole doesn’t work well when the VPN is enabled.&lt;/p&gt;

&lt;p&gt;I then installed it on the other laptop with direct internet access and although it worked, I couldn’t cleanly change the port of the web interface which defaults to 80. This means that the Pi-hole interface is exposed when I configure it behind the reverse proxy. Not exactly a &lt;a href="https://www.reddit.com/r/pihole/comments/71vaf3/stop_exposing_your_pihole_to_the_internet/"&gt;safe&lt;/a&gt; practice. This was one of the main reasons why I picked a VPN solution instead.&lt;/p&gt;

&lt;p&gt;Upon finally getting the ad-blocker installed, the biggest difference I noticed was on my phone and my TV. Some ads that normally show up in apps have disappeared. I can have AdGuard Home block more ads by adding more domains to the filter list. Adguard Home also has a nice looking interface that shows me the DNS queries made by the device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fRSrqeVr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__adguard_home.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fRSrqeVr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/home_server__adguard_home.png" alt="Adguard Home" width="768" height="419"&gt;&lt;/a&gt;&lt;p&gt;Adguard Home&lt;/p&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Besides ads, the other concern I had was to stop Samsung from &lt;a href="https://www.nytimes.com/2018/07/05/business/media/tv-viewer-tracking.html"&gt;tracking my watching habit&lt;/a&gt;. And you can see from the screenshot that this has worked nicely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Networking
&lt;/h2&gt;

&lt;p&gt;While I have no problems setting up a simple home WIFI network with routers and access points, this is the first time I set up a reverse proxy, VPN server, DNS proxy (network-wide ad blocker) and Samba file sharing. As a developer, I always find networking to be a bit too finicky, where things wouldn’t work for no apparent reasons (at least with my networking debugging skills). I had a similar experience when working on this project, but I think I’ve picked up some valuable debugging skills to solve networking problems a bit quicker (though I still standby rebooting a device when not able to connect as the first option 😄).&lt;/p&gt;
&lt;h2&gt;
  
  
  Deeper Understanding of Ubuntu
&lt;/h2&gt;

&lt;p&gt;I brought this mess to myself. Rather than installing the same OS, I had the idea of trying different flavours of Ubuntu a while ago. Although they’re both Ubuntu-based and works similarly on day-to-day usage, the subtle differences still tripped me up when getting into the nitty-gritty.&lt;/p&gt;

&lt;p&gt;Also, some of the most time-consuming things on this project were not due to the apps installations and configurations, but to the laptops themselves.&lt;/p&gt;
&lt;h3&gt;
  
  
  1 - Screen Sharing with VNC
&lt;/h3&gt;

&lt;p&gt;I want to reboot and login to the laptop completely from remote, and to share the GUI on the laptop rather than create a new GUI session when connecting through VNC. It turns out this was not easy with some of the VNC servers that I used before (namely TightVNC and TigerVNC). What worked at the end was the Ubuntu built-in &lt;a href="https://help.ubuntu.com/community/VNC/Servers#x11vnc"&gt;x11vnc&lt;/a&gt; that allows me to boot straight to the greeter to login and share the screen.&lt;/p&gt;
&lt;h3&gt;
  
  
  2 - Power Management in Linux
&lt;/h3&gt;

&lt;p&gt;While power management was easy to configure for both laptops in Windows mode, it wasn’t so smooth on Linux. I needed my laptops to never go into Suspend Mode and to keep on running even when the lid is closed. Unfortunately having these settings set in the GUI didn’t work. What I found at the end was to use this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent the laptop from suspending when the lid is closed, you just need to add this line to &lt;strong&gt;&lt;code&gt;/etc/UPower/UPower.conf&lt;/code&gt;&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IgnoreLid=true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I guess these problems are a fair price to pay for building the server on free hardware.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Overall this project took 1-2 weeks of a good few solid hours every day, way more time than it should have. But I was tinkering and trying new things. There are occasions that I have completely wasted my time on. For example, I installed Pi-Hole on 3 different devices and ended up using AdGuard Home instead. Thank goodness AdGuard Home had a much simpler setup procedure as I was getting so tired of tinkering with adblockers at the end.&lt;/p&gt;

&lt;p&gt;A big chunk of the time was spent pouring through articles, reviews and forums on finding the most suitable setup. It was also probably one of the most fun I’ve had since the lockdown as building the server piece by piece and seeing it all working together at the end is satisfying.&lt;/p&gt;

&lt;h1&gt;
  
  
  Future Ideas
&lt;/h1&gt;

&lt;p&gt;There are still a few ideas that are on my mind which I want to try:&lt;/p&gt;

&lt;h3&gt;
  
  
  Proxmox VE
&lt;/h3&gt;

&lt;p&gt;I would like to reinstall everything under &lt;a href="https://pve.proxmox.com/wiki/Main_Page"&gt;Proxmox VE&lt;/a&gt;, a virtual machine hypervisor, so that I can put some of the apps in their own VMs. This will help eliminate some of the problems like port clashes and also help me better manage the machine’s resources. Though my IdeaPad only has 8GB of RAM so I’m not sure how many VMs it can handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  File System With Spanning &amp;amp; Recovery
&lt;/h3&gt;

&lt;p&gt;Instead of the current &lt;a href="https://en.wikipedia.org/wiki/Non-RAID_drive_architectures#JBOD"&gt;JBOD&lt;/a&gt; setup, I intend to add pooling and parity recovery to recover from disk failures. Right now I’m leaning towards &lt;a href="https://github.com/trapexit/mergerfs/"&gt;MergerFS&lt;/a&gt; + &lt;a href="https://github.com/amadvance/snapraid/"&gt;SnapRAID&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Coronavirus Suspended My First Google Play App</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Mon, 02 Mar 2020 19:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/coronavirus-suspended-my-first-google-play-app-nj2</link>
      <guid>https://dev.to/flyingnobita/coronavirus-suspended-my-first-google-play-app-nj2</guid>
      <description>&lt;p&gt;I recently submitted a simple app to Google Play, hoping to get a taste of the app submission process. I was expecting the process to be a bit painful, and I ended up getting suspended.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Update (Mar 6, 2020): Added a section at the end to talk about a recent CNBC article that discussed Apple and Google are removing coronavirus apps from their app store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Data&lt;/li&gt;
&lt;li&gt;Background Tasks &amp;amp; Notifications&lt;/li&gt;
&lt;li&gt;UI&lt;/li&gt;
&lt;li&gt;Widgets&lt;/li&gt;
&lt;li&gt;Submission to Google Play&lt;/li&gt;
&lt;li&gt;Coronavirus wiped from Google Play&lt;/li&gt;
&lt;li&gt;Appeals&lt;/li&gt;
&lt;li&gt;Some Clarity At Last&lt;/li&gt;
&lt;li&gt;Lessons&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have been following the recent &lt;a href="https://www.who.int/emergencies/diseases/novel-coronavirus-2019"&gt;coronavirus outbreak&lt;/a&gt; and find the &lt;a href="https://www.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6"&gt;dashboard&lt;/a&gt; created by &lt;a href="https://systems.jhu.edu/research/public-health/ncov/"&gt;Johns Hopkins University CSEE&lt;/a&gt; to be very helpful.&lt;/p&gt;

&lt;p&gt;I look at the dashboard often to check the latest numbers and thought it would be convenient to have an app that notifies me of updates. This also sounds like the perfect candidate to be my first submission to different app stores. So away I went.&lt;/p&gt;

&lt;p&gt;This is the set of features that I want to implement in my app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;periodic checks for changes in data&lt;/li&gt;
&lt;li&gt;display notifications&lt;/li&gt;
&lt;li&gt;display widgets&lt;/li&gt;
&lt;li&gt;allow the user to filter by country&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;JHU initially released all their data on a Google Sheet and has since migrated to a &lt;a href="https://github.com/CSSEGISandData/COVID-19"&gt;Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q7l-9AIS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__csse_github.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q7l-9AIS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__csse_github.webp" alt="JHU CSSE Github CSVs" width="768" height="369"&gt;&lt;/a&gt;&lt;p&gt;The CSVs themselves are grouped by day&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NP-DZnrw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__csse_github_csv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NP-DZnrw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__csse_github_csv.webp" alt="JHU CSSE CSVs format" width="768" height="300"&gt;&lt;/a&gt;&lt;p&gt;In each CSV, there are 6 columns clearly labelled making the parsing of this CSV a breeze.&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;With the CSVs from Github, getting the app to load the data was straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Tasks &amp;amp; Notifications
&lt;/h2&gt;

&lt;p&gt;A trickier aspect of this app is it needs to be able to periodically pull Github for data even if the app is closed and to send a notification to inform the user of any changes. I found two packages, &lt;a href="https://pub.dev/packages/flutter_local_notifications"&gt;flutter_local_notifications&lt;/a&gt;, which help send notifications, and &lt;a href="https://pub.dev/packages/workmanager"&gt;workmanager&lt;/a&gt;, which help implement background tasks. These packages significantly reduced my development time as Flutter doesn’t support these functions on its own and requires the user to write native code through Flutter’s &lt;a href="https://flutter.dev/docs/development/platform-integration/platform-channels"&gt;platform channels&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JvSYSEVy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__notifications.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JvSYSEVy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__notifications.webp" alt="Notifications showing the global infected numbers upon update" width="338" height="135"&gt;&lt;/a&gt;&lt;p&gt;Notifications showing the global infected numbers upon update&lt;/p&gt;

&lt;/p&gt;

&lt;h2&gt;
  
  
  UI
&lt;/h2&gt;

&lt;p&gt;I prefer a UI that is simple and to the point. It should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a dropdown for the user to select the country/region (can also add a city selection at a later stage as they are also provided by the CSV)&lt;/li&gt;
&lt;li&gt;prominent display of the numbers (confirmed, deaths, recovered)&lt;/li&gt;
&lt;li&gt;a switch for the user to turn the notifications on, which will also enable periodic background refresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--64jXlZm1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__ui.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--64jXlZm1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__ui.webp" alt="App UI" width="338" height="704"&gt;&lt;/a&gt;&lt;p&gt;App UI&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;I have also added an “About Dialog” that tells the users the information is from JHU CSSE and a way for them to give me feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GBtmw6da--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__about_dialog.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GBtmw6da--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__about_dialog.webp" alt="About Dialog with a User Feedback Form" width="277" height="369"&gt;&lt;/a&gt;&lt;p&gt;About Dialog with a User Feedback Form&lt;/p&gt;

&lt;/p&gt;

&lt;h2&gt;
  
  
  Widgets
&lt;/h2&gt;

&lt;p&gt;It turns out that Flutter supports neither &lt;a href="https://developer.android.com/guide/topics/appwidgets"&gt;Android Widgets&lt;/a&gt; nor &lt;a href="https://support.apple.com/en-us/HT207122"&gt;iOS Today View Widgets&lt;/a&gt;. As this needs to be implemented natively through the platform channels, I’ll implement it in a later version update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submission to Google Play
&lt;/h2&gt;

&lt;p&gt;As my main development machine is on Windows, so naturally I will submit to Google Play first. This involves setting up my developer account and paying a USD25 registration fee. After putting in more information about my app entry, I submitted my app for the Internal Test, the first of the four tests before the actual public release.&lt;/p&gt;

&lt;p&gt;After 3 days of waiting, I was shocked upon seeing the app got suspended:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--veSXuNzI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__google_play_suspension.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--veSXuNzI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__google_play_suspension.webp" alt="Google's email saying my app was suspended by Google Play" width="768" height="696"&gt;&lt;/a&gt;&lt;p&gt;Google’s email saying my app was suspended by Google Play&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;My app got suspended because it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“lack reasonable sensitivity towards or capitalize on a natural disaster, atrocity, conflict, death, or other tragic event”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All my app does is it shows the latest infected numbers from a widely accepted source and give the option to the user for receiving notifications when these numbers change. It does not misrepresent or interpret the numbers in any way. Nor does it contain any ads and I’m not “taking advantage” of the situation. I simply want to receive notifications on the latest numbers.&lt;/p&gt;

&lt;p&gt;It seems that while this simple app violates their policy, a news app (e.g. CNN, BBC) that reports the same numbers, while giving their interpretation and getting ad money is NOT a violation of the policy.&lt;/p&gt;

&lt;p&gt;I went online and saw horror &lt;a href="https://news.ycombinator.com/item?id=8838050"&gt;story&lt;/a&gt; after &lt;a href="https://andrewrezk.com/how-i-restored-my-app-on-google-play-after-suspension/"&gt;story&lt;/a&gt; on unjust suspension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coronavirus wiped from Google Play
&lt;/h2&gt;

&lt;p&gt;While I was feeling a bit frustrated, something interesting popped up. Before developing the app, I did a bit of research on existing apps that were also centred on the coronavirus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PdrJ20BC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__existing_apps.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PdrJ20BC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__existing_apps.webp" alt="Existing coronavirus related apps" width="338" height="227"&gt;&lt;/a&gt;&lt;p&gt;Existing coronavirus related apps&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;I found 5 of them. They all provide a similar function of displaying the latest numbers from the virus with a different UI applied. Some apps also provided the latest news and charts. One of them even allow the user to enable notifications but it didn’t have the simple UI that I wanted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--supoITSo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__play_store_all_gone.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--supoITSo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__play_store_all_gone.webp" alt="All previously existed coronavirus apps disappeared from the Play Store" width="338" height="600"&gt;&lt;/a&gt;&lt;p&gt;All previously existed coronavirus apps disappeared from the Play Store&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;All 5 apps have now all but disappeared from the Play Store. All that shows up are games, with an exception of an app on the “History of Coronavirus” that talks about the scientific discovery of the virus in English and 9 other languages, without any mention of infected numbers or news.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appeals
&lt;/h2&gt;

&lt;p&gt;So at least it wasn’t only my app that got rejected. I appealed the suspension with the reasons I mentioned above (through Google Play Console so I don’t have the email) and 2 days later I got a reply that said the same thing as the first email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4u4_aS14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__1st_appeal_reply.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4u4_aS14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__1st_appeal_reply.webp" alt="Google's reply to 1st appeal" width="429" height="929"&gt;&lt;/a&gt;&lt;p&gt;Google’s reply to 1st appeal&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;To be honest, this was within expectation as it seems to be in line with what others have said.&lt;/p&gt;

&lt;p&gt;I appealed again because I still don’t agree with the suspension as my app was never released to the public. &lt;a href="https://www.reddit.com/r/androiddev/comments/6wz6wr/some_questions_about_developer_account_suspension/?utm_source=share&amp;amp;utm_medium=web2x"&gt;Apparently, having suspensions&lt;/a&gt; will scar my account status permanently where it will eventually be terminated and &lt;em&gt;everything&lt;/em&gt; in the account will become inaccessible (inc. the account’s Gmail).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y75-Lp7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__2nd_appeal.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y75-Lp7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__2nd_appeal.webp" alt="My second appeal" width="431" height="643"&gt;&lt;/a&gt;&lt;p&gt;My second appeal&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Google’s reply to my second appeal only took them less than half a day. They didn’t give me any more information than their first email and nothing was changed on my end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aORprxm2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__2nd_appeal_reply.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aORprxm2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__2nd_appeal_reply.webp" alt="Google's reply to 2nd appeal" width="430" height="459"&gt;&lt;/a&gt;&lt;p&gt;Google’s reply to 2nd appeal&lt;/p&gt;

&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Clarity At Last
&lt;/h2&gt;

&lt;p&gt;A few days past, CNBC published an &lt;a href="https://www.cnbc.com/2020/03/05/apple-rejects-coronavirus-apps-that-arent-from-health-organizations.html"&gt;article&lt;/a&gt; that talks about how Apple is removing all apps related to coronavirus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ew9bOHJj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__cnbc_news.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ew9bOHJj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/first_google_play__cnbc_news.webp" alt="CNBC news article on coronavirus app removal" width="338" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of the developers whose coronavirus app was taken down got a response from Apple that said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“apps with information about current medical information need to be submitted by a recognized institution,”…Apple has been specifically evaluating coronavirus apps to prevent the spread of misinformation. It looks at both where the health data comes from and whether the developers represent organizations that users can trust to publish accurate data, like governments or health-focused organizations, according to a person familiar with the matter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While Google didn’t comment on the issue, they did release a &lt;a href="https://play.google.com/store/apps/topic?id=campaign_editorial_3003109_crisis_medical_outbreak_apps_cep"&gt;list of apps&lt;/a&gt; that are from well-recognized sources or government, such as the CDC of US Government, American Red Cross and Twitter (obviously no misinformation here 🙃).&lt;/p&gt;

&lt;p&gt;I think this measure is fair and helpful in preventing the spread of misinformation. But Google should’ve been more transparent and tell developers the reasons for the app takedown. However, I’m still highly skeptical that suspension is needed as the data my app presented was purely factual, from a recognized source, and without misrepresentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;Good thing that I didn’t spend too much time on this. And since I coded it in Flutter, perhaps I should try and submit to the Apple App Store and see how it goes. However, I couldn’t find any apps that report numbers of infected on the Apple App Store either. I guess I better move on to the next app idea.&lt;/p&gt;

&lt;p&gt;This experience has taught me firsthand how helpless developers can be when it comes to relying on the App Store for distributions. I couldn’t have imagined how stressful this will be if I relied on the app for revenue and income.&lt;/p&gt;

&lt;p&gt;While I understand these checks and balances are in place to prevent malicious apps from abusing the system, they are also far from perfect as they also create unnecessary barriers for app developers with good intentions.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>flutter</category>
      <category>cornavirus</category>
    </item>
    <item>
      <title>Why Cafés Need an App?</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Tue, 21 Jan 2020 19:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/why-cafes-need-an-app-26i8</link>
      <guid>https://dev.to/flyingnobita/why-cafes-need-an-app-26i8</guid>
      <description>&lt;p&gt;What do customers and café owners have to gain with a mobile app?&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
👪 Customers’ Benefits

&lt;ol&gt;
&lt;li&gt;Skip the Line&lt;/li&gt;
&lt;li&gt;Never Forget Your Loyalty Card&lt;/li&gt;
&lt;li&gt;Easy Access to Store Menu&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
💁 Store Owners’ Benefits

&lt;ol&gt;
&lt;li&gt;Reduce Staff Needed&lt;/li&gt;
&lt;li&gt;Individual Customer Intelligence&lt;/li&gt;
&lt;li&gt;Direct Channel to Customers&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;📈 Proven Success&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that I have &lt;a href="///mobile/flutter/react%20native/2020/01/08/mobile_dev_framework.html"&gt;picked&lt;/a&gt; Flutter, I will describe the first mobile app that I made.&lt;/p&gt;

&lt;p&gt;I recently talked to a café chain where they wanted to revamp their website to create an online store to sell their shelf items (e.g. coffee beans, espresso machines) and ship to customers.&lt;/p&gt;

&lt;p&gt;It is very easy to create a store at platforms such as &lt;a href="https://www.barrons.com/articles/shopify-stock-why-it-could-go-higher-51578933886"&gt;Shopify&lt;/a&gt; to do this. Our discussion continued to talk about selling on mobile phones and I was surprised at how they don’t have an app already that can sell food and drink items.&lt;/p&gt;

&lt;h2&gt;
  
  
  👪 Customers’ Benefits
&lt;/h2&gt;

&lt;p&gt;For customers, the benefits are plentiful and obvious:&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip the Line
&lt;/h3&gt;

&lt;p&gt;Instead of waiting in line at the cashier or for the waiter to take your order, you can put in the order on your phone and the kitchen can prepare it immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never Forget Your Loyalty Card
&lt;/h3&gt;

&lt;p&gt;How many loyalty cards with 1 stamp do you have because you always forget to bring it with you?&lt;/p&gt;

&lt;p&gt;Some payment apps offer this (e.g. Apple Wallet, Google Pay) but they don’t let you add menu items.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy Access to Store Menu
&lt;/h3&gt;

&lt;p&gt;Customers can now easily access the latest store menu, which can be updated as frequently as needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q92_kCsM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/cafe_companion_app__cafe_lineup.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q92_kCsM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/cafe_companion_app__cafe_lineup.webp" alt="Queue at Café" width="450" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💁 Store Owners’ Benefits
&lt;/h2&gt;

&lt;p&gt;Developing a mobile app today is a lot cheaper (both in terms of time and money) than a few years. The benefits easily outweigh the costs for store owners:&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduce Staff Needed
&lt;/h3&gt;

&lt;p&gt;An app that takes orders transforms every customer’s phone into a POS (Point of Sale System). The order is no longer restricted to be taken only at the cash register. Not only does this help reduce cashier staff, but it also helps reduce the staff power needed for cashier balancing and depositing cash to the banks or ATM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Individual Customer Intelligence
&lt;/h3&gt;

&lt;p&gt;Store owners only have aggregate data on their customers (e.g. busiest time of the day, most popular item) through their POS system. However, with the mobile app, any customers that order through the app will be building their individual order history. This is essential for the stores in understanding their customers’ individual preferences and habits.&lt;/p&gt;

&lt;p&gt;✔️ Free coffee on their birthday?&lt;br&gt;&lt;br&gt;
✔️ Offer drink and food combo deals to customers who only buy drinks?&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct Channel to Customers
&lt;/h3&gt;

&lt;p&gt;The app provides a 2-way communication channel. Stores can now reach out directly to their customer through the app. This can be in the form of notifications of promotional materials (e.g. new menu items), to a direct chat between the customers and store staff for feedbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  📈 Proven Success
&lt;/h2&gt;

&lt;p&gt;Selling coffee on mobile apps is not only a complementary strategy for cafés such as &lt;a href="https://itunes.apple.com/hk/app/starbucks-hong-kong/id636266448?mt=8"&gt;Starbucks&lt;/a&gt;, but coffee chains like &lt;a href="https://www.luckincoffee.com/"&gt;Luckin Coffee&lt;/a&gt; have made mobile ordering their main channel for sales. This is so successful for them that they are already ranked 2nd in China and &lt;a href="https://www.npr.org/sections/goatsandsoda/2019/05/17/723193259/what-does-chinas-luckin-coffee-have-that-starbucks-doesnt"&gt;threatening Starbucks&lt;/a&gt; to become the biggest coffee chain in China.&lt;/p&gt;

&lt;p&gt;Let’s look at how I built one in the next article.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobileappdev</category>
      <category>flutterdev</category>
    </item>
    <item>
      <title>Why I Ditch React Native for Flutter</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Wed, 08 Jan 2020 19:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/why-i-ditch-react-native-for-flutter-312m</link>
      <guid>https://dev.to/flyingnobita/why-i-ditch-react-native-for-flutter-312m</guid>
      <description>&lt;p&gt;Deciding between React Native and Flutter turns out to be a lot easier than I expected!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Requirements&lt;/li&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a &lt;a href="///general/2019/02/04/building_google_lens.html"&gt;previous post&lt;/a&gt;, I talked about building my version of Google Lens because it lacked a few features that I wanted and was also not readily accessible in phones unless it was a Pixel. Fast forward a year, Google has made a lot of improvements to the app and I can also install it in all my phones (various Android &amp;amp; iOS).&lt;/p&gt;

&lt;p&gt;While I was looking for ways to build the mobile app quickly, I realized it was going to take a bit more effort than a day or two. Mobile app development is something that I was always intrigued by, but never took the time to learn.&lt;/p&gt;

&lt;p&gt;It’s about time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Picking a framework took some time as it has to satisfy my requirements:&lt;/p&gt;

&lt;h3&gt;
  
  
  1 - Cross-platform (Android &amp;amp; iOS)
&lt;/h3&gt;

&lt;p&gt;It doesn’t make sense to create a mobile app today that can only be used by some and not others. Maintaining two separate code base also doesn’t make sense for an individual or a small team of developers.&lt;/p&gt;

&lt;p&gt;As I won’t be creating any complex games that require super-fast response time or apps that use advanced hardware features, a cross-platform framework fits the bill nicely.&lt;/p&gt;

&lt;p&gt;One can also get around this problem by building a web app. However, I think the performance and native experience from a native app is a significant advantage over problems such as app fatigue. &lt;a href="https://medium.com/james-johnson/a-simple-progressive-web-app-tutorial-f9708e5f2605"&gt;Progress web app&lt;/a&gt; might be a possible solution at some point.&lt;/p&gt;

&lt;h3&gt;
  
  
  2- Reputable and Trust Worthy Provider
&lt;/h3&gt;

&lt;p&gt;With so many cross-platform frameworks out there, it is important to pick one that is gaining momentum, or at least it’s not showing any signs of &lt;a href="https://github.com/jquery/jquery-mobile/releases"&gt;death&lt;/a&gt;. No framework will last forever, but you want to pick the one that lasts long enough so you aren’t forced to migrate in the coming few years.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UoSjHeK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/mobile_dev_framework__platform_trends_comparison.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UoSjHeK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/mobile_dev_framework__platform_trends_comparison.webp" alt="Google Trends Platform Comparison for last 5 years" width="768" height="375"&gt;&lt;/a&gt;&lt;p&gt;Google Trends of the past 5 years&lt;/p&gt;

&lt;/p&gt;

&lt;h3&gt;
  
  
  3 - Large eco-system
&lt;/h3&gt;

&lt;p&gt;It is hard to build any apps today that only use what’s provided in the framework. One usually makes use of various plugins/libraries that are written by other open-source developers so to speed up the development process (numpy and pandas library are what made Python so popular amongst people who work with data).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CsOE7AfT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/mobile_dev_framework__scipy-ecosystem.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CsOE7AfT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/mobile_dev_framework__scipy-ecosystem.webp" alt="Python Scientific Ecosystem" width="600" height="449"&gt;&lt;/a&gt;&lt;p&gt;Python Scientific Ecosystem (Source: Jake VanderPlas, PyCon 2017)&lt;/p&gt;

&lt;/p&gt;

&lt;h3&gt;
  
  
  4 - Modern, Lightweight &amp;amp; Friendly Experience
&lt;/h3&gt;

&lt;p&gt;Since I’m starting from scratch, and I intend the projects that I develop to be for my personal use, I can dismiss corporate-friendly languages such as C# or Java, and look for a language and framework that provides the best experience for an indie-developer or small team.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Native
&lt;/h2&gt;

&lt;p&gt;When considering all the above, I thought React Native would be best. React uses Javascript and JSX, which will get me more familiar with Javascript. It is a spinoff of React, and thus I can take this opportunity to get to know React better and build web apps with it later. Two birds, one stone. Besides, the ecosystem is probably the biggest with Facebook putting a lot of resources into it.&lt;/p&gt;

&lt;p&gt;Unfortunately, I had a lot of hiccups getting started. I followed the official tutorial and started with &lt;a href="https://expo.io/"&gt;Expo&lt;/a&gt;, which is supposed to make it easier for beginners to learn RN. Unfortunately, I had problems getting it to run on my device (while it was working fine on the simulator) and it took a good night to get it to work. Debugging the infrastructure while trying to learn the framework isn’t fun.&lt;/p&gt;

&lt;p&gt;Nonetheless, I continued coding for the next few weeks and made some good progress in getting a prototype with a few basic features to work.A major thing that bugged me is RN’s implementation that surrounds components. Everything in RN is a component, and all components are controlled by its props (properties) and state. In a component, you define the view, style and behaviour (logic) all in one place. I find this to be very confusing.&lt;/p&gt;

&lt;p&gt;Also, everyone seems to have their state management system, and it isn’t easy for someone starting new to pick the right one. I used Redux initially but quickly I found myself wanting to switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter
&lt;/h2&gt;

&lt;p&gt;During the midst of crawling along with RN, I noticed that more and more people were using Flutter, now that it has some time for people to use it after it went out of beta in Dec 2018. One drawback of Flutter is that it is made by Google. Google has a horrible history of &lt;a href="https://gcemetery.co/"&gt;killing products&lt;/a&gt;, even if there is a significant amount of people still using them.&lt;/p&gt;

&lt;p&gt;Flutter also uses Dart, a language that I have never used before. Though I was promised that it was an easy language to learn. After following their tutorial, I find myself spending a fraction of the time it took to rebuild my existing RN prototype! Moreover, I also got it to work on my iPhone without much hassle. It has been a while since I lasted worked with a static typed and compiled language, as a good chunk of the last decade or so I’ve been programming in VBA, Python &amp;amp; JavaScript. SQL is probably the closest to static define types as I got. And to be honest, I was quite happy about it, the same was I was happy about the introduction of Type Hints in &lt;a href="https://www.python.org/dev/peps/pep-0484/"&gt;PEP 484&lt;/a&gt; from Python 3.5.&lt;/p&gt;

&lt;p&gt;Unfortunately, things are not all rosy with Flutter. One thing that Flutter still falls behind is its ecosystem, especially when compared to RN. For example, I was looking for integration with Stripe for payments but the plugins available at the time only provided credit card payments that require the user to input the card details each time, definitely not ready for production use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;While not everything is perfect with Flutter, it has instilled good faith in me. I watched some of &lt;a href="https://www.youtube.com/playlist?list=PLjxrf2q8roU0o0wKRJTjyN0pSUA6TI8lg"&gt;Flutter Interact 2019&lt;/a&gt; live and I have increasing confidence that Google will not be killing this product anytime soon. The Flutter community is also growing quickly. &lt;a href="https://flutter.dev/web"&gt;Flutter Web&lt;/a&gt; has also been making good progress and entered the Beta phase. If the current pace continues, I will be able to launch mobile, web and desktop apps with a single codebase pretty soon.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>flutter</category>
      <category>mobileapp</category>
    </item>
    <item>
      <title>Serverless Deployment of fastai on Azure</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Mon, 18 Mar 2019 19:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/serverless-deployment-of-fastai-on-azure-2ci8</link>
      <guid>https://dev.to/flyingnobita/serverless-deployment-of-fastai-on-azure-2ci8</guid>
      <description>&lt;p&gt;How can you deploy a machine learning model without provisioning ANY servers?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UPDATE: This guide is also referenced in the &lt;a href="https://course.fast.ai/deployment_azure_functions.html"&gt;fast.ai course v3 (Part 1)&lt;/a&gt;. The only difference is the fast.ai course version doesn’t mention and use the Recommendations (pipenv &amp;amp; pyenv) thus making it a bit less opinionated. Otherwise the two guides are identical.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
FaaS - Function as a Service (aka serverless)

&lt;ol&gt;
&lt;li&gt;Supported Languages&lt;/li&gt;
&lt;li&gt;Storage And Memory Limitations&lt;/li&gt;
&lt;li&gt;Time Limitation&lt;/li&gt;
&lt;li&gt;fastai Doesn’t Compile In Windows WSL Ubuntu Using pip (unexpected)&lt;/li&gt;
&lt;li&gt;Storage Limit From Amazon Lambda and Google Cloud Functions Is Too Small (expected)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
Microsoft Azure Functions

&lt;ol&gt;
&lt;li&gt;Pricing&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
Requirements

&lt;ol&gt;
&lt;li&gt;Software&lt;/li&gt;
&lt;li&gt;Accounts&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Recommendations&lt;/li&gt;
&lt;li&gt;
1 - Local Setup

&lt;ol&gt;
&lt;li&gt;Setup Project Directory&lt;/li&gt;
&lt;li&gt;Create Azure Functions project&lt;/li&gt;
&lt;li&gt;Create Azure Function&lt;/li&gt;
&lt;li&gt;Install fastai &amp;amp; Dependencies&lt;/li&gt;
&lt;li&gt;Update Function&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&amp;lt;FUNCTION_NAME&amp;gt;/ __init__.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&amp;lt;FUNCTION_NAME&amp;gt;/function.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;export.pkl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Test Function&lt;/li&gt;
&lt;li&gt;Check Test Outputs&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
2 - Docker Setup

&lt;ol&gt;
&lt;li&gt;Build Docker image&lt;/li&gt;
&lt;li&gt;Test Docker image&lt;/li&gt;
&lt;li&gt;Push Docker image to Docker Hub&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
3 - Azure Setup

&lt;ol&gt;
&lt;li&gt;Setup Azure Resources&lt;/li&gt;
&lt;li&gt;Create Resource Group&lt;/li&gt;
&lt;li&gt;Create Storage Account&lt;/li&gt;
&lt;li&gt;Create a Linux App Service Plan&lt;/li&gt;
&lt;li&gt;Create the App &amp;amp; Deploy the Docker image from Docker Hub&lt;/li&gt;
&lt;li&gt;Configure the function app&lt;/li&gt;
&lt;li&gt;Run your Azure Function&lt;/li&gt;
&lt;li&gt;Delete Resource Group&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;References:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the &lt;a href="///deployment/2019/02/18/machine_learning_deployment_options.html"&gt;previous article&lt;/a&gt;, we’ve looked at different ways to deploy a trained machine learning model for a mobile app. This includes implementing the inference process on the mobile device and on different cloud-based architectures (IaaS, VPS, PaaS, ML PaaS).&lt;/p&gt;

&lt;p&gt;In this article, I will explore the &lt;em&gt;serverless&lt;/em&gt; architecture, the newest kid on the block, and see what are its characteristics, who the major service providers are, and implement a simple image classifier in fastai/PyTorch using one of the providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  FaaS - Function as a Service (aka serverless)
&lt;/h2&gt;

&lt;p&gt;This category of server implementation brings PaaS to a whole new level.&lt;/p&gt;

&lt;p&gt;You write your code, which is called a &lt;em&gt;function&lt;/em&gt;, it can access &lt;em&gt;resources&lt;/em&gt; you set up in the cloud provider, such as online storage for the photos. Then you set up &lt;em&gt;events&lt;/em&gt; that trigger the function to run.&lt;/p&gt;

&lt;p&gt;There’re four main advantages of going serverless:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;no need to provision or manage any hardware&lt;/li&gt;
&lt;li&gt;no need to pay for any idle resource time&lt;/li&gt;
&lt;li&gt;infrastructure can scale automatically depending on load&lt;/li&gt;
&lt;li&gt;availability and fault tolerance of the servers are built in&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is an attractive list of qualities. Sounds like everyone should be going serverless.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But should you?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In reality, there are &lt;a href="https://medium.com/@amiram_26122/the-hidden-costs-of-serverless-6ced7844780b"&gt;hidden costs&lt;/a&gt; in both dollar and time that you should be aware of. Though these costs only become problematic if your app is being heavily utilized (I mean like &lt;em&gt;millions of times a month&lt;/em&gt;). First world problems.&lt;/p&gt;

&lt;p&gt;What is relevant for your app is the limitations imposed by the cloud provider that can make your deployment problematic. Some of the main ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;
  
  
  Supported Languages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;As the responsibility of setting up and maintaining the software framework for running the code falls to the cloud provider, they have to make most of their resources and only support the most popular languages and frameworks. Your choices will be limited, down to the version number.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;h4&gt;
  
  
  Storage And Memory Limitations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You are usually limited to the amount of disk space and memory that your code has access to. This is especially a problem for ML applications because:
&lt;/li&gt;
&lt;li&gt;the application usually has a long list of dependencies and their dependencies (besides ML framework such as scikit-learn, PyTorch, TensorFlow, they also have dependencies such as numpy, pandas, etc.)&lt;/li&gt;
&lt;li&gt;the model file that contains the pre-trained weights can be big&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;h4&gt;
  
  
  Time Limitation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Each function is allowed a certain amount of time to run (usually 5-10 mins) before it is forced to terminate.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Serverless is still a new approach to the cloud, and both companies and developers are beginning to embrace it. However, there is already a lot of service providers to choose from. We can define 2 categories of serverless service providers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;own hardware and provides API for access&lt;/li&gt;
&lt;li&gt;do not own any hardware but provide its own API to access the previous category’s hardware&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here we have a list of the major providers from the first category:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Python Runtime Version&lt;/th&gt;
&lt;th&gt;Deployment Package Size&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Timeout&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Lambda&lt;/td&gt;
&lt;td&gt;2.7, 3.6, 3.7&lt;/td&gt;
&lt;td&gt;50 MB (compressed)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;250 MB (uncompressed)&lt;/td&gt;
&lt;td&gt;3 GB&lt;/td&gt;
&lt;td&gt;900 sec&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Cloud Functions&lt;/td&gt;
&lt;td&gt;3.7.1 (beta)&lt;/td&gt;
&lt;td&gt;Source: 100 MB (compressed)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source + Modules: 500 MB (uncompressed)&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;540 sec&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IBM OpenWhisk&lt;/td&gt;
&lt;td&gt;2.7.15, 3.6.8, 3.7.2&lt;/td&gt;
&lt;td&gt;48 MB&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;600 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft Azure Functions&lt;/td&gt;
&lt;td&gt;3.6 (preview)&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;1.5 GB&lt;/td&gt;
&lt;td&gt;600 sec (Consumption Plan)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unlimited (App Service Plan)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a simple image classification app, the function shouldn’t have any problem staying within the memory limits and the timeout limits. What might be a problem is the size of the deployment package. Basically, the upload of the deployment package directly to the serverless architecture will probably fail as it is likely to be bigger than the limits.&lt;/p&gt;

&lt;p&gt;A workaround to the disk space limitation is to &lt;a href="https://github.com/alecrubin/pytorch-serverless/"&gt;stripdown the ML libraries&lt;/a&gt; such that you are only left with what’s absolutely needed. &lt;a href="https://medium.com/@angelatao0123/serving-pytorch-nlp-models-on-aws-lambda-f735190ec16c"&gt;Additionally,&lt;/a&gt; you can also separate the libraries into submodules such that each module can be fitted into its own cloud function. Making an inference function call would trigger a chain of cloud functions, with the last function returning the prediction result to you.&lt;/p&gt;

&lt;p&gt;While these methods work, it seems to me that it introduces another problem. Because the slimming down of the ML libraries isn’t officially supported, there will be some work that needs to be done in order to upgrade the library to the latest version. Given the fast-paced development of all the ML framework today, this might not be a very sustainable solution.&lt;/p&gt;

&lt;p&gt;There is an &lt;a href="https://course.fast.ai/deployment_aws_lambda.html"&gt;interesting method&lt;/a&gt; (and probably the proper method) of using &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html"&gt;AWS Lambda Layers&lt;/a&gt; with AWS Lambda to bypass the storage limits. AWS Lambda Layers allows you to organize and store dependency libraries in the form of ZIP archives in AWS. These archives can be called from a Lambda function as needed and thus keep the Lambda function deployment package to a minimal, avoiding the 250 MB (uncompressed) size limit. Layers can be made public and shared. And there is a layer which contains PyTorch v1 running on Python 3.6 that the aforementioned method uses.&lt;/p&gt;

&lt;p&gt;Note from the above table that Microsoft Azure Functions doesn’t state any limits.&lt;/p&gt;

&lt;p&gt;Let’s continue and look at the second category of serverless providers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Remarks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zappa&lt;/td&gt;
&lt;td&gt;Python-only API wrapper for AWS Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zeit&lt;/td&gt;
&lt;td&gt;API wrapper around AWS Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubeless&lt;/td&gt;
&lt;td&gt;Kubernetes-centric API wrapper that supports a number of serverless providers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serverless Framework&lt;/td&gt;
&lt;td&gt;API wrapper that supports most major serverless providers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These providers offer an API wrapper that attempts to make the serverless experience friendlier and add other values to the user where they see fit. Providers like Serverless Framework and Kubeless supports multiple serverless infrastructure providers (our first category providers). This makes them especially useful because you can use one API to deploy to any of their supported providers and helps with mitigating the problem of provider lock-ins.&lt;/p&gt;

&lt;p&gt;Out of these providers, &lt;a href="https://serverless.com/"&gt;Serverless Framework&lt;/a&gt; seems the most interesting because its free API wrapper supports the most serverless infrastructure providers and in a number of languages. It has a large community that has written many &lt;a href="https://github.com/serverless/plugins"&gt;plugins&lt;/a&gt; which add extra functionality to the core API wrapper.&lt;/p&gt;

&lt;p&gt;Let’s start to use Serverless and deploy to Amazon Lambda (without using Layers) and Google Cloud Compute and see what problems we might encounter:&lt;/p&gt;

&lt;h3&gt;
  
  
  fastai Doesn’t Compile In Windows WSL Ubuntu Using pip (unexpected)
&lt;/h3&gt;

&lt;p&gt;All the serverless architectures require the use of pip and requirements.txt to install dependencies, thus I couldn’t use conda to install fastai. This led to a lot compiling issues which didn’t come up when I use conda. I found this somewhat surprising as I’ve never encountered differences between Ubuntu on WSL and straight up Ubuntu. This makes sense in hindsight as I only used WSL Ubuntu for ruby or node development. Whereas I always used conda under Windows for Python developments, which is the time when I use libraries that have more compiling complications which conda help solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage Limit From Amazon Lambda and Google Cloud Functions Is Too Small (expected)
&lt;/h3&gt;

&lt;p&gt;Once the compilation problems went away after I started deploying on a &lt;em&gt;real&lt;/em&gt; Ubuntu machine, I was hitting the storage limits.&lt;/p&gt;

&lt;p&gt;Unfortunately, the trained model file for MNIST is already 80 MB. When you add fastai, PyTorch, and their dependencies, there’s no way everything can fit in GCF, or in AWS Lambda even if you &lt;a href="https://github.com/UnitedIncome/serverless-python-requirements#dealing-with-lambdas-size-limitations"&gt;compressed the libraries&lt;/a&gt; and remove unnecessary files by &lt;a href="https://github.com/UnitedIncome/serverless-python-requirements#slim-package"&gt;enabling &lt;code&gt;slim package&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft Azure Functions
&lt;/h2&gt;

&lt;p&gt;Let’s turn our attention to Azure. This wasn’t the first choice because the &lt;a href="https://serverless.com/framework/docs/providers/azure/"&gt;Serverless documentation&lt;/a&gt; lacked Python implementation examples and their &lt;a href="https://github.com/serverless/serverless-azure-functions"&gt;Azure plugin&lt;/a&gt; hasn’t got any updates for a while. This is in stark contrast when compared with the official &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/"&gt;Azure documentation&lt;/a&gt; which is detailed with good support for Python. Perhaps Azure has been moving along quickly and there isn’t enough time for the wrapper APIs to catch up yet.&lt;/p&gt;

&lt;p&gt;In order to try Azure, we will need to forego the Serverless Framework (and all the benefits that a wrapper API provides) and directly use the Azure. It’s worth a try.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;Microsoft Azure Functions offers two kinds of pricing, &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#consumption-plan"&gt;Consumption plan&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans"&gt;App Service plan&lt;/a&gt;. The main difference is that the Consumption plan allows you to pay only when your function runs. It will scale the architecture for you if needed but you don’t have any control over how it scales. See &lt;a href="https://azure.microsoft.com/en-us/pricing/details/functions/"&gt;here&lt;/a&gt; for the Consumption plan pricing.&lt;/p&gt;

&lt;p&gt;With the App Service plan, you can pick the level of computing resources that you want your function to run on. You are then charged for as long as your resources are defined, regardless of whether your function is running or not. See &lt;a href="https://azure.microsoft.com/en-us/pricing/details/app-service/windows/"&gt;here&lt;/a&gt; for the App Service plan pricing.&lt;/p&gt;

&lt;p&gt;Currently, python is still in preview stage in Azure Functions and fastai only works when you provide your own custom Docker image on the App Service plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Software
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;real&lt;/em&gt; Linux (Windows WSL Ubuntu isn’t sufficient. Below is using Ubuntu 18.04)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/install/linux/docker-ce/ubuntu/"&gt;Docker&lt;/a&gt; (to compile fastai dependencies that don’t support manylinux-compatible wheels from PyPI e.g. Bottleneck)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.6&lt;/a&gt; (the only Python runtime currently supported by Azure Functions)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#v2"&gt;Azure Functions Core Tools version 2.x&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli"&gt;Azure CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Accounts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/"&gt;Microsoft Azure Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/"&gt;Docker Hub Account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pipenv.readthedocs.io/en/latest/"&gt;pipenv&lt;/a&gt; (Azure Function require virtualenv, so might as well use pipenv which uses virtualenv underneath)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; (in case you use a Python version other than 3.6. Besides, pyenv is &lt;a href="https://pipenv.readthedocs.io/en/latest/advanced/#automatic-python-installation"&gt;natively supported&lt;/a&gt; by pipenv)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1 - Local Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup Project Directory
&lt;/h3&gt;

&lt;p&gt;Replace &lt;code&gt;&amp;lt;PROJECT_DIR&amp;gt;&lt;/code&gt; with your own project directory name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir &amp;lt;PROJECT_DIR&amp;gt;
cd &amp;lt;PROJECT_DIR&amp;gt;
pipenv --python 3.6
pipenv shell

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Azure Functions project
&lt;/h3&gt;

&lt;p&gt;Create an Azure Function Project that uses the Python runtime. This will generate several files in the &lt;code&gt;&amp;lt;PROJECT_DIR&amp;gt;&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;func init --docker

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted, select python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Select a worker runtime:&lt;/code&gt; &lt;strong&gt;python&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create Azure Function
&lt;/h3&gt;

&lt;p&gt;Create a function with name &lt;code&gt;&amp;lt;FUNCTION_NAME&amp;gt;&lt;/code&gt; using the template &lt;code&gt;HttpTrigger&lt;/code&gt;. Replace &lt;code&gt;&amp;lt;FUNCTION_NAME&amp;gt;&lt;/code&gt; with your own function name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func new --name &amp;lt;FUNCTION_NAME&amp;gt; --template "HttpTrigger"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install fastai &amp;amp; Dependencies
&lt;/h3&gt;

&lt;p&gt;Add Azure’s dependencies to Pipfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipenv install -r requirements.txt 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install fastai and any other dependencies your app needs in the virtual environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipenv install fastai

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then output all the dependencies to requirements.txt which will be used when you build the Docker image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipenv lock -r &amp;gt; requirements.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Function
&lt;/h3&gt;

&lt;p&gt;Modify the following files in the  directory:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;/&amp;lt;FUNCTION_NAME&amp;gt;/ __init__.py&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is where your inference function lives. The following is an example of using a trained image classification model.&lt;br&gt;
&lt;/p&gt;

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

import azure.functions as func
from fastai.vision import *
import requests

def main(req: func.HttpRequest) -&amp;gt; func.HttpResponse:

    path = Path.cwd()
    learn = load_learner(path)

    request_json = req.get_json()
    r = requests.get(request_json['url'])

    if r.status_code == 200:
        temp_image_name = "temp.jpg"        
        with open(temp_image_name, 'wb') as f:
            f.write(r.content)
    else:
        return func.HttpResponse(f"Image download failed, url: {request_json['url']}")

    img = open_image(temp_image_name)
    pred_class, pred_idx, outputs = learn.predict(img)

    return func.HttpResponse(f"request_json['url']: {request_json['url']}, pred_class: {pred_class}")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;/&amp;lt;FUNCTION_NAME&amp;gt;/function.json&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Update the function authorization so that it can be called without any additional security key. Replace the corresponding line in the file with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
      "authLevel": "anonymous",
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;export.pkl&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Copy your trained model file &lt;code&gt;export.pkl&lt;/code&gt; to &lt;code&gt;&amp;lt;PROJECT_DIR&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Function
&lt;/h3&gt;

&lt;p&gt;Run the following command to start the function on your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func host start

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give you an output with the URL for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.

Http Functions:

    inference_function: [GET,POST] http://localhost:7071/api/&amp;lt;FUNCTION_NAME&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Test Outputs
&lt;/h3&gt;

&lt;p&gt;To check that your function is running properly, visit &lt;a href="http://localhost:7071"&gt;http://localhost:7071&lt;/a&gt; and you should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--swy2f8iJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/serverless_deployment__azure_docker_test_success_screeshot.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--swy2f8iJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/serverless_deployment__azure_docker_test_success_screeshot.webp" alt="Azure Docker Running Successful Screenshot" width="648" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can send an HTTP POST method to &lt;code&gt;http://localhost:7071/api/&amp;lt;FUNCTION_NAME&amp;gt;&lt;/code&gt; to check that your inference function is working. Replace &lt;code&gt;&amp;lt;URL_TO_IMAGE&amp;gt;&lt;/code&gt; with a URL that points to an image for inferencing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST http://localhost:7071/api/&amp;lt;FUNCTION_NAME&amp;gt; HTTP/1.1
content-type: application/json

{
    "url": "&amp;lt;URL_TO_IMAGE&amp;gt;"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should then see a HTTP response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Connection: close
Date: Sun, 17 Mar 2019 06:30:29 GMT
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Content-Length: 216

request_json['url']: &amp;lt;URL_TO_IMAGE&amp;gt;, pred_class: &amp;lt;PREDICTED_CLASS&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the class that your inference function predicts in &lt;code&gt;&amp;lt;PREDICTED_CLASS&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can press &lt;code&gt;Ctrl+C&lt;/code&gt; to stop the testing when you’re ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 - Docker Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build Docker image
&lt;/h3&gt;

&lt;p&gt;You can now build the Docker image that will contain your app and all the python libraries that it needs to run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build --tag &amp;lt;DOCKER_HUB_ID&amp;gt;/&amp;lt;DOCKER_IMAGE_NAME&amp;gt;:&amp;lt;TAG&amp;gt; .

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Docker image
&lt;/h3&gt;

&lt;p&gt;Start the Docker image on your local machine for testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 8080:80 -it &amp;lt;DOCKER_HUB_ID&amp;gt;/&amp;lt;DOCKER_IMAGE_NAME&amp;gt;:&amp;lt;TAG&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app in the Docker image is now running at the &lt;code&gt;localhost:8080&lt;/code&gt;. You can run the same tests in &lt;strong&gt;Check Test Outputs&lt;/strong&gt; with the new URL and you should see the same test output as before.&lt;/p&gt;

&lt;p&gt;You can press &lt;code&gt;Ctrl+C&lt;/code&gt; to stop the testing when you’re ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Docker image to Docker Hub
&lt;/h3&gt;

&lt;p&gt;Log in to Docker from the command prompt. Enter your Docker Hub password when prompted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker login --username &amp;lt;DOCKER_HUB_ID&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now push the Docker image created earlier to Docker Hub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push &amp;lt;DOCKER_HUB_ID&amp;gt;/&amp;lt;DOCKER_IMAGE_NAME&amp;gt;:&amp;lt;TAG&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3 - Azure Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup Azure Resources
&lt;/h3&gt;

&lt;p&gt;Login to Microsoft Azure with Azure CLI if you haven’t already.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az login

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute the following commands to create Azure resources and run the inference app on Azure Functions.&lt;/p&gt;

&lt;p&gt;The following example uses the lowest pricing tier, B1.&lt;/p&gt;

&lt;p&gt;Replace the following placeholders with your own names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;RESOURCE_GROUP&amp;gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;name of the Resource Group that all other Azure Resources created for this app will fall under&lt;/li&gt;
&lt;li&gt;e.g. &lt;code&gt;ResourceGroup&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;LOCATION_ID&amp;gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;run the following command to see the list of available locations:
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;az appservice list-locations --sku B1 --linux-workers-enabled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;e.g. &lt;code&gt;centralus&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;STORAGE_ACCOUNT&amp;gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;name of the Azure Storage Account, which is a general-purpose account to maintain information about your function&lt;/li&gt;
&lt;li&gt;must be between 3 and 24 characters in length and may contain numbers and lowercase letters only&lt;/li&gt;
&lt;li&gt;e.g. &lt;code&gt;inferencestorage&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;FUNCTION_APP&amp;gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;name of the Azure Function App that you will be creating&lt;/li&gt;
&lt;li&gt;will be the default DNS domain and must be unique across all apps in Azure&lt;/li&gt;
&lt;li&gt;e.g. &lt;code&gt;inferenceapp123&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create Resource Group
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group create \
--name &amp;lt;RESOURCE_GROUP&amp;gt; \
--location &amp;lt;LOCATION_ID&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create Storage Account
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az storage account create \
--name &amp;lt;STORAGE_ACCOUNT&amp;gt; \
--location &amp;lt;LOCATION_ID&amp;gt; \
--resource-group &amp;lt;RESOURCE_GROUP&amp;gt; \
--sku Standard_LRS

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create a Linux App Service Plan
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az appservice plan create \
--name &amp;lt;LOCATION_ID&amp;gt; \
--resource-group &amp;lt;RESOURCE_GROUP&amp;gt; \
--sku B1 \
--is-linux

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create the App &amp;amp; Deploy the Docker image from Docker Hub
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az functionapp create \
--resource-group &amp;lt;RESOURCE_GROUP&amp;gt; \
--name &amp;lt;FUNCTION_APP&amp;gt; \
--storage-account &amp;lt;STORAGE_ACCOUNT&amp;gt; \
--plan &amp;lt;LOCATION_ID&amp;gt; \
--deployment-container-image-name &amp;lt;DOCKER_HUB_ID&amp;gt;/&amp;lt;DOCKER_IMAGE_NAME&amp;gt;:&amp;lt;TAG&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure the function app
&lt;/h4&gt;

&lt;p&gt;The following assumes the Docker image uploaded earlier in your Docker Hub profile is public. If you have set it to private, you can see &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-function-linux-custom-image#configure-the-function-app"&gt;here&lt;/a&gt; to add your Docker credentials so that Azure can access the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;storageConnectionString=$(az storage account show-connection-string \
--resource-group &amp;lt;RESOURCE_GROUP&amp;gt; \
--name &amp;lt;STORAGE_ACCOUNT&amp;gt; \
--query connectionString --output tsv)  

az functionapp config appsettings set --name &amp;lt;FUNCTION_APP&amp;gt; \
--resource-group &amp;lt;RESOURCE_GROUP&amp;gt; \
--settings AzureWebJobsDashboard=$storageConnectionString \
AzureWebJobsStorage=$storageConnectionString

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run your Azure Function
&lt;/h3&gt;

&lt;p&gt;After the previous command, it will generally take 15-20 minutes for the app to deploy on Azure. You can also see your app in the &lt;a href="https://portal.azure.com/"&gt;Microsoft Azure Portal&lt;/a&gt; under Function Apps.&lt;/p&gt;

&lt;p&gt;The URL for your app will be:&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://&amp;lt;FUNCTION_APP&amp;gt;.azurewebsites.net/api/&amp;lt;FUNCTION_NAME&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can run the same tests in &lt;strong&gt;Check Test Outputs&lt;/strong&gt; with the new URL and you should see the same output as before.&lt;/p&gt;
&lt;h3&gt;
  
  
  Delete Resource Group
&lt;/h3&gt;

&lt;p&gt;When you are done, delete the Resource Group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group delete \
--name &amp;lt;RESOURCE_GROUP&amp;gt; \
--yes

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that with the App Service plan, you are being charged for as long as you have resources running, even if you are not calling the function. So it is best to delete the resource group when you are not calling the function to avoid unexpected charges.&lt;/p&gt;

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

&lt;p&gt;Microsoft Azure Function seems to be the simplest to deploy, without the need for any manual tinkering on our inference code or on the ML library. Unfortunately, their App Service pricing plan works like other non-serverless pricing and foregoes one of the major advantages of a serverless architecture, paying for resources only when the function runs. But as the serverless solutions mature, it won’t be long before we can run PyTorch functions like how we’re promised.&lt;/p&gt;

&lt;p&gt;Feel free to let me know in the comments if you know of other ways to deploy fastai/PyTorch on a serverless architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  References:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-python"&gt;Create your first Python function in Azure (preview)&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-function-linux-custom-image"&gt;Create a function on Linux using a custom image&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python"&gt;Azure Functions Python developer guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>fastai</category>
      <category>azure</category>
      <category>deployment</category>
    </item>
    <item>
      <title>How do I deploy a machine learning web app?</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Mon, 18 Feb 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/how-do-i-deploy-a-machine-learning-web-app-525f</link>
      <guid>https://dev.to/flyingnobita/how-do-i-deploy-a-machine-learning-web-app-525f</guid>
      <description>&lt;p&gt;A comparison of machine learning deployment methods on mobile devices and on servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Does Deployment Matters?&lt;/li&gt;
&lt;li&gt;Where Should Inference Take Place?&lt;/li&gt;
&lt;li&gt;Mobile Device Implementation&lt;/li&gt;
&lt;li&gt;Server Implementation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my &lt;a href="///general/2019/02/04/building_google_lens.html"&gt;previous post&lt;/a&gt;, I talked about how I wanted to build a mobile app that works like Google Lens, but with my own image classification models. While there are plenty of online resources with example code on building a simple image classification model, there are not nearly enough on deployment. Nonetheless, I find a plethora of choices on where and how to deploy the model both fascinating and overwhelming.&lt;/p&gt;

&lt;p&gt;While some people would say I’m digressing from ML, I would argue that deployment is an integral part of ML and it shouldn’t be dismissed. It is not my goal to become an expert in deploying ML applications, but I think knowing the basics and being aware of the different types of deployment options is essential as it will help me see the big picture and how different parts fit in. Even if I don’t ever need to do ML deployment in the future, this will still help me in designing better ML applications.&lt;/p&gt;

&lt;p&gt;If data cleaning is the &lt;a href="https://www.forbes.com/sites/gilpress/2016/03/23/data-preparation-most-time-consuming-least-enjoyable-data-science-task-survey-says/#102387a96f63"&gt;most unenjoyable part&lt;/a&gt; of machine learning for data scientists, then deploying the model is probably a close second. In fact, the skill set required by deployment is so different that it is usually done by a separate team.&lt;/p&gt;

&lt;p&gt;In this post, I will give an overview of the choices available and discuss their trade-offs. I hope this will help you with your next ML application deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Deployment Matters?
&lt;/h2&gt;

&lt;p&gt;With the help of fastai, we can reduce the training of an image classification model to 3 lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = ImageDataBunch.from_folder(path) # Load some images
learn = cnn_learner(data, models.resnet18, metrics=accuracy) # Load a model
learn.fit(1) # Train the model

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, it’s just another line of code to make a prediction using the trained model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prediction = learn.predict(img)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crux of machine learning is basically trying to figure out how to improve prediction accuracy.&lt;/p&gt;

&lt;p&gt;Armed with the above code, you can now call yourself a data scientist or &lt;a href="https://blog.usejournal.com/fake-data-jobs-9b1c74bf7da8"&gt;whatever title tickles your fancy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Jokes aside, it is amazing at how simple ML has become. Of course, this is just one example and there are many more problems where the solution isn’t nearly as simple if it is even solved. But you get my point.&lt;/p&gt;

&lt;p&gt;The image classification model implementation in this first fastai project is similar to the above and also very simple. Most of the time will be spent on cleaning the data, which will have a high impact on prediction accuracy. Then the rest of the time is spent on deployment.&lt;/p&gt;

&lt;p&gt;One of the most common ways of doing model training and ad-hoc prediction is on a Jupyter Notebook. But this method doesn’t work for a Google Lens type of app. You don’t want to take a picture with your phone, manually upload it to your computer, then make the prediction and see what the predicted outcome is. You would have to either carry your laptop or wait till you’re back home. This alone will probably throw you off the idea of building the app in the first place.&lt;/p&gt;

&lt;p&gt;Instead, you would need to build a mobile app that can make and display predictions right away.&lt;/p&gt;

&lt;p&gt;With that said, let’s explore the different ways I can deploy my mobile app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Should Inference Take Place?
&lt;/h2&gt;

&lt;p&gt;Inferencing is using a trained model to make predictions. Unlike training a model, it requires much less computing power. So little that even a smartphone nowadays can do it. A big part of deployment is to decide where and how inference takes place.&lt;/p&gt;

&lt;p&gt;Inference for a mobile app can take place either at the mobile device or at the server. There are pros and cons to either approach and the following table summarizes some of the key differences:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Mobile Device&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Internet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Required&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Internet Bandwidth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Depends on application&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Internet Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;&lt;em&gt;High&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Computing Power&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Low&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data stays on-device&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Data leaks to server&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frameworks &amp;amp; Libraries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Restricted&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;No restrictions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model Implementation Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Approach depends on the framework and device OS (Android, iOS, etc)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The decision to which approach to take will largely depend on your use case. For a simple image classification app, many of the factors regarding internet, computing power and privacy are much of a non-issue.&lt;/p&gt;

&lt;p&gt;Instead, your decision will more probably be based on which framework you would like to use. Let’s explore what is available on the mobile device front.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile Device Implementation
&lt;/h2&gt;

&lt;p&gt;Here is a summary of the more common framework or approaches that you can use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework / Approach&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.tensorflow.org/lite"&gt;TensorFlow Lite&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- for building mobile native apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.tensorflow.org/js"&gt;TensorFlow.js&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- for building web apps, which work on mobile and desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://pytorch.org/tutorials/advanced/super_resolution_with_caffe2.html"&gt;PyTorch -&amp;gt; ONNX -&amp;gt; Caffe2&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- PyTorch and Caffe2 might produce different model outputs due to slightly different implementations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PyTorch -&amp;gt; ONNX -&amp;gt; TensorFlow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- importing ONNX models into TensorFlow is still &lt;a href="https://github.com/onnx/tutorials"&gt;experimental&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://firebase.google.com/docs/ml-kit/"&gt;&lt;strong&gt;Google ML Kit&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;- API that provides inference access to on-device (via TensorFlow Lite) or cloud (via Google Cloud Vision API)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;support custom TensorFlow Lite models which can be hosted on cloud (Firebase) or device
&lt;/li&gt;
&lt;li&gt;auto-updates of on-device models from cloud
&lt;/li&gt;
&lt;li&gt;auto-switching of using models from on-device or from cloud, depending on device connectivity
&lt;/li&gt;
&lt;li&gt;beta release |
| &lt;a href="https://developer.apple.com/machine-learning/"&gt;&lt;strong&gt;Core ML 2&lt;/strong&gt;&lt;/a&gt; | - iOS only |&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some interesting observations we see from the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are a plethora of choices for TensorFlow users as it is a relatively matured framework.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/onnx/onnx"&gt;ONNX (Open Neural Network Exchange)&lt;/a&gt; is an open standard that is gaining momentum. It allows the trained models from one framework to be used by another framework. Thus you can freely choose the framework for building and training your model, knowing that it can be deployed to other supported framework for inferencing. Besides addressing the framework preferences of different people, it also helps resolve the problem of framework lock-ins.&lt;/li&gt;
&lt;li&gt;Google ML Kit seems like a very interesting option. The idea that it allows auto switching of models between on-device and the cloud, while also supporting auto-update of on-device models, is very useful. We will probably see wider adoption of this approach as it moves out of the beta phase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the current app, there are several preferences that we would like to keep:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The motivation is to apply the fastai library, thus using PyTorch is a must. &lt;em&gt;(rules out TensorFlow Lite, TensorFlow.js, Google ML Kit)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;To have full control of the whole deployment process without dealing with another framework, in addition to any model translation complications &lt;em&gt;(rules out ONNX)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;It works on Android and iOS &lt;em&gt;(rules out Core ML2)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It seems that none of the on-device implementations fully fits the bill. Let’s explore the server-side approaches next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Implementation
&lt;/h2&gt;

&lt;p&gt;Unlike mobile device implementation, there are a lot more choices to choose from on the server side. The choices that you make will depend on factors such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your level of knowledge of provisioning and managing servers&lt;/li&gt;
&lt;li&gt;the amount of time you are prepared to spend on infrastructure&lt;/li&gt;
&lt;li&gt;the computing power you need for your project&lt;/li&gt;
&lt;li&gt;the flexibility of the infrastructure to take heavier loads later on&lt;/li&gt;
&lt;li&gt;the amount of budget that you have&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8OJDfZ44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/machine_learning_deployment__server_implementation_spectrum.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8OJDfZ44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/machine_learning_deployment__server_implementation_spectrum.webp" alt="Server Implementation Spectrum" width="768" height="251"&gt;&lt;/a&gt;&lt;p&gt;The spectrum of choices can be summarized on this chart (&lt;a href="https://www.fullstackpython.com/serverless.html"&gt;source&lt;/a&gt;)&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Let’s pick a few of the above categories for a brief discussion.&lt;/p&gt;

&lt;h3&gt;
  
  
  IaaS - Infrastructure as a Service
&lt;/h3&gt;

&lt;p&gt;This is for you if you are experienced in provisioning your own servers. You are required to set up the hardware configuration, and install the required software libraries for inference and communicating with the mobile device. Furthermore, the job of maintaining the server, libraries and scalability all falls on you. You will need to patch your server on a regular basis or &lt;a href="https://www.theregister.co.uk/2017/09/14/missed_patch_caused_equifax_data_breach/"&gt;risk having it hacked&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While the first step is to learn the functions of different cloud building blocks and how they fit in with each other, the next step of getting familiar with the pricing of all the services offered can get complicated quickly (or you may even dare to compare costs of different providers).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With great flexibility comes great power, and probably frustrations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Pros:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;cost can be reduced to a minimal at all times through tweaking configurations while maintaining maximum performance&lt;/li&gt;
&lt;li&gt;valuable experience and skills that can be transferred to any projects or other providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;high degree of configuration can be overwhelming&lt;/li&gt;
&lt;li&gt;cost can be unexpectedly high &lt;a href="https://www.itproportal.com/features/7-hidden-aws-costs-that-could-be-killing-your-budget/"&gt;if you are not familiar with how the pricing works&lt;/a&gt;, e.g. resources that you turned off are still being charged because they have not been decommissioned, or your app experienced an unexpectedly high amount of traffic and you haven’t put in place any alerts or countermeasures&lt;/li&gt;
&lt;li&gt;a large investment of time for anyone new in order to learn the “art of provisioning servers”, which might not be worth its weight if it doesn’t suit your goal&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Providers:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ec2/"&gt;AWS EC2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/compute/"&gt;Google Compute Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/services/virtual-machines/"&gt;Microsoft Azure Virtual Machine&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VPS - Virtual Private Servers
&lt;/h3&gt;

&lt;p&gt;VPS simplifies the process of hardware selection by giving you preselected configurations to choose from, thus vastly limiting the choices that you have to make. It’s like renting a preassembled computer a month at a time where your choices are “small”, “medium”, or “large”. You are charged a fixed fee per month and there isn’t much room (i.e. risk) to go over the fixed fee.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;great choice for people new to the cloud&lt;/li&gt;
&lt;li&gt;much simpler to set up than IaaS&lt;/li&gt;
&lt;li&gt;many providers to choose from&lt;/li&gt;
&lt;li&gt;risk of “surprise charges” significantly reduced&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Con:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;need to upgrade to a higher pricing tier even if only one of the resources needs to be upgraded e.g. you will need to pay for a higher tier if you need more memory but the other resources in the current tier are still under-utilized (processor, disk space, etc.)&lt;/li&gt;
&lt;li&gt;most people find the lack of customization becoming more of a problem as their application grows and their cloud knowledge increase&lt;/li&gt;
&lt;li&gt;more expensive than IaaS&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Providers:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/lightsail/"&gt;Amazon Lightsail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azuremarketplace.microsoft.com/en-us"&gt;Microsoft Azure Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/"&gt;Digital Ocean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linode.com/"&gt;Linode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PaaS - Platform as a Service
&lt;/h3&gt;

&lt;p&gt;PaaS offers a level of abstraction where you don’t need to “speak servers”. They usually come with a preinstalled OS, database, web server and a programming environment of a language of your choice. The cloud provider will handle any necessary server or software maintenance for you.&lt;/p&gt;

&lt;p&gt;Another major advantage of PaaS over IaaS and VPS is it will auto-scale the server for you. You can define budgets beforehand and let the cloud provider handle the rest. This can be handy if your app hits the coveted &lt;a href="https://thehftguy.com/2017/09/26/hitting-hacker-news-front-page-how-much-traffic-do-you-get/"&gt;Hacker News FP&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;auto-scaling of services&lt;/li&gt;
&lt;li&gt;easier to manage than IaaS&lt;/li&gt;
&lt;li&gt;cheaper than MLaaS&lt;/li&gt;
&lt;li&gt;support &lt;a href="https://www.atlassian.com/continuous-delivery/continuous-integration"&gt;Continuous Integration&lt;/a&gt; &amp;amp; &lt;a href="https://www.atlassian.com/continuous-delivery/continuous-deployment"&gt;Continuous Deployment&lt;/a&gt; through popular source code repositories (e.g. Github)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;more setup required than MLaaS&lt;/li&gt;
&lt;li&gt;more expensive (usually) than IaaS&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Providers:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/elasticbeanstalk/"&gt;AWS Beanstalk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/appengine/"&gt;Google App Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/services/app-service/"&gt;Microsoft Azure App Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.heroku.com/"&gt;Heroku&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ML PaaS - Machine Learning Platform as a Service
&lt;/h3&gt;

&lt;p&gt;This is the newest offerings from cloud providers that give ML practitioners cloud computing power with as little infrastructure setup as possible. It can be considered as an ML flavoured form of PaaS. Each provider will have their own list of supported ML frameworks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;all the pros of PaaS&lt;/li&gt;
&lt;li&gt;good integration of all the infrastructure resources needed within the cloud provider to run a complete ML setup&lt;/li&gt;
&lt;li&gt;ability to choose the type of infrastructure (from a single computer to a computer cluster)&lt;/li&gt;
&lt;li&gt;GUI for building the whole ML project pipeline&lt;/li&gt;
&lt;li&gt;support hyperparameter tuning&lt;/li&gt;
&lt;li&gt;track experiment results&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;prone to lock-ins to a cloud provider (“&lt;a href="https://www.thoughtworks.com/radar#sticky-clouds"&gt;Sticky Clouds&lt;/a&gt;”)&lt;/li&gt;
&lt;li&gt;most expensive&lt;/li&gt;
&lt;li&gt;might not have support for your particular ML framework&lt;/li&gt;
&lt;li&gt;hard to fine tune infrastructure for better performance and cost&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Providers:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sagemaker/"&gt;Amazon SageMaker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/ml-engine/"&gt;Google Cloud Machine Learning Engine&lt;/a&gt; (doesn’t currently support PyTorch)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/services/machine-learning-service/"&gt;Microsoft Azure Machine Learning Service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prebuild Models
&lt;/h3&gt;

&lt;p&gt;This is a sub-type of ML PaaS (or maybe it’s a SaaS, Software as a Solution, who knows) that allows you to use their pre-trained models. These solutions reduce the whole image classification solution to a few API calls or a few clicks. You just upload your photos and it’ll give you information about the image which might already contain the information that you’re trying to predict. This is only useful if their pre-trained solutions overlap with the solution that you’re looking for, as you will not be able to do much customization. This is designed for people who don’t have any ML experience. Since we want to use our own trained model, we will not be considering this for our project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Providers:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/machine-learning/#AI_SERVICES"&gt;Amazon Machine Learning AI Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/products/ai/building-blocks/"&gt;Google Cloud AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/services/machine-learning-studio/"&gt;Microsoft Azure Machine Learning Studio&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our app, using the most basic option from any of the above solution would be sufficient. It’ll probably take is no more than 15 mins top. But what fun is that? (I’ve spent more time than I wish in the past provisioning servers) What if we don’t have to provision &lt;em&gt;any&lt;/em&gt; servers?&lt;/p&gt;

&lt;p&gt;Let’s look into this a bit more in the &lt;a href="///deployment/serverless/2019/03/18/serverless_deployment_of_fastai_on_azure.html"&gt;next post&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>machinelearning</category>
      <category>webapp</category>
    </item>
    <item>
      <title>Building My Own (And Better) Google Lens</title>
      <dc:creator>Flying Nobita</dc:creator>
      <pubDate>Mon, 04 Feb 2019 12:00:00 +0000</pubDate>
      <link>https://dev.to/flyingnobita/building-my-own-and-better-google-lens-562e</link>
      <guid>https://dev.to/flyingnobita/building-my-own-and-better-google-lens-562e</guid>
      <description>&lt;p&gt;What finally motivated me to build an image classification mobile app?&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;So what is fastai? Or fast.ai?&lt;/li&gt;
&lt;li&gt;Why Google Lens&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I recently took the &lt;a href="https://course.fast.ai"&gt;Deep Learning Part 1 online course&lt;/a&gt; offered by Jeremy Howard. Having taken other machine learning MOOCs before, including courses from &lt;a href="https://www.coursera.org/learn/machine-learning"&gt;Andrew Ng&lt;/a&gt;, &lt;a href="https://www.udacity.com/course/intro-to-machine-learning--ud120"&gt;Sebastien Thrun&lt;/a&gt;, &lt;a href="https://www.youtube.com/playlist?list=PLoRl3Ht4JOcdU872GhiYWf6jwrk_SNhz9"&gt;Geoffrey Hinton&lt;/a&gt;, I can say that Jeremy’s course is indeed a bit different from the others. I don’t know if this is because of the top-down approach he used to present the topic (he quoted &lt;a href="https://www.gse.harvard.edu/news/uk/09/01/education-bat-seven-principles-educators"&gt;research&lt;/a&gt; that shows it helps students learn and I agree), or his strong emphasis on the practical approach to AI, or it’s his self-deprecating humour that he enjoys using to poke fun at his management consulting job in his previous life. The correct answer is probably all of the above.&lt;/p&gt;

&lt;p&gt;Now that I am eagerly waiting for Part 2 of his course, I have decided to heed his advice and build a (supposedly) small project to practice the techniques he taught in class using the fastai library. This is an effect that the previous courses I took didn’t have on me.&lt;/p&gt;

&lt;p&gt;In this and future posts, I will talk about how I progress on the project and discuss topics that come up which I find interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is fastai? Or fast.ai?
&lt;/h2&gt;

&lt;p&gt;Jeremy started a company &lt;a href="https://www.fast.ai/"&gt;fast.ai&lt;/a&gt; to educate AI to the masses, with an interesting endgame:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our goal at fast.ai is for there to be nothing to teach.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;fast.ai offers free online courses, which are recordings from his &lt;a href="https://www.usfca.edu/data-institute/certificates/deep-learning-part-one"&gt;SFU classes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.fast.ai/2017/09/08/introducing-pytorch-for-fastai/"&gt;2017&lt;/a&gt;, he decided to switch the deep learning framework he was using in his course from Keras and TensorFlow to &lt;a href="https://pytorch.org/"&gt;PyTorch&lt;/a&gt;. But he didn’t just stop there. Instead, he took the idea of Keras and built an opinionated library, &lt;a href="https://github.com/fastai/fastai"&gt;fastai&lt;/a&gt;, on top of PyTorch. He added helpful tools and incorporated the latest best practices that made applying AI to solving common problems a breeze.&lt;/p&gt;

&lt;p&gt;PyTorch, a rewrite of &lt;a href="http://torch.ch/"&gt;Torch&lt;/a&gt; (a defunct ML library based on Lua), got its roots from the ML research community and has been getting a lot of &lt;a href="https://www.oreilly.com/ideas/why-ai-and-machine-learning-researchers-are-beginning-to-embrace-pytorch"&gt;attention&lt;/a&gt; in the general ML community recently. I think doing the fasta.ai course would be the perfect way for me to start using it.&lt;/p&gt;

&lt;p&gt;Having said that, a key point that Jeremy made which resonates well with me is that we should not be focusing on learning the actual framework (though this is necessary), as there will always be newer and better frameworks being developed. Instead, we should be focusing our attention on learning the underlying AI concepts and techniques, which will allow us to switch framework if needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cmeza2DA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/building_google_lens__arxiv_monthly_submission_rates.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cmeza2DA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/building_google_lens__arxiv_monthly_submission_rates.webp" alt="arXiv.org Monthly Submission Rates" width="600" height="383"&gt;&lt;/a&gt;&lt;p&gt;Keeping up with AI research is hard enough (&lt;a href="https://arxiv.org/stats/monthly_submissions"&gt;source&lt;/a&gt;)&lt;/p&gt;

&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Google Lens
&lt;/h2&gt;

&lt;p&gt;Computer vision has always been a popular branch of AI for anyone new to the field as the basic concepts can be understood easily when presented with vivid visuals. &lt;a href="https://en.wikipedia.org/wiki/MNIST_database"&gt;MNIST&lt;/a&gt; is basically the Hello World for ML. While the recent advances in AI have made writing a simple image classifier easy (e.g. classifying digits, dogs vs cats) and requiring only a few lines of code, it gets much harder if we want to classify an object from a much larger number of possible labels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6CJNoTqE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/building_google_lens__not-hotdog.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6CJNoTqE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/flyingnobita/image/fetch/c_limit%2Cf_auto%2Cq_auto%2Cw_768/https://flyingnobita.com/assets/images/posts/building_google_lens__not-hotdog.webp" alt="Not A Hotdog!" width="337" height="600"&gt;&lt;/a&gt;&lt;p&gt;Building this should be much quicker&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://lens.google.com/"&gt;Google Lens&lt;/a&gt; as an example, it is very good at recognizing objects from a few pre-defined categories, such as landmarks, plants, and dog breeds. But if I want to identify an object that is not in these categories, say the &lt;a href="https://butterfly-conservation.org/butterflies/adonis-blue"&gt;species of a butterfly&lt;/a&gt;, or the name of a &lt;a href="https://www.kaggle.com/gfolego/vangogh"&gt;Van Gogh painting&lt;/a&gt;, then it wouldn’t work. Google Lens will suggest images which contain objects that look similar to my photo, but I would still need to click on that image and search the page for the information that I’m looking for.&lt;/p&gt;

&lt;p&gt;Now if I had a trained model for identifying butterfly species and another model for identifying Van Gogh paints, and I could tell Google Lens beforehand which model to use, then the aforementioned problem might be solved.&lt;/p&gt;

&lt;p&gt;This sounds to me like a simple and fun project to work on, and it might even be useful at times (at least until Google Lens, or &lt;a href="https://www.digitaltrends.com/mobile/bing-visual-search-launches/"&gt;Bing Visual Search&lt;/a&gt;, can start doing this).&lt;/p&gt;

&lt;p&gt;Let’s try it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>imageclassification</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
