<?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: Dominik Zarsky</title>
    <description>The latest articles on DEV Community by Dominik Zarsky (@loupeznik).</description>
    <link>https://dev.to/loupeznik</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%2F583933%2F5c17d582-e17f-4cf2-b018-abce9b0c9b0d.png</url>
      <title>DEV Community: Dominik Zarsky</title>
      <link>https://dev.to/loupeznik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/loupeznik"/>
    <language>en</language>
    <item>
      <title>Automate your Laravel app deployments with Azure DevOps</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Wed, 01 Mar 2023 18:54:25 +0000</pubDate>
      <link>https://dev.to/loupeznik/automate-your-laravel-app-deployments-with-azure-devops-1f2j</link>
      <guid>https://dev.to/loupeznik/automate-your-laravel-app-deployments-with-azure-devops-1f2j</guid>
      <description>&lt;p&gt;Deploying Laravel applications can be a tedious and time-consuming process, but with the use of continuous integration and continuous deployment (CI/CD) pipelines, the process can be automated. Azure DevOps is a popular choice for creating such pipelines, as it offers a wide range of tools for building, testing and deploying code. In this article, we will explore how to configure a build pipeline and release pipeline, and automate the deployment of a Laravel application using Azure DevOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Azure DevOps
&lt;/h2&gt;

&lt;p&gt;Azure DevOps Services is a cloud-based CI/CD and project management tool developed by Microsoft. It also has an on-premises alternative called Azure DevOps Server.&lt;/p&gt;

&lt;p&gt;Most of the features of this tool are free to use for companies and individual developers (up to certain limits), including private git repositories, Boards for project management, Test cases, Artifact repositories (for storing npm, NuGet or Python packages) and Azure Pipelines to support your CI/CD processes.&lt;/p&gt;

&lt;p&gt;In this article, we are going to be looking at Azure Pipelines via Azure DevOps Services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the repository and the Build pipeline
&lt;/h2&gt;

&lt;p&gt;For this article, I have created a starter MVC application with Laravel Breeze and React which will be hosted on GitHub (this is not a prerequisite as Azure DevOps has excellent support for other platforms such as GitLab or BitBucket as well).&lt;/p&gt;

&lt;p&gt;For the build, a free Azure DevOps account is required. The free tier limits are described on the &lt;a href="https://azure.microsoft.com/en-us/pricing/details/devops/azure-devops-services/" rel="noopener noreferrer"&gt;Pricing page&lt;/a&gt;. To summarize - the free tier includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1800 minutes per month for builds and releases running on the Microsoft-hosted agents for private projects (self-hosted agents have no limits, public projects have no limits on the Microsoft-hosted agents as well)&lt;/li&gt;
&lt;li&gt;Unlimited private git repositories (we will not be using these)&lt;/li&gt;
&lt;li&gt;2GB artifact storage (should be plenty as Laravel build artifacts do not consume much space anyway)&lt;/li&gt;
&lt;li&gt;Azure Boards for project management&lt;/li&gt;
&lt;li&gt;One parallel job (build or release) running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The account can be created &lt;a href="https://dev.azure.com" rel="noopener noreferrer"&gt;here&lt;/a&gt;. If you already have an Azure subscription, you can use your Azure account as well.&lt;/p&gt;

&lt;p&gt;After the signup, create a project and connect your git repository. For this article, I will be using a public project which is publicly visible by default. This will make all the builds, artifacts and repositories in such projects also public. Use a private project if your pipelines are intended to be private.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1gml0avae7hy1sivnn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1gml0avae7hy1sivnn2.png" alt="Setting up a project in Azure DevOps" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To create a build pipeline, we first need to create a YAML file describing the build process. This file is usually called &lt;code&gt;azure-pipelines.yaml&lt;/code&gt; and is stored in the root of the git repo. Other naming conventions (for example &lt;code&gt;ci.yaml&lt;/code&gt;) and file locations can also be used. This file can be either created manually or via Azure DevOps. For me, configuring the pipeline via Azure DevOps is the preferred way as all the possible build tasks and their options are listed and can be used as helpers for building the pipeline. You can also use the &lt;em&gt;Classic pipelines&lt;/em&gt; alternative which is a no-code solution to building your pipelines. I generally recommend having pipelines stored as code, thus I will not be covering &lt;em&gt;Classic pipelines&lt;/em&gt; in this article.&lt;/p&gt;

&lt;p&gt;The steps to create a new pipeline directly from Azure DevOps are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your projects at &lt;a href="https://dev.azure.com/your_organization" rel="noopener noreferrer"&gt;https://dev.azure.com/your_organization&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select your project&lt;/li&gt;
&lt;li&gt;In the left menu, go to &lt;em&gt;Pipelines&lt;/em&gt; and select &lt;em&gt;Pipelines&lt;/em&gt; from the submenu&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Create Pipeline&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Select the &lt;em&gt;GitHub with YAML&lt;/em&gt; option&lt;/li&gt;
&lt;li&gt;Select your repo (you might need to authenticate to GitHub and grant repo permissions)&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;Configure your pipeline&lt;/em&gt; section, select &lt;em&gt;Starter pipeline&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should end up on the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdne75y2fjtlq20qus14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdne75y2fjtlq20qus14.png" alt="The starter pipeline" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this screen, you can start writing the pipeline. Azure Pipelines are well documented by Microsoft, you can check out the documentation &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/yaml-pipeline-editor?view=azure-devops" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For now, let's focus on the steps needed to build the pipeline for our Laravel application. The final build pipeline looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;phpVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8.1&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;sudo update-alternatives --set php /usr/bin/php$(phpVersion)&lt;/span&gt;
    &lt;span class="s"&gt;sudo update-alternatives --set phar /usr/bin/phar$(phpVersion)&lt;/span&gt;
    &lt;span class="s"&gt;sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpVersion)&lt;/span&gt;
    &lt;span class="s"&gt;sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpVersion)&lt;/span&gt;
    &lt;span class="s"&gt;sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpVersion)&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PHP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(phpVersion)'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install --no-interaction --prefer-dist&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;composer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeTool@0&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;versionSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18.x'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NodeJS'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Npm@1&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;install'&lt;/span&gt;
    &lt;span class="na"&gt;workingDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Npm@1&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;custom'&lt;/span&gt;
    &lt;span class="na"&gt;workingDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;customCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CopyFiles@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;SourceFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;Contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;**/**&lt;/span&gt;
      &lt;span class="s"&gt;!**/node_modules/**&lt;/span&gt;
      &lt;span class="s"&gt;!**/.git/**&lt;/span&gt;
      &lt;span class="s"&gt;!**/storage/**&lt;/span&gt;
    &lt;span class="na"&gt;TargetFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;CleanTargetFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;OverWrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Copy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;directory'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishBuildArtifacts@1&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;PathtoPublish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;ArtifactName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;drop'&lt;/span&gt;
    &lt;span class="na"&gt;publishLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Container'&lt;/span&gt;
    &lt;span class="na"&gt;StoreAsTar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dissect the pipeline a little:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;trigger&lt;/em&gt; determines a Continual Integration (CI) trigger on a branch of your project. If the branch is defined in the &lt;em&gt;trigger&lt;/em&gt; array, the build will run every time code is pushed into the branch. It will also run build validation on pull requests&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;pool&lt;/em&gt; determines an agent pool which will be used for the build. In this case, &lt;em&gt;ubuntu-latest&lt;/em&gt; is used. You can also use Windows and MacOS agents. These agents are hosted by Microsoft and their use is counted towards the free build minutes included in the Azure DevOps Free Tier. You can also use self-hosted agents (these will be covered in the next section of this article)&lt;/li&gt;
&lt;li&gt;As this is not a multi-stage build, &lt;em&gt;steps,&lt;/em&gt; which describe the tasks the pipeline has to run, are defined directly in the base of the YAML file&lt;/li&gt;
&lt;li&gt;The first couple of steps is to install dependencies - PHP, Composer and NodeJS. On Ubuntu agents, PHP and Composer are installed by default, we only have to select the version of PHP which we want to use. This is achieved by setting the version in the &lt;em&gt;variables&lt;/em&gt; section and consequently using the &lt;code&gt;update-alternatives&lt;/code&gt; command inside the first &lt;em&gt;script&lt;/em&gt; task&lt;/li&gt;
&lt;li&gt;We then run &lt;code&gt;composer install&lt;/code&gt; to install dependencies. In this case, the application sits in the root of the git repo so we do not have to define a path for Composer&lt;/li&gt;
&lt;li&gt;NodeJS is installed via the &lt;em&gt;NodeTool@0&lt;/em&gt; task, we can specify the version of Node we want to install&lt;/li&gt;
&lt;li&gt;Node packages are installed and the JS bundle is built using the &lt;em&gt;Npm@1&lt;/em&gt; tasks. We need to define the command and path to files to each command (in this case we &lt;em&gt;install&lt;/em&gt; and then run a custom &lt;em&gt;build&lt;/em&gt; command for build). The path is by default &lt;code&gt;$(Build.SourcesDirectory)&lt;/code&gt; which represents the root of the pulled git repository&lt;/li&gt;
&lt;li&gt;After all dependencies are installed and the frontend bundle is built, we can copy the files to &lt;code&gt;$(Build.ArtifactStagingDirectory)&lt;/code&gt;. We are excluding the &lt;em&gt;node_modules, .git and storage&lt;/em&gt; directories as these are no longer needed&lt;/li&gt;
&lt;li&gt;Finally, we take the complete application files from the &lt;em&gt;artifact staging directory&lt;/em&gt;, compress these into a &lt;em&gt;tar&lt;/em&gt; archive and publish them. This will upload the &lt;em&gt;tar&lt;/em&gt; to Azure Pipelines storage for later use in &lt;em&gt;Release&lt;/em&gt; pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to add more tasks, the &lt;em&gt;Assistant&lt;/em&gt; which can be found in the top right corner of the editor is very useful as it contains a list of usable tasks, you can also define the task values in a UI which will then be transformed into YAML representation.&lt;/p&gt;

&lt;p&gt;After completing your pipeline, click &lt;em&gt;Save and run&lt;/em&gt; button in the top right corner above the editor, this will commit the pipeline to your repo and run the pipeline. Ideally, it would complete on the first try, if not, open the build and search the task logs for errors. The pipeline logs should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j9it83rngsgs9t88849.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j9it83rngsgs9t88849.png" alt="The pipeline logs" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the deployment
&lt;/h2&gt;

&lt;p&gt;The deployment can be a little trickier depending on where your application is published. If you use shared hosting, Azure AppService or a similar service, the deployment would be pretty straightforward as you would only need to download the artifact and upload it via FTP or AzureRM to your destination.&lt;/p&gt;

&lt;p&gt;For 'enterprise' use or for people who host applications on their own servers, this is where the fun begins. One of the best features of Azure DevOps CI/CD pipelines is the option to use self-hosted agents (or private agents as they are sometimes called).&lt;/p&gt;

&lt;p&gt;This means that an agent (or multiple agents) which is used for the deployment is running directly on the application server. This has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The communication is done in such a way that the self-hosted agent on your server communicates to Azure DevOps via an API. Therefore, in many cases it eliminates the need to have multiple ports open on the application server's firewall to be able to deploy your application there from a remote CI/CD server, thus eliminating possible attack vectors on your infrastructure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You also do not need to store credentials or SSH keys anywhere in the CI/CD tool as the agent runs directly on the machine as a regular user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The deployments via the Private agent also tend to be faster than deploying via SFTP/WinRM/Robocopy or other tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These agents can be used free of charge (with no monthly limits on usage minutes or number of agents)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The release pipeline
&lt;/h3&gt;

&lt;p&gt;Unlike the build pipelines, the release pipelines do not use YAML but rather a visual editor just like &lt;em&gt;Classic pipelines&lt;/em&gt;. You can find release pipelines under &lt;em&gt;Releases in the Pipelines&lt;/em&gt; submenu. From there you can create a &lt;em&gt;New pipeline&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw1h62k8w61ihmbfign0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw1h62k8w61ihmbfign0.png" alt="The artifact setup screen" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating the pipeline, you can &lt;em&gt;Add an artifact&lt;/em&gt;, where you can select the desired artifact. For the source pipeline, choose the pipeline you've just build from the dropdown and set the source alias (this will be the alias which you'll use to reference the build artifact), for example, _build. After that, we'll need to define one or multiple release stages. These would normally be DEV, STAGING, UAT, PROD, etc. Stages then contain deployment tasks.&lt;/p&gt;

&lt;p&gt;You can also use the YAML pipelines to deploy your application. This feature made it from preview to stable a few years ago but in my opinion, it is not as polished as the current &lt;em&gt;Classic Releases&lt;/em&gt; (there are some caveats to managing your deployments this way). You can check out the YAML deployments documentation &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs?view=azure-devops" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you are interested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using self-hosted agent
&lt;/h3&gt;

&lt;p&gt;As stated before, for deploying to your own server, the recommended way is to use a self-hosted agent. This agent can be run in a Docker container or directly on the target system as a regular user. The agent can make use of utilities installed on the system which is helpful for the deployment.&lt;/p&gt;

&lt;p&gt;The pipeline for self-hosted applications looks as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0f7hslof1wat3lr9y86r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0f7hslof1wat3lr9y86r.png" alt="The release pipeline" width="637" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A pseudo-representation in YAML would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artisan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;down'&lt;/span&gt;
  &lt;span class="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(DeployDirectory)'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Take&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;offline'&lt;/span&gt;
  &lt;span class="na"&gt;continueOnError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$(DeployTempDirectory)"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mkdir&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(DeployTempDirectory);&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tar&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-xf&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(System.DefaultWorkingDirectory)/_build/drop/drop.tar&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-C&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(DeployTempDirectory)'&lt;/span&gt;
  &lt;span class="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(DeployDirectory)'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Extract&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;temporary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;directory'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CopyFiles@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;application'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;SourceFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(DeployTempDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;TargetFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(DeployDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;OverWrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artisan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;migrate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force'&lt;/span&gt;
  &lt;span class="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(DeployDirectory)'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;migrations'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artisan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;up'&lt;/span&gt;
  &lt;span class="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(DeployDirectory)'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Start&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;application'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline also uses variables which are defined in the &lt;em&gt;Variables&lt;/em&gt; section of the release. For each stage, a &lt;code&gt;$(DeployDirectory)&lt;/code&gt; variable is defined as the path to the application deploy directory on the target system.&lt;/p&gt;

&lt;p&gt;To install a self-hosted agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;em&gt;Project Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Agent Pools&lt;/em&gt; in your DevOps project&lt;/li&gt;
&lt;li&gt;Create a new Agent Pool&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;New Agent&lt;/em&gt; in the top right corner of the window&lt;/li&gt;
&lt;li&gt;Follow the instructions on the screen&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also install the agent as a Docker container, refer to &lt;a href="https://github.com/Loupeznik/utils/tree/master/az-devops-agent" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; for more.&lt;/p&gt;

&lt;p&gt;For PAT authentication, you will also need to generate a Personal Access Token, this can be done from the settings menu. The PAT needs to have permission to &lt;em&gt;Read and manage&lt;/em&gt; Agent Pools, otherwise, it will not be able to register itself to the created agent pool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvont8fea6kcmwg4hejqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvont8fea6kcmwg4hejqc.png" alt="Creating a PAT" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also run the agent as a service, for Linux hosts, you would run these commands from the agent's install directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./svc.sh &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./svc.sh start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the agent is installed and running, you can begin using it in your pipelines. The agent can be selected from the &lt;em&gt;Job&lt;/em&gt; settings inside a release.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohkltzlea99kxw2rxz2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohkltzlea99kxw2rxz2s.png" alt="Selecting an agent" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that there might be additional configuration needed on the server for the deployments to be successful. You may have to add a &lt;code&gt;.env&lt;/code&gt; file and change permissions on the deploy folder. An example of this would be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TARGET_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/your_project

&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:www-data &lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;find &lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;644 &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;find &lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;755 &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt;

&lt;span class="nb"&gt;sudo chgrp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data storage bootstrap/cache
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; ug+rwx storage bootstrap/cache

&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt;/.env
&lt;span class="c"&gt;# fill your .env file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Microsoft-hosted agent
&lt;/h3&gt;

&lt;p&gt;If you are deploying to a managed hosting service via FTP or you need to deploy via SCP/SFTP to the target server, you can use Microsoft-hosted agents. Using these in the release pipelines also counts towards the monthly build-minutes limit.&lt;/p&gt;

&lt;p&gt;If your application is hosted in Azure AppService, there are dedicated tasks for example the &lt;em&gt;Azure App Service deploy&lt;/em&gt; task which uses a service connection to Azure to deploy your application to the AppService.&lt;/p&gt;

&lt;p&gt;In any case, the pipeline would then have only one task, which would probably be the &lt;em&gt;FTP Upload&lt;/em&gt; or the &lt;em&gt;App Service deploy&lt;/em&gt; task. In case of deploying to managed hosting service via FTP, it would look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1iao7m4a63nqcorkkkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1iao7m4a63nqcorkkkb.png" alt="Basic pipeline for FTP uploads" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that in the aforementioned case, you would also need to set up the &lt;code&gt;$(Password)&lt;/code&gt; secret variable.&lt;/p&gt;

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

&lt;p&gt;In this article, I went through the steps to set up CI/CD pipelines for a Laravel application in Azure DevOps. The advantages and possible disadvantages of Azure DevOps as a CI/CD and project management tool were clarified.&lt;/p&gt;

&lt;p&gt;For reference, you can find the complete Azure DevOps project &lt;a href="https://dev.azure.com/cclil/public" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and the repo is located &lt;a href="https://github.com/Loupeznik/laravel-azure-devops-sample" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading...&lt;/p&gt;

</description>
      <category>php</category>
      <category>backend</category>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Introducing ingoreinit - A CLI tool for creating gitignores</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Sun, 23 Oct 2022 16:26:00 +0000</pubDate>
      <link>https://dev.to/loupeznik/introducing-ingoreinit-a-cli-tool-for-creating-gitignores-48f6</link>
      <guid>https://dev.to/loupeznik/introducing-ingoreinit-a-cli-tool-for-creating-gitignores-48f6</guid>
      <description>&lt;p&gt;Let's talk generating .gitignore files - a file every git repo should have. Some tools for scaffolding projects like &lt;code&gt;create-tauri-app&lt;/code&gt; or &lt;code&gt;composer create-project&lt;/code&gt; initialize .gitignore for you. But what about applications which do not use these? Like when generating new solution in Visual Studio or creating a new Go app. Many developers tend to manually copy the ignore files from their previous projects or go to &lt;a href="https://github.com/github/gitignore"&gt;github/gitignore&lt;/a&gt; to get them. In my opinion, this is very tedious and can be achieved in a simpler and faster way - via the CLI.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://github.com/Loupeznik/ignoreinit"&gt;&lt;strong&gt;ignoreinit&lt;/strong&gt;&lt;/a&gt; comes in. &lt;/p&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;I originally wanted to create a tool like this mainly for the aformentioned reasons - it is faster and more comfortable to get your .gitignore from the command line. And as somebody who is always working on new projects, I wanted to simplify my workflow as much as possible.&lt;/p&gt;

&lt;p&gt;The tool itself is written in Go, leveraging a library called &lt;a href="https://github.com/google/go-github"&gt;&lt;code&gt;go-github&lt;/code&gt;&lt;/a&gt; to fetch gitignores. In the first iteration, it can either download a new gitignore file based on selected language and framework (for now it has to be one of the gitignore files present in the aformentioned &lt;code&gt;github/gitignore&lt;/code&gt; repo) or replace existing gitignore by specifying new language and path of the ignore to be replaced.&lt;/p&gt;

&lt;p&gt;The flow is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;ignoreinit&lt;/em&gt; parses the CLI parameters&lt;/li&gt;
&lt;li&gt;It fetches the list of all available gitignores from the repo&lt;/li&gt;
&lt;li&gt;Determines whether a .gitignore is available for selected language or framework&lt;/li&gt;
&lt;li&gt;If it is, &lt;em&gt;ignoreinit&lt;/em&gt; downloads the ignore file and creates .gitignore with its contents in the selected directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow is pretty simple. As is the main function which handles the fetching itself, I will include a snippet at the end of the article.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to get and use ignoreinit
&lt;/h3&gt;

&lt;p&gt;If you find the tool interesting, it can currently be installed in three ways. If you have Go already installed on your machine, you could just do &lt;code&gt;go install github.com/loupeznik/ignoreinit@latest&lt;/code&gt;, this should automatically install it and add it to your &lt;code&gt;$PATH&lt;/code&gt; (if you have path to Go binaries in your &lt;code&gt;$PATH&lt;/code&gt; already). Cloning the repo and building the binary yourself is also a possibility. &lt;/p&gt;

&lt;p&gt;If you are Go-less, you can always get it from the &lt;a href="https://github.com/Loupeznik/ignoreinit/releases"&gt;Releases&lt;/a&gt; page of the &lt;a href="https://github.com/Loupeznik/ignoreinit"&gt;Github repo&lt;/a&gt;. Binaries for Linux, Windows and MacOS are available. Using the last method, you might want to add the binary to your &lt;code&gt;$PATH&lt;/code&gt; (e.g. moving it for example to &lt;code&gt;/usr/local/bin&lt;/code&gt; on Linux or appending the folder which the executable resides in to your &lt;code&gt;PATH&lt;/code&gt; on Windows).&lt;/p&gt;

&lt;p&gt;Usage is pretty straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ignoreinit &amp;lt;init|replace&amp;gt; &amp;lt;language&amp;gt; &amp;lt;location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initialize a new .gitignore for Go in current directory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ignoreinit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Initialize a new .gitignore for Python in different directory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ignoreinit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Dev\my-flask-api&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Replace gitignore in current directory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ignoreinit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion and future development
&lt;/h2&gt;

&lt;p&gt;This article briefly introduced the &lt;a href="https://github.com/Loupeznik/ignoreinit"&gt;&lt;strong&gt;ignoreinit&lt;/strong&gt;&lt;/a&gt; CLI tool for generating .gitignore files conveniently from the commandline. In the future, I would like to enhance the tool to also support .dockerignore files, have more .gitignore repositories available and have the tool available in package managers like snap, apt or chocolatey for smoother installation experience.&lt;/p&gt;

&lt;p&gt;If you've made it this far, thanks for reading. Until next time...&lt;/p&gt;

&lt;p&gt;P.S. the aformentioned snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getIgnore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isNew&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RepositoryContentGetOptions&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;directoryContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repositories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gitOwner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gitRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;directoryContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EqualFold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not find .gitignore for %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repositories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DownloadContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gitOwner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gitRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;
    &lt;span class="n"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".gitignore"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isNew&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDWR&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_TRUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cover image credit to &lt;a href="https://unsplash.com/@6heinz3r?utm_source=unsplash&amp;amp;utm_medium=referral"&gt;Gabriel Heinzer&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/gitignore?utm_source=unsplash&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article was originally posted over at &lt;a href="https://blog.dzarsky.eu/introducing-ingoreinit-a-cli-tool-for-creating-gitignores"&gt;blog.dzarsky.eu&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>tooling</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Creating a Time-Triggered AWS Lambda Function</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Fri, 06 Aug 2021 13:28:16 +0000</pubDate>
      <link>https://dev.to/loupeznik/creating-a-time-triggered-aws-lambda-function-1dl3</link>
      <guid>https://dev.to/loupeznik/creating-a-time-triggered-aws-lambda-function-1dl3</guid>
      <description>&lt;p&gt;This article will cover basic steps for creating a time-triggered Lambda function. This can be useful if you want to check for something periodically (for example once per hour). You might want to check if your server is up, fetch data from an API and compare them to previously collected values and so on. The possibilities are endless with a serverless platform like Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is AWS Lambda
&lt;/h2&gt;

&lt;p&gt;Let's briefly touch upon what Lambda actually is and how you can access it. Lambda is a serverless platform offered by Amazon Web Services (AWS). By using Lambda, you eliminate the need to provision, set up and administer your own server, you just upload the scripts you want to run to Lambda and the AWS backend takes care of everything related to actually running your script. This is especially useful if you have small scripts which don't run long and provisioning a server just to run them wouldn't make much sense. With Lambda, you pay only for the resources/time said script consumes, as opposed to running it on a dedicated EC2 instance.&lt;/p&gt;

&lt;p&gt;As part of the &lt;em&gt;Always Free&lt;/em&gt; tier of AWS, you get 1 million requests per month for free on Lambda, which is pretty good. Accessing Lambda is easy, you may do it from the AWS Console or with a command line, for this article I'm going to be using the AWS Console as it is more of a straight-forward approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the function
&lt;/h2&gt;

&lt;p&gt;For the sake of simplicity, let's assume we want to create a function which gets values of Bitcoin, Ethereum and Dogecoin from the &lt;a href="https://messari.io"&gt;messari.io&lt;/a&gt; API every hour. It would also be nice to save these to a database. Let's also assume we have an instance of MongoDB up and running somewhere. An S3 bucket on AWS or any other database can also be used, you may also just log the values without storing them, that should be enough as a proof of concept.&lt;/p&gt;

&lt;p&gt;I will be creating this function in Go, it is also possible to create Lambda functions in Python, .NET Core, Java, Ruby or NodeJS.&lt;/p&gt;

&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;

&lt;p&gt;First things first, let's get the code done. For creating a function in Go, you'll need the &lt;code&gt;aws-lambda-go&lt;/code&gt; package, you can get it by invoking &lt;code&gt;go get github.com/aws/aws-lambda-go/lambda&lt;/code&gt; from the command line. The function will probably work even without this library, but even if your script completes successfully, you'll get errors reported by Lambda (trust me, I've tried).&lt;/p&gt;

&lt;p&gt;After that, it's just a matter of importing the rest of the libraries needed to make this work and implement the functionality we want. The main function's code would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/aws/aws-lambda-go/lambda"&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="s"&gt;"github.com/bozd4g/go-http-client"&lt;/span&gt;
    &lt;span class="s"&gt;"go.mongodb.org/mongo-driver/mongo"&lt;/span&gt;
    &lt;span class="s"&gt;"go.mongodb.org/mongo-driver/mongo/options"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Coin&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"timestamp"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s"&gt;`json:"status"`&lt;/span&gt;
    &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Symbol&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"symbol"`&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name"`&lt;/span&gt;
        &lt;span class="n"&gt;MarketData&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="kt"&gt;float32&lt;/span&gt; &lt;span class="s"&gt;`json:"price_usd"`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s"&gt;`json:"market_data"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s"&gt;`json:"data"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;assets&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"btc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"eth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"doge"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;httpClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://data.messari.io/api/v1/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mongo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MONGO_URI"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"assets/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/metrics?fields=symbol,name,market_data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Coin&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"coins"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"coins"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InsertOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not save values for "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Saved data for "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Symbol&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="s"&gt;" as "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InsertedID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it isn't apparent, I'll shortly explain what the code does. In the &lt;em&gt;handle()&lt;/em&gt; function, it initializes the HTTP client for fetching the API data and the MongoDB client for storing it. It then fetches the data from the API, serializes it into the Coin struct which defines our structure of a cryptocurrency (or at least the data we need to fetch/store), then it stores the data in the database and logs the result. The &lt;em&gt;main()&lt;/em&gt; function calls the &lt;em&gt;handle()&lt;/em&gt; function and does it's Lambda stuff, pretty straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note that  an environment variable is being used to store the MongoDB connection string, this is important and will come into play when setting the function up in AWS Lambda control panel.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Now that we are done with the code, let's go to the AWS Console and into Lambda. A new function can be set up here. Let's click &lt;em&gt;Create function&lt;/em&gt;, select the &lt;em&gt;Go&lt;/em&gt; runtime and let's name it &lt;em&gt;CryptoRateFetcher&lt;/em&gt;. Then click Create function again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YdEQCvHE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628016386282/ByXJ0v-hC.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YdEQCvHE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628016386282/ByXJ0v-hC.png" alt="Creating the function" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should land at your function's dashboard. Here you can upload your scripts, select triggers and events and configure your function.&lt;/p&gt;

&lt;p&gt;First things first, let's build the script, upload it and set the MongoDB URI as a environment variable. You will need to build a Linux binary, as Lambda can only execute these. If you're on Windows, you can use WSL or Docker to create the binary. In the project's directory, we'll need to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 go build &lt;span class="nt"&gt;-o&lt;/span&gt; main &lt;span class="nb"&gt;.&lt;/span&gt;
zip main.zip main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, I'll upload the &lt;em&gt;.zip&lt;/em&gt; directly to Lambda, uploading it to S3 and running the function from there is also possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--elQCaY_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628016935307/vp9NLqip7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--elQCaY_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628016935307/vp9NLqip7.png" alt="Function dashboard" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above shows the function dashboard where you can upload the zip. Note that the &lt;em&gt;Handler&lt;/em&gt; is set to &lt;em&gt;hello&lt;/em&gt;, this is a default for some reason and you will need to change it to &lt;em&gt;main&lt;/em&gt; or however is your main function called. After uploading the code, head over to the &lt;em&gt;Configuration&lt;/em&gt; tab and select &lt;em&gt;Environment Variables&lt;/em&gt;. There you can add the &lt;code&gt;MONGO_URI&lt;/code&gt; variable which will be consumed by the script on execution. You can now save the function by clicking &lt;em&gt;Actions&lt;/em&gt; -&amp;gt; &lt;em&gt;Publish new version&lt;/em&gt; in the upper right corner of the dashboard. You can now test the function in the &lt;em&gt;Test&lt;/em&gt; tab, which should yield a success, if everything has been set up correctly. The output might look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tPCgYlH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628017472956/JvArLRu9H.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tPCgYlH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628017472956/JvArLRu9H.png" alt="Test result" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trigger
&lt;/h3&gt;

&lt;p&gt;Now that we know our function is working, let's set the timed trigger to start our function every hour. Back on the main function dashboard, hit &lt;em&gt;Add trigger&lt;/em&gt; on the left, a dropdown should appear. Choose &lt;em&gt;Event Bridge&lt;/em&gt; and create a new &lt;em&gt;Rule&lt;/em&gt;. A new form where you can configure your event should appear. Select &lt;em&gt;Schedule expression&lt;/em&gt; as your &lt;em&gt;Rule type&lt;/em&gt; and configure it accordingly. You can use regular CRON syntax or use &lt;em&gt;rates&lt;/em&gt;. After that, click Add and you're pretty much finished.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KrX0qOq1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628017878893/7XKejjH76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KrX0qOq1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628017878893/7XKejjH76.png" alt="Event Bridge configuration" width="800" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last thing to do is to wait for the results it yields. As you can see in the picture below, over the last several hours, we've fetched and stored multiple Bitcoin values, with the time interval being indeed one hour per record.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UN9ZnWMq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628087769683/tnbf0Co0R.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UN9ZnWMq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628087769683/tnbf0Co0R.png" alt="The result" width="800" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;In this article, I've demonstrated an easy way to deploy a rather simple script to Amazon Web Services' serverless platform AWS Lambda. I've also touched upon what Lambda actually is and what pros there are to use it with AWS Always Free tier. A way to set up a timed trigger to start the script in a certain interval was shown.&lt;/p&gt;

&lt;p&gt;The examples in this article were very simple, indeed, and you can run much more complex scripts or programs in Lambda, there are also options to invoke the function via REST API which were not touched upon in this article, but I may come back to them on a later date. The example script shown in this article will be available in this &lt;a href="https://github.com/Loupeznik/lambda-crypto-fetcher"&gt;repo&lt;/a&gt; over at my GitHub. If you found this article interesting, you might also want to check out my other Lambda function written in Go which checks if array of user-defined hosts (webservers) are up, in this &lt;a href="https://github.com/Loupeznik/ServerStatusChecker"&gt;repo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Thanks for reading, until next time...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally published on my personal &lt;a href="https://blog.dzarsky.eu/creating-a-time-triggered-aws-lambda-function"&gt;blog&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>go</category>
    </item>
    <item>
      <title>Deploying a Laravel application to GCP with Bitnami LAMP</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Sat, 03 Jul 2021 12:57:27 +0000</pubDate>
      <link>https://dev.to/loupeznik/deploying-a-laravel-application-to-gcp-with-bitnami-lamp-1e14</link>
      <guid>https://dev.to/loupeznik/deploying-a-laravel-application-to-gcp-with-bitnami-lamp-1e14</guid>
      <description>&lt;p&gt;In this article, I will be going through the process of Laravel application deployment, namely to Google Cloud Platform (GCP). This article will cover the basics of GCP as a Cloud Service Provider, the steps of setting up a new Virtual Machine via the Google Cloud Console and deploying your application onto it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Cloud Platform introduction
&lt;/h2&gt;

&lt;p&gt;First, let's talk about what the GCP is and what it offers. Google Cloud Platform is a cloud service provider run by Google and is very similar to other cloud service providers like AWS or Oracle Cloud Infrastructure. It offers services like the Compute Engine where you can deploy virtual machines, various tiers of Cloud Storage which offers AWS S3-like storage buckets, variety of serverless solutions and so on. GCP also offers a generous free tier, with &lt;strong&gt;$300&lt;/strong&gt; in free credits you may use in your first 3 months on any service, as well as forever free services beyond this trial period. These include for example 1 VM (F-1 micro only in US datacenters), 5GB of Cloud Storage or Firebase functionality (up to monthly limits). You can read more about the free tier &lt;a href="https://cloud.google.com/free"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To get started with the GCP, you'll need a Google account. You will also need to register an account with the GCP. The registration process is fairly easy and includes filling various information about yourself, such as billing information, as you might be accustomed to when setting up an account with any Cloud Service Provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Virtual Machine
&lt;/h2&gt;

&lt;p&gt;To spin up a new Virtual Machine, head over to the &lt;a href="https://console.cloud.google.com"&gt;Cloud Console&lt;/a&gt;, then to Compute Engine and select &lt;em&gt;Create Instance&lt;/em&gt;. For deploying any PHP application to the cloud (be it Laravel, Symfony or any framework-independent application), I tend to use the &lt;em&gt;Bitnami LAMP&lt;/em&gt; cloud images which can, fortunately, be found on AWS, GCP and Azure marketplaces. I have very good experience with these images, as they tend to be secure and have everything you'd need for hosting a PHP web application already preinstalled. You can find out more about these &lt;a href="https://bitnami.com/stack/lamp/cloud"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;Create Instance&lt;/em&gt; dialog, several image options pop up on the left-hand side of the screen. We'll go to &lt;em&gt;Marketplace&lt;/em&gt; and search for &lt;em&gt;LAMP Certified by Bitnami&lt;/em&gt; image. A listing for &lt;em&gt;LAMP Certified by Bitnami&lt;/em&gt; should appear. Upon clicking the listed image, you'll be redirected to the image's description screen. Once there, click &lt;em&gt;Launch&lt;/em&gt;, this takes you back to the Virtual Machine configuration screen as seen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QMLPo_wW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625213523950/l1wFRqWBl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QMLPo_wW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625213523950/l1wFRqWBl.png" alt="VM Configuration Screen" width="800" height="986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may set machine parameters here, the main ones to focus on are the &lt;strong&gt;Deployment Zone&lt;/strong&gt;, &lt;strong&gt;Machine Type&lt;/strong&gt; and &lt;strong&gt;Disk Size&lt;/strong&gt;. As you can see in the screenshot above, I've chosen the &lt;em&gt;F-1 micro&lt;/em&gt; type as it is powerful enough for the purpose of this showcase, if you're running a larger application, be sure to check machine types with more RAM and CPU. &lt;strong&gt;Just a reminder - the F-1 micro instances as part of the forever free tier are applicable only in selected US regions&lt;/strong&gt; (us-east-1, us-west-1 and us-central-1 as to my knowledge), if you deploy these anywhere else, you &lt;strong&gt;will&lt;/strong&gt; be charged.&lt;/p&gt;

&lt;p&gt;As to other settings, you can allow traffic on ports 80 and 443 on this screen or you can do it later by adding firewall rules to this machine. For convenience, it is better to tick the &lt;em&gt;Allow HTTP/HTTPS traffic&lt;/em&gt; options from this screen directly. Port 22 should also be allowed by default. After you're finished configuring the instance, accept the terms and click &lt;em&gt;Deploy&lt;/em&gt;. The deployment might take a little while, this would depend on your selected datacenter, machine type, and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing the Virtual Machine
&lt;/h2&gt;

&lt;p&gt;After the machine is successfully deployed, you should arrive on the following screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZypklADg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625214328679/igPj7qJBA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZypklADg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625214328679/igPj7qJBA.png" alt="After deployment screen" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, you might want to take a note of the &lt;em&gt;IP address&lt;/em&gt; and the &lt;em&gt;admin password&lt;/em&gt;. Accessing the VM from here is simple, just click &lt;em&gt;SSH&lt;/em&gt; underneath the basic machine information table, a new browser window should pop up and you should be connected to the VM via SSH. You can also connect to the VM via another SSH client like &lt;em&gt;Termius&lt;/em&gt; or with &lt;em&gt;gcloud CLI&lt;/em&gt;, instructions for these methods should be available in the attached documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the VM
&lt;/h2&gt;

&lt;p&gt;As we have all needed software like PHP, Apache 2, MariaDB (MySQL), Composer and Git already installed, we'll just need to set these up properly to get our application running. The only thing you might need is NodeJS/NPM as most of Laravel applications make use of NPM to install JavaScript packages like Laravel Mix. To install NodeJS fast and easy, we'll make use of &lt;a href="https://github.com/nodesource/distributions#deb"&gt;nodesource&lt;/a&gt;. Bitnami LAMP currently runs Debian 10, thus we'll import a Debian NodeJS 16.x source and install it via &lt;em&gt;apt&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_16.x | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Database
&lt;/h3&gt;

&lt;p&gt;Let's set up a database first, we'll make use of the admin password we've been given. The following flow will create a database and a user for your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dominik_zarsky@lampstack-2-vm:/home/bitnami&lt;span class="nv"&gt;$ &lt;/span&gt;mysql &lt;span class="nt"&gt;-uroot&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
Enter password: &amp;lt;your_admin_password_here&amp;gt;
Welcome to the MariaDB monitor.  Commands end with &lt;span class="p"&gt;;&lt;/span&gt; or &lt;span class="se"&gt;\g&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Your MariaDB connection &lt;span class="nb"&gt;id &lt;/span&gt;is 8
Server version: 10.3.29-MariaDB Source distribution
Copyright &lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="o"&gt;)&lt;/span&gt; 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type &lt;span class="s1"&gt;'help;'&lt;/span&gt; or &lt;span class="s1"&gt;'\h'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;help. Type &lt;span class="s1"&gt;'\c'&lt;/span&gt; to clear the current input statement.
MariaDB &lt;span class="o"&gt;[(&lt;/span&gt;none&lt;span class="o"&gt;)]&amp;gt;&lt;/span&gt; CREATE DATABASE laravel&lt;span class="p"&gt;;&lt;/span&gt;
Query OK, 1 row affected &lt;span class="o"&gt;(&lt;/span&gt;0.021 sec&lt;span class="o"&gt;)&lt;/span&gt;
MariaDB &lt;span class="o"&gt;[(&lt;/span&gt;none&lt;span class="o"&gt;)]&amp;gt;&lt;/span&gt; CREATE USER &lt;span class="s1"&gt;'laravel'&lt;/span&gt;@&lt;span class="s1"&gt;'localhost'&lt;/span&gt; IDENTIFIED BY &lt;span class="s1"&gt;'test123'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
Query OK, 0 rows affected &lt;span class="o"&gt;(&lt;/span&gt;0.001 sec&lt;span class="o"&gt;)&lt;/span&gt;
MariaDB &lt;span class="o"&gt;[(&lt;/span&gt;none&lt;span class="o"&gt;)]&amp;gt;&lt;/span&gt; GRANT ALL PRIVILEGES ON laravel.&lt;span class="k"&gt;*&lt;/span&gt; TO &lt;span class="s1"&gt;'laravel'&lt;/span&gt;@&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
Query OK, 0 rows affected &lt;span class="o"&gt;(&lt;/span&gt;0.015 sec&lt;span class="o"&gt;)&lt;/span&gt;
MariaDB &lt;span class="o"&gt;[(&lt;/span&gt;none&lt;span class="o"&gt;)]&amp;gt;&lt;/span&gt; FLUSH PRIVILEGES&lt;span class="p"&gt;;&lt;/span&gt;
Query OK, 0 rows affected &lt;span class="o"&gt;(&lt;/span&gt;0.017 sec&lt;span class="o"&gt;)&lt;/span&gt;
MariaDB &lt;span class="o"&gt;[(&lt;/span&gt;none&lt;span class="o"&gt;)]&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
Bye
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Application
&lt;/h3&gt;

&lt;p&gt;For the purpose of this showcase, I'll clone the &lt;a href="https://github.com/rappasoft/laravel-boilerplate"&gt;Laravel Boilerplate&lt;/a&gt; application from GitHub, as I presume you'd be installing your own the same way. If you're not using Git, you may want to use SCP to copy your application's files over to the VM (you'd need to download the machine's SSH keys for that, instructions for this could be found &lt;a href="https://docs.bitnami.com/google/faq/get-started/connect-ssh/"&gt;in this documentation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Let's hop on over to &lt;em&gt;/opt/bitnami&lt;/em&gt; which is the default directory for all preinstalled Bitnami software and files. It is customary to create a &lt;em&gt;projects&lt;/em&gt; directory here, this will be the directory where all custom web applications would be placed. The following commands can be used as a general flow for setting up the whole application. If you encounter permissions problem, execute the following commands as &lt;em&gt;sudo&lt;/em&gt; or change the permissions to the &lt;em&gt;projects&lt;/em&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/bitnami/
&lt;span class="nb"&gt;mkdir &lt;/span&gt;projects &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;projects
git clone https://github.com/rappasoft/laravel-boilerplate
&lt;span class="nb"&gt;cd &lt;/span&gt;laravel-boilerplace
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
composer &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run prod
php artisan key generate
php artisan storage:link
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; daemon:daemon /opt/bitnami/projects/laravel-boilerplate/storage
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; daemon:daemon /opt/bitnami/projects/laravel-boilerplate/bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll need to update the .env file's database settings for the database we've created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;....
# Database
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=test123
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can migrate and seed the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate
php artisan db:seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Server
&lt;/h3&gt;

&lt;p&gt;Now the application is all set up, the only thing that is left to be done is to set up the Apache 2 webserver itself. For that, we'll need to set up a new &lt;em&gt;Virtual Host&lt;/em&gt;, we can achieve this by going to the &lt;em&gt;Apache2&lt;/em&gt; configuration directory and creating a new VHOST entry. Let's create a &lt;em&gt;laravel-boilerplate-vhost.conf&lt;/em&gt; in &lt;em&gt;/opt/bitnami/apache2/conf/vhosts&lt;/em&gt; directory. You might do this with Vim or nano. The vhost needs to be configured like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;VirtualHost 127.0.0.1:80 _default_:80&amp;gt;
    ServerAlias *
    # ServerName yourdomain.com
    DocumentRoot /opt/bitnami/projects/laravel-boilerplate/public
    &amp;lt;Directory "/opt/bitnami/projects/laravel-boilerplate/public"&amp;gt;
      Options -Indexes +FollowSymLinks -MultiViews
      AllowOverride All
      Require all granted
    &amp;lt;/Directory&amp;gt;
  &amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To apply the changes, we'll restart the Apache 2 webserver by running&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo /opt/bitnami/ctlscript.sh restart apache&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  It works
&lt;/h2&gt;

&lt;p&gt;After restarting Apache 2, head over to the VM's IP, you should see your application up and running, just like the sample Laravel Boilerplate application shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r9OLMO5o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625218066351/12odiqNG9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r9OLMO5o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625218066351/12odiqNG9.png" alt="Deployed application" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Up next, you might want to set up a static IP for your instance, you can do that from the &lt;em&gt;VPC network&lt;/em&gt; section under &lt;em&gt;Networking&lt;/em&gt; in the left-hand side menu. In that section, choose the &lt;em&gt;External IP addresses&lt;/em&gt; tab. You should see the following screen, where you can change the IP type from &lt;em&gt;ephemeral&lt;/em&gt; to &lt;em&gt;static&lt;/em&gt;. That way, the IP won't change over time and you can happily assign a domain name to this machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i6EkOYJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625218600381/aK-dmqDQf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i6EkOYJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1625218600381/aK-dmqDQf.png" alt="External IP addresses" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to get a free &lt;em&gt;Let's Encrypt&lt;/em&gt; SSL certificate, the Bitnami LAMP image is equipped with the &lt;em&gt;bncert&lt;/em&gt; tool which makes it easy to generate and configure an SSL certificate for your website. You can run the bncert tool with&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;sudo /opt/bitnami/bncert-tool&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;You can learn more about it in &lt;a href="https://docs.bitnami.com/general/how-to/understand-bncert/"&gt;Bitnami's documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;In this article, I've summarized the use-cases for the Google Cloud Platform, then a way to spin up a Bitnami LAMP Virtual Machine instance on the GCP by using the Google Cloud Console was shown. After that, we've set the instance up to a state when we could deploy and publish a sample Laravel application on it, this included setting up a MySQL database, the application itself and an Apache 2 web server. I hope this article helped some of you with deploying your application to the GCP. If not, you may also want to check out the &lt;a href="https://cloud.google.com/compute/docs"&gt;GCP VM Documentation&lt;/a&gt; and &lt;a href="https://docs.bitnami.com/google/infrastructure/lamp/"&gt;Bitnami documentation&lt;/a&gt;. Until next time...&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>laravel</category>
      <category>lamp</category>
      <category>bitnami</category>
    </item>
    <item>
      <title>How To Create Multiple Laravel Models With A Single Command</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Thu, 01 Apr 2021 14:47:08 +0000</pubDate>
      <link>https://dev.to/loupeznik/how-to-create-multiple-laravel-models-with-a-single-command-1n2a</link>
      <guid>https://dev.to/loupeznik/how-to-create-multiple-laravel-models-with-a-single-command-1n2a</guid>
      <description>&lt;p&gt;Have you ever encountered the need to create multiple artisan objects, like Eloquent models, controllers or Livewire components at one moment? When starting a new project, this is a very likely situation. Solving it by running command after command like &lt;em&gt;php artisan make:model $NAME -msc&lt;/em&gt; over and over again isn't a particularly good idea, in my opinion, as it takes a lot of time. As far as I know, there aren't any command-line tools to remediate this problem, so I decided to make one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solving the problem
&lt;/h1&gt;

&lt;p&gt;The obvious solution to this is to take a OS-native shell, like Bash for Linux or Powershell for Windows (although you can use PowerShell on Linux as well), and write a script to automate the tedious task of running the aforementioned artisan commands by hand. As I was on Windows at the time of getting the idea of this script, I'm going to present the solution to the problem in Powershell. Note that it can also be easily converted to a Linux shell script and I'm surely going to do that some time in the future. &lt;/p&gt;

&lt;p&gt;This implementation of the script contains a solution to batch Eloquent model creation with additional (optional) parameters like migrations, controllers, seeders and factories connected to the model. Let's take a look at the final script:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The full version of this script is also available at my &lt;a href="https://github.com/Loupeznik/laravel-system-helpers"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# artisan-model-generator.ps1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".\artisan"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PathType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR: Artisan not found in this directory"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="bp"&gt;$input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Read-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Prompt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enter model names separated by commas"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="bp"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR: No model names entered"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enter switches to create additional classes (like -msfc)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Read-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Prompt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enter the desired switches"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WARNING: No switch selected"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-notcontains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-notmatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[mscf]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR: The switch can contain only [mscf] characters"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="bp"&gt;$input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'\s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'\s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;","&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$models&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Creating model &lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;artisan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;make:model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$switch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet above is a simplified version of the script you can get in the GitHub repo linked above it.&lt;/p&gt;

&lt;h1&gt;
  
  
  What the script does
&lt;/h1&gt;

&lt;p&gt;The script works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It checks if the &lt;em&gt;artisan&lt;/em&gt; script is present, if it isn't, the script exits&lt;/li&gt;
&lt;li&gt;It prompts the user for input, in this case, the names of the models the user wants to create&lt;/li&gt;
&lt;li&gt;If the input is valid, it prompts the user for additional components to create&lt;/li&gt;
&lt;li&gt;If input is empty, only the model is created, otherwise, the script takes the entered input as artisan make:model switches. It also checks if the entered switches are valid&lt;/li&gt;
&lt;li&gt;After this, white spaces are removed from the input strings and the &lt;em&gt;models&lt;/em&gt; array is created from the first input string (model names)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After these steps are complete, the script starts to generate our models with the desired names and switches. The output of the command would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS C:\Users\domin\Documents\www\laravel-test&amp;gt; .\artisan-models.ps1
Artisan model generator
Enter model names separated by commas: Person, Article, Item
Enter the desired switches: ms
Creating model Person
Model created successfully.
Created Migration: 2021_03_31_112410_create_people_table
Seeder created successfully.
Creating model Article
Model created successfully.
Created Migration: 2021_03_31_112527_create_articles_table
Seeder created successfully.
Creating model Item
Model created successfully.
Created Migration: 2021_03_31_112533_create_items_table
Seeder created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the snippet above, we're creating 3 models - Person, Article and Item. With these, we are also creating a migration and a seeder. The artisan command &lt;em&gt;php artisan make:model $ITEM -ms&lt;/em&gt; is run by the script and we've saved some time. It's a win-win situation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;This has been just a quick article about a script I've made a couple of days back, just to show you how easy it can be to batch generate a bunch of Eloquent models. Of course, this script can be taken a bit further, maybe with better exception handling or by expanding it beyond just generating models. If you'd like to expand upon this basis, feel free to fork the aforementioned &lt;a href="https://github.com/Loupeznik/laravel-system-helpers"&gt;repo&lt;/a&gt; and submit a pull request.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>artisan</category>
      <category>powershell</category>
    </item>
    <item>
      <title>Setting Up API Call Limits In Laravel - Part 2</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Wed, 24 Mar 2021 16:46:30 +0000</pubDate>
      <link>https://dev.to/loupeznik/setting-up-api-call-limits-in-laravel-part-2-5ef</link>
      <guid>https://dev.to/loupeznik/setting-up-api-call-limits-in-laravel-part-2-5ef</guid>
      <description>&lt;p&gt;In this second part of my API request limiting series, I'll dive into building a custom middleware for tracking user's requests to our API endpoints and setting limits according to the user's privileges. As a reminder to the previous part, we're building a public API for our users to fetch data, these users are split into various subscription tiers and for some tiers, we need to limit their access to the API over time. Now that we're all caught up, let's get started.&lt;/p&gt;

&lt;h1&gt;
  
  
  How does this work
&lt;/h1&gt;

&lt;p&gt;For this to work properly, we're going to need to take into account three parts of our application - the API, the database and the middleware. This method is going to be slightly different than the one used in the previous article, as we are going to store information about the user's request to the database, rather than doing the limiting under the hood using a rate limiter. This way, we'll have more information about the request for future use, as you'll see at the end of the article.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up the database
&lt;/h1&gt;

&lt;p&gt;First of all, we need to set up a database table and Laravel model for our requests. We'll achieve this with one artisan command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:model Request --migration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the &lt;em&gt;Request&lt;/em&gt; model and the &lt;em&gt;create_requests_table&lt;/em&gt; migration. In my database, I have columns for request id, API token via which a given endpoint was called, the id of the user that called that endpoint, date and time of the request and the URI the user called.  The token and the user id reference the same row in the users table, so the second one is kinda redundant, I didn't want to address this problem when I was creating the API though, so we'll go with what we've got. The migration function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public function up()
    {
        Schema::create('requests', function (Blueprint $table) {
            $table-&amp;gt;id();
            $table-&amp;gt;string('token');
            $table-&amp;gt;integer('user_id');
            $table-&amp;gt;datetime('date');
            $table-&amp;gt;string('content');
            $table-&amp;gt;foreign('user_id')
                       -&amp;gt;references('id')
                       -&amp;gt;on('users');
            $table-&amp;gt;foreign('token')
                       -&amp;gt;references('api_token')
                       -&amp;gt;on('users');
        });
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the model, it is pretty straightforward. We're disabling timestamps because the rows in the table will not be updated and we've already supplied the request datetime in the &lt;em&gt;date&lt;/em&gt; column. We're also making every column except &lt;em&gt;id&lt;/em&gt; fillable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Models/Request.php

&amp;lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Request extends Model
{

    public $timestamps = false;

    protected $fillable = [
        'token',
        'user_id',
        'date',
        'content'
    ];
}

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Setting up the middleware
&lt;/h1&gt;

&lt;p&gt;Now that we have the database layer set up, we can go ahead and create the middleware. We're going to be referencing the &lt;em&gt;User&lt;/em&gt; and &lt;em&gt;Request&lt;/em&gt; models as the request is tied to the user who is calling the API via their API token. As in the previous part, we are using bearer tokens to authenticate users. To continue the subscription-based public API model we've discussed in the &lt;a href="https://blog.dzarsky.eu/setting-up-api-call-limits-in-laravel-part-1" rel="noopener noreferrer"&gt;last article&lt;/a&gt;, we'll set request limits to certain groups of users. This time, free-tier users are limited to one request to the API per day, subscribed users can make unlimited number of requests. Let's see the middleware first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Http/Middleware/AccessLevel.php

&amp;lt;?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Carbon;
use App\Models\Request as ApiRequest;

class AccessLevel
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {

        $token = $request-&amp;gt;bearerToken();
        if (User::where('api_token', $token)-&amp;gt;exists()) {
            $user = User::where('api_token', $token)-&amp;gt;first();
        } else {
            return response()
                -&amp;gt;json(['error' =&amp;gt; 'Bad token'], 401);
        }

        if ($user-&amp;gt;access == 0) {
            if ($this-&amp;gt;checkLimit($token, today())) {
                return response()
                -&amp;gt;json(['error' =&amp;gt; 'Daily call limit exceeded'], 429);
            }
        }
        ApiRequest::create([
            'token' =&amp;gt; $token,
            'user_id' =&amp;gt; $user-&amp;gt;id,
            'date' =&amp;gt; Carbon::now(),
            'content' =&amp;gt; $request-&amp;gt;url()
        ]);
        return $next($request);

    }

    private function checkLimit($token, $date)
    {

        if (ApiRequest::where('token', $token)
            -&amp;gt;where('date', 'like', $date-&amp;gt;format('Y-m-d') . '%')
            -&amp;gt;exists()) {
                return true;
            } else {
                return false;
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;There is much going on in this middleware, let's digest it a little bit. The request goes to a given API route and the middleware fires up. From the request, a bearer token is extracted and checked against the users table. If a user with given token exists, the middleware registers the user into a variable and continues to do other checks, however if a user with this token doesn't exist, the middleware returns a 401. When this check is complete, the middleware checks the user's &lt;em&gt;Access Level&lt;/em&gt;. If the user is a free-tier user, the &lt;em&gt;checkLimit()&lt;/em&gt; method fires up and checks if a user has made an API call today. The check is of course done via the &lt;em&gt;requests&lt;/em&gt; table. If the user has made a request today, the middleware returns a &lt;em&gt;Too many requests&lt;/em&gt; error.&lt;/p&gt;

&lt;p&gt;If no error was triggered, the request is stored in the database for future reference (either for the middleware or for statistical reasons).&lt;/p&gt;

&lt;p&gt;As usual, we need to register this new middleware in &lt;em&gt;app/Http/Kernel&lt;/em&gt;. You should register it in both the &lt;em&gt;api&lt;/em&gt; middleware group and &lt;em&gt;$routeMiddleware&lt;/em&gt;. If you do not want this restriction to fire up on all of your API routes, register it only in the &lt;em&gt;$routeMiddleware&lt;/em&gt; array. I have mine registered like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Http/Kernel.php

&amp;lt;?php

...
protected $middlewareGroups = [
    'web' =&amp;gt; [
        ...
    ],
    'api' =&amp;gt; [
        \App\Http\Middleware\ForceJson::class,
        \App\Http\Middleware\AccessLevel::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

protected $routeMiddleware = [
    ...
    'access.level' =&amp;gt; \App\Http\Middleware\AccessLevel::class,
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you choose not to register the middleware in the &lt;em&gt;api&lt;/em&gt; middleware group, you will also need to register it in your &lt;em&gt;routes/api.php&lt;/em&gt; file. Let's test the middleware to make sure it works...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1616408438477%2FuMavneNr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1616408438477%2FuMavneNr7.png" alt="Test for a free-tier user"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the picture above, we've only managed to get one request through before getting the &lt;em&gt;Too many requests&lt;/em&gt; error when we supplied a non-subscribed user's token, &lt;strong&gt;thus fulfilling our goal&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the collected data
&lt;/h1&gt;

&lt;p&gt;As I've used this technique to limit API requests for users on my own site - &lt;a href="https://www.onlyresultz.com" rel="noopener noreferrer"&gt;OnlyResultz&lt;/a&gt;, I wanted to display relevant API usage metrics to my users via the user's dashboard. For that use case, I displayed only daily, monthly and lifetime requests, but more ways to use the collected data exist, for your users and your application's administrators alike. An example of such functionality in Blade can be found in this &lt;a href="https://gist.github.com/Loupeznik/4f232445f0cc520b784af027a14b9f7d" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And the end result looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1616424889615%2FG3t1vjYHF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1616424889615%2FG3t1vjYHF.png" alt="Example metrics"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the blade file above, we are supplying the &lt;em&gt;$requests&lt;/em&gt; variable, which uses the &lt;em&gt;Request&lt;/em&gt; model to fetch relevant requests for the user. The individual metrics are processed inside the blade file and served to the user in a readable form.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;This has been it for the &lt;em&gt;Setting Up API Call Limits In Laravel&lt;/em&gt; series. Check out the previous article in the series if you haven't yet. In the other article, I explain how to use Laravel's built-in Rate Limiter to achieve similar result to that which we've seen in this article.&lt;/p&gt;

&lt;p&gt;Until next time.&lt;/p&gt;

&lt;p&gt;Cover image by &lt;a href="https://pixabay.com/users/mohamed_hassan-5229782/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=3967926" rel="noopener noreferrer"&gt;Mohamed Hassan&lt;/a&gt; from &lt;a href="https://pixabay.com/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=3967926" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>api</category>
      <category>php</category>
      <category>rest</category>
    </item>
    <item>
      <title>Building an Anonymized Website Visitor Counter in Laravel</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Mon, 15 Mar 2021 18:23:38 +0000</pubDate>
      <link>https://dev.to/loupeznik/building-an-anonymized-website-visitor-counter-in-laravel-4o6a</link>
      <guid>https://dev.to/loupeznik/building-an-anonymized-website-visitor-counter-in-laravel-4o6a</guid>
      <description>&lt;p&gt;In this day and age, we all need to have some sort of idea of our website's performance and the least we can do for this is to somehow track our audience - the visitors of our webpage. This can be done by using a third-party service like Google Analytics, Cloudflare or other services. These, unfortunately, expose our visitors to cookies and come in with a whole set of challenges pertaining to GDPR and other privacy-regulating laws in the EU and also other countries of the world. Thought this approach might be the easiest one when it comes to implementing such functionality into your website, you might have to deal with the legal side of things (like privacy policy, cookie consent, and more) on top of it. If you are already using cookies in some capacity other than for example session cookies or XSFR tokens (like in basic Laravel apps), you may not have a problem with this, but if you want to be free of these third-party cookies, I've got another option for you.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Concept
&lt;/h1&gt;

&lt;p&gt;The concept of this approach to visitor tracking is fairly simple. The goal is to have an anonymized record of unique visits of our website for each day. I'm going to show an example of this in Laravel, but this concept can be taken to other frameworks and languages. I've tested this in regular PHP, it can be also done in .NET or Python (with some modifications of course).&lt;/p&gt;

&lt;p&gt;The main thing that makes this method more privacy-compliant than using those third-party tools is anonymization. Almost no data about the user is being collected on the server-side. We only collect the date of the visit, and the visitor's IP address which is then &lt;strong&gt;hashed and only the hash&lt;/strong&gt; is stored in the database.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Execution
&lt;/h1&gt;

&lt;p&gt;As simple as the concept was, the execution is keeping it simple as well. For this to work, we are going to need to write a custom middleware which determines if a visitor has already been recorded for a given day. If the visitor hasn't been recorded, it inserts their visit into the database, otherwise, the visit is not registered. The middleware checks the visitor's IP hash against the hash in the database to determine if the visit had been recorded before. Now let's look at the code of this middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Http/Middleware/CountVisitor.php

&amp;lt;?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\Visitor;

class CountVisitor
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $ip = hash('sha512', $request-&amp;gt;ip());
        if (Visitor::where('date', today())-&amp;gt;where('ip', $ip)-&amp;gt;count() &amp;lt; 1)
        {
            Visitor::create([
                'date' =&amp;gt; today(),
                'ip' =&amp;gt; $ip,
            ]);
        }
        return $next($request);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is pretty straightforward and does exactly what has already been described at the start of this part of the article. We are only inserting today's date and SHA-512 hashed IP address of the visitor into the database. You could hash it with another hashing algorithm like bcrypt or SHA-256, but I went for SHA-512 in this case, though they all are fairly safe ways to hash the data.&lt;/p&gt;

&lt;p&gt;Of course, the middleware also needs to be registered in &lt;em&gt;app/Http/Kernel&lt;/em&gt; as a route middleware. I've registered it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Http/Kernel.php

...
protected $routeMiddleware = [
    ...,
    'visitor' =&amp;gt; \App\Http\Middleware\CountVisitor::class
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can add this to your web routes as a middleware. Alternatively, you might want to apply it to all web routes, in that case, you would register this middleware in the web middleware group in &lt;em&gt;app/Http/Kernel&lt;/em&gt;. I usually don't want this to fire up on administrative routes though, so I use this middleware only for publicly accessible pages. The routes then looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// routes/web.php

&amp;lt;?php

use Illuminate\Support\Facades\Route;

Route::name('front.')-&amp;gt;middleware('visitor')-&amp;gt;group(function() {
    Route::get('/', 'FrontpageController@index')-&amp;gt;name('index');
    ...
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Visualizing the collected data
&lt;/h1&gt;

&lt;p&gt;For this article, I'm not going to go into detail about visualizing the data we've collected as that could be a whole separate article. I'm just going to leave a link to a GitHub gist containing the code needed to visualize the data. The final result can be seen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HNfJpEK6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1615637361730/nhdCcVlxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HNfJpEK6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1615637361730/nhdCcVlxj.png" alt="hashnode_visits.png" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/Loupeznik/5fe0e3fff362bfd46dabda82c782ff00"&gt;Here is the source&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The pros and cons
&lt;/h1&gt;

&lt;p&gt;This method has several advantages and disadvantages, let's explore them here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pros
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anonymized data&lt;/li&gt;
&lt;li&gt;Simple solution&lt;/li&gt;
&lt;li&gt;Simple implementation&lt;/li&gt;
&lt;li&gt;First party&lt;/li&gt;
&lt;li&gt;No cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The collected data doesn't reflect the visitor's behavior on the website (e.g. time spent on the webpage, visited pages, etc.)&lt;/li&gt;
&lt;li&gt;You have to write your own front-end to visualize the data&lt;/li&gt;
&lt;li&gt;The database size might get quite big if you have a lot of traffic and the queries might get slow over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are more or less the biggest advantages and disadvantages of using this approach.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;This has been a showcase of a simple way of counting your website visitation by using a Laravel middleware. Note that this has only been tested on applications with about 50 unique visitors per day which I consider a small sample size. I am not very sure how viable would this approach be for a bigger project, but if you want to give it a fair try, go right ahead.&lt;/p&gt;

&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@kmuza?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Carlos Muza&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/graph?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>analytics</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Setting Up API Call Limits In Laravel - Part 1</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Tue, 09 Mar 2021 16:53:40 +0000</pubDate>
      <link>https://dev.to/loupeznik/setting-up-api-call-limits-in-laravel-part-1-2min</link>
      <guid>https://dev.to/loupeznik/setting-up-api-call-limits-in-laravel-part-1-2min</guid>
      <description>&lt;p&gt;In this two part series of articles, I'll go through two ways to set up a request limit for your API based on the calling user's access level. This is extremely useful for any subscription-based API where you want to limit user groups to a specific number of API calls per a certain amount of time.&lt;/p&gt;

&lt;p&gt;This part is going to be focused on using Laravel's built in Rate limiters, the second one will dive into creating a custom middleware to enforce the set limits.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using a Rate Limiter
&lt;/h1&gt;

&lt;p&gt;The simplest way to achieve this goal is to set up a rate limiting service which is built into Laravel and enabled by default for API middleware-restriced routes. The default limit is set to &lt;strong&gt;60 requests&lt;/strong&gt; per minute. The configuration can be changed in &lt;em&gt;/app/Providers/RouteServiceProvider&lt;/em&gt; in the &lt;em&gt;configureRateLimiting()&lt;/em&gt; method. This is the default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60);
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wanted to disable the default limit (which I wouldn't recommend as you would allow users to make unlimited requests to your API endpoints which may lead to overloads), you may do &lt;em&gt;return Limit::none();&lt;/em&gt; in this function, or you could remove this throttle from the &lt;em&gt;api&lt;/em&gt; middleware group in &lt;em&gt;/app/Http/Kernel&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining a custom rate limiter
&lt;/h2&gt;

&lt;p&gt;In the &lt;em&gt;configureRateLimiting()&lt;/em&gt; method, you can define more rate limiters. Let's make a limiter called &lt;em&gt;matches&lt;/em&gt; which is going to limit calls to API route for fetching results from various football matches.&lt;/p&gt;

&lt;p&gt;For this use case, let's say we have 3 types of users, an unregistered visitor, a registered user with a free account and a registered user who has subscribed to our service. The first one isn't registered, thus doesn't have an API token, therefore they cannot access our API, the second one has an API access token, but their request limit is one request per hour, the subscribed user can make unlimited number of calls to the API. We will be using the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access Level - Category the user belongs to, 0 is a free account user, 1 is a subscribed user. This is represented by the &lt;em&gt;access&lt;/em&gt; column of the &lt;em&gt;users&lt;/em&gt; table in the database&lt;/li&gt;
&lt;li&gt;Bearer Token - API token type for request authentication which is represented by the &lt;em&gt;api_token&lt;/em&gt; column of the &lt;em&gt;users&lt;/em&gt; table in the database, every user has a unique token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's put it all together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//RouteServiceProvider.php
protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60);
    });

    RateLimiter::for('matches', function (Request $request) {
      $user = User::where('api_token', $request-&amp;gt;bearerToken())
              -&amp;gt;first();
      switch ($user-&amp;gt;access) {
        case 0:
          return Limit::perHour(1)-&amp;gt;response(function () {
            return response()-&amp;gt;json(
              ['error' =&amp;gt; 'Request limit exceeded'],
              429
            );
          });
          break;
        case 1:
          return Limit::none();
          break;
      }
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//routes/api.php
Route::middleware(['auth:api','json.force','throttle:matches'])
-&amp;gt;get('test', 'ApiController@testMatches');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have successfully set our own limit, we can disable the original &lt;em&gt;api&lt;/em&gt; rate limiter, as our subscribed users would still be limited by it, as you can see below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1614881058458%2F7ZJI_ENy9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1614881058458%2F7ZJI_ENy9.gif" alt="api_test.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The script used for testing the API can be found &lt;a href="https://gist.github.com/Loupeznik/10558006bfa3a14237c88d349aee1202" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To achieve this, we can simply set the API rate limiter to no limit, alternatively, we could disable the &lt;em&gt;throttle:api&lt;/em&gt; in the &lt;em&gt;api&lt;/em&gt; middleware group in &lt;em&gt;/app/Http/Kernel&lt;/em&gt; altogether. &lt;/p&gt;

&lt;h1&gt;
  
  
  Next up
&lt;/h1&gt;

&lt;p&gt;Next time, I will be showing you how I tackled the request rate limiting problem myself the first time I encountered the need to limit the access to my API. This option is a little bit more tricky than the one I've shown you today, as it involves creating a set of custom middleware and working with the database a little more.&lt;/p&gt;

&lt;p&gt;To be continued...&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>api</category>
      <category>php</category>
    </item>
    <item>
      <title>How To Backup Your VS Code Extensions And Settings</title>
      <dc:creator>Dominik Zarsky</dc:creator>
      <pubDate>Tue, 02 Mar 2021 17:14:11 +0000</pubDate>
      <link>https://dev.to/loupeznik/how-to-backup-your-vs-code-extensions-and-settings-4cji</link>
      <guid>https://dev.to/loupeznik/how-to-backup-your-vs-code-extensions-and-settings-4cji</guid>
      <description>&lt;p&gt;Backup your Visual Studio Code extension list and configuration files using Linux terminal.&lt;/p&gt;

&lt;p&gt;Having the same working environment across multiple machines is always a good thing. This might be a case for your system configuration files (dotfiles) and your development tools alike. What I'm going to show you is a simple script that can help with the task of backing up your Visual Studio Code settings and extensions for use on your secondary PC, virtual environment or even your computer at work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up the backup
&lt;/h1&gt;

&lt;p&gt;For this article, I'm going to suppose that you have VS Code already installed on your system and want to backup the settings and extensions you've spent some of your precious time setting up. We'll be using the old trusty Linux terminal magic and VS Code's command line utility &lt;em&gt;code&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Our basic command for exporting the list of extensions is going to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;code --list-extensions | xargs -L 1 echo code --install-extension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;code --list-extensions&lt;/em&gt; command exports the list of installed VS Code extensions which we then pipe into xargs, this transforms each extension's name into an extension install command. The output of this command looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;code --install-extension anseki.vscode-color
code --install-extension autsenc.laravel-blade-spacer
code --install-extension Dart-Code.flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see we've successfully transformed the extension names to installation commands via xargs. You can output these into a file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir /home/$USER/code-backup &amp;amp;&amp;amp; cd /home/$USER/code-backup
code --list-extensions | xargs -L 1 echo code --install-extension &amp;gt; ext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we also need to export the configuration files. These usually reside in your .config directory. The config files are saved in JSON format, so to back these up, you would simply tar them and save them somewhere in the cloud. Alternatively, you could push these into a Git repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /home/$USER/.config/Code/User
tar -czf code-config.tar.gz *.json
mv code-config.tar.gz /home/$USER/code-backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your code-backup folder, you should have your config files in a tar archive and your extension install script as &lt;em&gt;ext&lt;/em&gt;. If you want to restore these on another machine, you'd just copy the code-backup directory's contents over and reverse the process shown above (that is, untar the config files into VS Code config directory and run the &lt;em&gt;ext&lt;/em&gt; script)&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;This was just a basic example of backing up your files. If you don't fancy writing backup scripts, you can check out my own full and a little enhanced implementation of this backup workflow over &lt;a href="https://github.com/Loupeznik/utils/blob/master/backup_utils/vscode-backup.sh"&gt;here&lt;/a&gt;. There is also a VS Code extension for synchronizing your settings called &lt;a href="https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync"&gt;Settings Sync&lt;/a&gt; that synchronizes everything for you into a GitHub Gist.&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>backup</category>
      <category>bash</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
