<?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: atymic</title>
    <description>The latest articles on DEV Community by atymic (@atymic).</description>
    <link>https://dev.to/atymic</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%2F306849%2Fb443809a-e83c-4b79-8aaf-698b04977bf0.png</url>
      <title>DEV Community: atymic</title>
      <link>https://dev.to/atymic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/atymic"/>
    <language>en</language>
    <item>
      <title>Running Laravel on DigitalOcean's App Platform</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Sun, 11 Oct 2020 09:59:37 +0000</pubDate>
      <link>https://dev.to/atymic/running-laravel-on-digitalocean-s-app-platform-fi5</link>
      <guid>https://dev.to/atymic/running-laravel-on-digitalocean-s-app-platform-fi5</guid>
      <description>&lt;p&gt;Today DigitalOcean App Platform entered general availability and they offer support for PHP/Laravel apps out of the box. &lt;br&gt;
Read on for a deep dive and explore the pros and cons of DigitalOcean's new app platform with me.&lt;/p&gt;

&lt;p&gt;If you've used Heroku in the past you'll be right at home on the App Platform. It actually uses heroku buildbacks under the hood, making it super easy to get an app running with no or minimal configuration (or, so they say).&lt;/p&gt;

&lt;p&gt;Just want the Review / TL;DR? Skip the Deep Dive and read the Review.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying your first App
&lt;/h2&gt;

&lt;p&gt;Head over the &lt;code&gt;Apps&lt;/code&gt; tab on the DigitalOcean dashboard. You'll need to link your GitHub account (they don't support gitlab or any other providers currently). I like that you can give access to only the repos actually required, instead of granting account wide permissions like most other services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JrJL09lL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/tYbZssW.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JrJL09lL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/tYbZssW.png" alt="linking with GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you'd configured the GitHub integration you can start deploying your project. For the purposes of this demo, I scaffolded a Laravel 8 application with Intertia Jetstream, as it encompasses all of the moving parts in a normal Laravel app (Backend, plus building the javascript with Mix). I selected my demo app and hit &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next up, we need to give the app a name and select the region. I've kept the default name (repo name) and selected &lt;code&gt;New York&lt;/code&gt;. Currently, they offer 3 regions being New York, Amsterdam and Frankfurt. &lt;/p&gt;

&lt;p&gt;This isn't ideal for me as these locations are all very far away from Australia, but I'm sure they'll launch more regions in the future. I've also selected the &lt;code&gt;Auto Deploy Code Changes&lt;/code&gt; option for continuous deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---CKjrBJr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/uYcmu0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---CKjrBJr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/uYcmu0r.png" alt="Creating the DigitalOcean App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen we can configure the service. It seems that we can't actually pick the buildpack here (was guessed from the repo code). We can also set environment variables (what you'd put in your &lt;code&gt;.env&lt;/code&gt; normally).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JpRYLMAR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/QlfeRIU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JpRYLMAR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/QlfeRIU.png" alt="Configuring the DigitalOcean App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've also added a development managed database (Postgres is the only option). It's a bit pricey at $7/m for a 256mb dev instance and 1GB of disk. You can also link an existing Managed Database (supports both pgsql and mysql, from $15/m).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x-c_dCeV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/PTY432z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x-c_dCeV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/PTY432z.png" alt="Linking a Database"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we've got the app configured we're presented with the plan selection page. I've picked the $5/m 512MB option here. See the "Review" section below for my thoughts on the pricing. Total cost for our dev setup is $12/m.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fE1WN_fO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/aqRZXne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fE1WN_fO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/aqRZXne.png" alt="Reviewing the DigitalOcean App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After hitting &lt;code&gt;Launch Basic App&lt;/code&gt; the platform gets started building right away. For this app, it took about 10 minutes to provision the app containers and the database. You can watch a live tail of the build logs under the &lt;code&gt;Deployments&lt;/code&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uIUZfZud--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/KixKsXy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uIUZfZud--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/KixKsXy.png" alt="Building the App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the initial deployment finished I jumped on over to the automatically provisioned URL. My first deployment was &lt;code&gt;500&lt;/code&gt;ing because I forgot to set the database driver to &lt;code&gt;pgsql&lt;/code&gt;, but after updating the environment variables (which automatically redeploys the app) the app was able to connect to the DB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wl7WQ4gc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/3quOQol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wl7WQ4gc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/3quOQol.png" alt="Updating the Env Vars"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that was finished, I jumped into the &lt;code&gt;Console&lt;/code&gt; tab to run the migrations. This was super easy, so props to them for&lt;br&gt;
the awesome console. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K-Hdat4z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/5qNksXN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K-Hdat4z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/5qNksXN.png" alt="Executing commands in the console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whoohoo! We're live, and I can see the splash page. Bad news is we've got an issue. The build pack didn't build the javascript, so our InertiaJS app doesn't work.&lt;/p&gt;

&lt;p&gt;I had a look through the docs, but it isn't immediately clear how to get the platform to build the javascript. I tried setting the &lt;code&gt;Build Command&lt;/code&gt; to &lt;code&gt;yarn install &amp;amp;&amp;amp; yarn prod&lt;/code&gt;, &lt;del&gt;but that didn't work because yarn isn't installed&lt;/del&gt;. &lt;/p&gt;

&lt;p&gt;Update: Turns out I didn't commit the &lt;code&gt;yarn.lock&lt;/code&gt; file (which is used to detect what build packs to load). It worked fine once I committed it. It would be good if the documentation around this was a bit better (someone from the DO team did reach out to me via twitter which was awesome but I still couldn't find it in the docs).&lt;/p&gt;

&lt;p&gt;I guess this is also where we'd do stuff like running migrations, so it would probably make sense to extract these steps into a bash script and execute that instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NI5j7t8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hYGdKN2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NI5j7t8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hYGdKN2.png" alt="Setting the build command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Onto the next issue! Our javascript and CSS is now built, but Laravel thinks we're running on &lt;code&gt;http&lt;/code&gt;, so it's generating &lt;code&gt;http&lt;/code&gt; urls, which our browser is refusing to load because the app is being served over &lt;code&gt;https&lt;/code&gt;. I tried setting the &lt;code&gt;APP_URL&lt;/code&gt; to be prefixed with &lt;code&gt;https://&lt;/code&gt;, but this didn't solve the issue. I'm guessing this is due to the internal load balancer configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LJCJTtD7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/iBy3RB2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LJCJTtD7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/iBy3RB2.png" alt="HTTP error"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To fix this, I change the &lt;code&gt;AppServiceProvider&lt;/code&gt; to force HTTPS in production, which fixed the issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;boot&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'production'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forceScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https'&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;h2&gt;
  
  
  Adding a Queue Worker
&lt;/h2&gt;

&lt;p&gt;Another pretty common task is running a queue worker (either using &lt;code&gt;queue:work&lt;/code&gt; or horizon). To do this, we need another separate worker container. Unfortunately this means an extra $5-12/m (depending on if you're on the basic or pro plans), since there's no way to reuse the existing resources.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;Components&lt;/code&gt; tab, hit &lt;code&gt;Create Component&lt;/code&gt; and select the &lt;code&gt;Worker&lt;/code&gt; option. You'll need to select the git repository and pick a branch (this will be the same for Laravel apps) as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nm6qCjgH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/AmXPGOr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nm6qCjgH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/AmXPGOr.png" alt="Add a worker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you need to set the run command that will be executed. In this case, it's &lt;code&gt;php artisan queue:work&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G4zXUftw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/r3M2cT1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G4zXUftw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/r3M2cT1.png" alt="Configure a worker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's worth noting that if one of your containers is on the &lt;code&gt;Pro&lt;/code&gt; plan, every future container needs to be as well. This is pretty annoying since you may be happy with a small, cheap queue worker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ISrtSf9f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/JTmezC1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ISrtSf9f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/JTmezC1.png" alt="Pricing for a worker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Success, we're live 🎉
&lt;/h2&gt;

&lt;p&gt;All in all, it took about an hour for this first exploratory deployment. If I was doing it again, it would probably take me around 15 minutes, which is super quick to get a fully featured app running in production (with horizontal scaling!).&lt;/p&gt;

&lt;p&gt;On top of that, we also get Zero Downtime Deployments out of the box as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review + Pros/Cons
&lt;/h2&gt;

&lt;p&gt;At a high level, I really like DigitalOcean's App Platform, and it's awesome to have a Heroku alternative in the space. It's way, way cheaper than Heroku ($5/m vs $25/m for the base plan, more savings for the higher plans). It's easy to approach for beginners, but still suitable for advanced users.&lt;/p&gt;

&lt;p&gt;Here's what I'd say an average service would require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2x 1GB Web Containers - $12m * 2 = $24/m&lt;/li&gt;
&lt;li&gt;1x 1GB Managed Database - $15/m&lt;/li&gt;
&lt;li&gt;1x 1GB Queue Worker Container - $12/m&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At ~ $50/month, it's not super cheap but consider the savings in time (no need for devops, managing servers, etc) I think it's a pretty great deal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Much cheaper than Heroku&lt;/li&gt;
&lt;li&gt;Zero downtime deployments&lt;/li&gt;
&lt;li&gt;Built in CI/CD&lt;/li&gt;
&lt;li&gt;Built in Horizontal scaling (no autoscaling, but it's coming)&lt;/li&gt;
&lt;li&gt;Awesome console for managing the app&lt;/li&gt;
&lt;li&gt;Built in managed databases (though, a bit expensive)&lt;/li&gt;
&lt;li&gt;Built in Cloudflare CDN&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tiny included outbound bandwidth (40gb on the &lt;code&gt;Basic&lt;/code&gt; plans, 100GB on &lt;code&gt;Professional&lt;/code&gt; plans)&lt;/li&gt;
&lt;li&gt;Super expensive bandwidth overage (10c per GiB, that's more than AWS 😬, and they offer 1TB bandwith on $5/m droplets)&lt;/li&gt;
&lt;li&gt;Documentation is a bit lacking (though, it's a very new offering)&lt;/li&gt;
&lt;li&gt;No VPCs for isolating apps&lt;/li&gt;
&lt;li&gt;No wildcard subdomains&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It's awesome to see competition, and nothing is perfect on launch. I'm excited to see the DigitalOcean App Platform improve, and hopefully they address some of the cons/concerns (which seem to be shared by a lot of people).&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this post and helped you to evaluate the App Platform for your projects.&lt;/p&gt;

&lt;p&gt;If you have any questions, leave a comment below &amp;amp; I'll do my best to respond to them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/docs/app-platform/"&gt;DigitalOcean App Platform Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/pricing/"&gt;DigitalOcean App Platform Pricing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/docs/app-platform/references/app-specification-reference/"&gt;App Platform Spec Docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>laravel</category>
      <category>php</category>
      <category>docker</category>
    </item>
    <item>
      <title>MySQL Performance Tuning - Speeding up slow inserts</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Wed, 10 Jun 2020 11:17:45 +0000</pubDate>
      <link>https://dev.to/atymic/mysql-performance-tuning-speeding-up-slow-inserts-3afb</link>
      <guid>https://dev.to/atymic/mysql-performance-tuning-speeding-up-slow-inserts-3afb</guid>
      <description>&lt;p&gt;If you're running a write heavy application with a MySQL database you might notice a fairly significant slowdown as the write load on the server increases. In our case, we're running an event sourcing based service which is writing incoming events to read tables. Under heavy write load, simple inserts and updates with small payloads were pushing the 250ms mark. Here's how you can speed up those slow writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a server backed by performant SSD storage
&lt;/h2&gt;

&lt;p&gt;MySQL (with default settings) writes the log buffer to disk after every transaction. Under high load, this causes a ton of small writes which are particularly slow on traditional platter based hard drives. &lt;/p&gt;

&lt;p&gt;In our case, we had undersized the Amazon RDS instance disk size,  &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html"&gt;which has a direct impact on write performance&lt;/a&gt;  (higher capacity means more provisioned IOPS/write bandwidth). Increasing the disk size dropped out write average down about 50ms.&lt;/p&gt;

&lt;p&gt;TL;DR: Make sure the disk for your server is as fast as possible&lt;/p&gt;

&lt;h2&gt;
  
  
  Increase the log file size limit
&lt;/h2&gt;

&lt;p&gt;The default &lt;code&gt;innodb_log_file_size&lt;/code&gt; limit is set to just &lt;code&gt;128M&lt;/code&gt;, which isn't great for insert heavy environments. Increasing this to something larger, like &lt;code&gt;500M&lt;/code&gt; will reduce log flushes (which are slow, as you're writing to the disk). This is  particularly important if you're inserting large payloads.&lt;/p&gt;

&lt;p&gt;In our case, bumping the log file size didn't actually help us much, since we had many smaller reads (not enough to overflow the log file frequently).&lt;/p&gt;

&lt;h2&gt;
  
  
  Defer disk writes by flushing the log less frequently
&lt;/h2&gt;

&lt;p&gt;By default MySQL will write the log to disk after every single transaction. In our case, it wasn't possible to wrap batches of inserts in transactions, so every single insert query was causing a disk write. &lt;/p&gt;

&lt;p&gt;You can instead write the log to disk at an interval by setting &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;. The main issue with setting this away from the default of &lt;code&gt;1&lt;/code&gt; is possible data loss. While you're still protected from MySQL crashes causing data loss, the entire server loosing power would mean potentially losing data. For example, if you have the flush interval set to the default of &lt;code&gt;1&lt;/code&gt;, you could lose a seconds worth of writes upon failure of the server.&lt;/p&gt;

&lt;p&gt;By setting &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;, we were able to drop the insert time average from over 200ms to under 50ms. While this is a pretty massive improvement, it's possible to squeeze even more speed if you are willing to risk a  few more seconds of potential data loss.&lt;/p&gt;

&lt;p&gt;You can increase the flush interval by setting &lt;code&gt;innodb_flush_log_at_timeout&lt;/code&gt; to your desired interval (in seconds). In our case, we went with 5 seconds, which resulted in the insert time average dropping under 5ms! A massive difference from the original 250ms, and in our case worth the risk of 5 seconds of potential data loss.&lt;/p&gt;

</description>
      <category>php</category>
      <category>database</category>
      <category>python</category>
      <category>aws</category>
    </item>
    <item>
      <title>Reverse engineering Google Reminders for fun and profit</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Sun, 07 Jun 2020 08:52:55 +0000</pubDate>
      <link>https://dev.to/atymic/reverse-engineering-google-reminders-for-fun-and-profit-3f23</link>
      <guid>https://dev.to/atymic/reverse-engineering-google-reminders-for-fun-and-profit-3f23</guid>
      <description>&lt;p&gt;If you've got a Google device you've probably used google reminders before. It's built into a lot of google services including Calendar, Keep, Inbox (💀), Home and others. It's pretty simple, but like many other things in the Google ecosystem, it's absurdly inconsistent. You'll get different options and see different reminders in a completely different UI even though they are all "Google Reminders". &lt;/p&gt;

&lt;p&gt;I use Todoist which is an awesome task management service. Naturally, I'd expected them to have some sort of integration with Google Reminders, but when I checked with their support, they said it wasn't possible as Google Reminders didn't have a public API.&lt;/p&gt;

&lt;p&gt;I decided that I'd build my own tool to sync reminders from Google to Todoist   (&lt;a href="https://todo-sync.atymic.dev/" rel="noopener noreferrer"&gt;todo-sync.atymic.dev&lt;/a&gt; for those interested).   First step, figure out how to query, update &amp;amp; delete google reminders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse Engineering Google's Reminders API
&lt;/h2&gt;

&lt;p&gt;I jumped into Google Calendar with Chrome's dev tools to figure out how the API worked. Usually it's pretty easy to figure out how they work, even without access to the code. I was completely surprised when I saw what looked like a gibberish request and response format. For example, this is a "List" request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"1"&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="nl"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WRP / /WebCalendar/calendar_200531.18_p0"&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="nl"&gt;"2"&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="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"24"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1599314400000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"25"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1583157600000"&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;Checking the &lt;code&gt;Content-Type&lt;/code&gt; header (&lt;code&gt;application/json+protobuf&lt;/code&gt;) tell us this is a protobuf request. Protobuf is typed data transfer protocol, used internally by lots of teams at google. Unfortunately, this made it a lot hard to figure out how we can interact with their APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the Protobuf Definitions
&lt;/h3&gt;

&lt;p&gt;Jumping into the &lt;code&gt;Sources&lt;/code&gt; tab in dev tools, I started looking through the Javascript files for code related to the reminder APIs. This was made more difficult by minified javascript but I managed to find the definitions burried in the code for part of the API Client. Here's what one of the looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="nx"&gt;FSc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FSc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ra&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;TQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ISc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ISc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TQ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ListTasksResponse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;caribou.tasks.service.ListTasksResponse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;zQ&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;continuation_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;continuation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HQ&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;storage_version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response_header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KQ&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skipped_storage_read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Ia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&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;A mess, but clearly we're looking in the right place. I also found some other attempts to re-implement the API which were helpful in figuring the format out. By referencing all the objects in the google calendar javascript we can figure out the format of all the requests.&lt;/p&gt;

&lt;p&gt;For example, when listing reminders in TodoSync, we make the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// include_archived = true&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// limit = 500&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;13&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// RecurrenceListOptions&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// collapse_mode = true&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;I've also created a &lt;a href="https://github.com/atymic/google-reminders-api" rel="noopener noreferrer"&gt;repository in github&lt;/a&gt; documenting how the API works for those interested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building TodoSync
&lt;/h2&gt;

&lt;p&gt;The concept is pretty simple. Poll Google Reminders, and sync those across to Todoist. Naturally, I leaned on Laravel's awesome tooling to quickly get the tool up and running.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://laravel.com/docs/7.x/socialite" rel="noopener noreferrer"&gt;Laravel Socialite&lt;/a&gt; to quick add Oauth authentication for both Google and Todoist. Once you've logged in and set up the sync, a minutely scheduled task runs to sync each user's reminders.   To make this scale with more than a few users, we trigger a background job for each user so they can run asynchronously. &lt;a href="https://laravel.com/docs/7.x/horizon" rel="noopener noreferrer"&gt;Laravel Horzion&lt;/a&gt; manages the workers and provides alerts if there's any issues.&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/.%2Fimages%2Freverse-engineering-google-reminders-for-fun-and-profit%2Freminder.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/.%2Fimages%2Freverse-engineering-google-reminders-for-fun-and-profit%2Freminder.png" alt="Synced Reminder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code is all &lt;a href="https://github.com/atymic/todo-sync" rel="noopener noreferrer"&gt;open source on Github&lt;/a&gt; if you'd like to see how an application like this works, or want to host it yourself!&lt;/p&gt;

&lt;p&gt;Feel free to hit me up with any questions you have and i'll do my best to answer them.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Serverless Cobol</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Tue, 19 May 2020 08:32:25 +0000</pubDate>
      <link>https://dev.to/atymic/serverless-cobol-22dh</link>
      <guid>https://dev.to/atymic/serverless-cobol-22dh</guid>
      <description>&lt;p&gt;I'm currently in the process of writing a post covering how to run Laravel Serverless on Google Cloud. There I was, sitting there hashing out the post and I wrote this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This pretty much means you can run anything that responds to HTTP request, hell you could even run Cobol if you wanted!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, I can't really claim that without actually trying it, can I? Well, an hour or so later and were running serverless cobol!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://serverless-cobol-max7gzuovq-uc.a.run.app/cobol+dev.to=%3C3"&gt;https://serverless-cobol-max7gzuovq-uc.a.run.app/cobol+dev.to=%3C3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was actually surprisingly easy to build a docker container to run cobol, despite it being a 60+ year old language. There's a modern re-implementation called &lt;a href="https://en.wikipedia.org/wiki/GnuCOBOL"&gt;GnuCobol&lt;/a&gt; which support modern OSes like Ubuntu.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://github.com/atymic/serverless-cobol/blob/master/Dockerfile"&gt;dockerfile&lt;/a&gt; which compiles the cobol source code &amp;amp; sets up apache (which interfaces with the cobol program as a CGI script).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:bionic&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;software-properties-common &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;add-apt-repository ppa:lud-janvier/gnucobol
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;gnucobol apache2 &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /var/www/public&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/public&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;cobc &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="nt"&gt;-free&lt;/span&gt; serverless.cbl &lt;span class="nt"&gt;-o&lt;/span&gt; the.app
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./the.app

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/apache.conf /etc/apache2/sites-available/000-default.conf&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;


&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Listen 8080"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/apache2/ports.conf &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/www/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    a2enmod rewrite &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    a2enmod cgid

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Building the container &amp;amp; getting the cobol code to run was the hard part, deploying to Cloud Run is incredibly easy - two command and a few minutes later and we're running cobol in the cloud!&lt;/p&gt;

&lt;p&gt;Is it useful? Definitely not. Was in fun? Sure was 🙃&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/atymic/serverless-cobol"&gt;Source code on Github&lt;/a&gt;.&lt;br&gt;
Now to do some actual work 😬&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>docker</category>
      <category>showdev</category>
      <category>php</category>
    </item>
    <item>
      <title>Building Calndr, a free AddEvent alternative</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Sun, 10 May 2020 02:37:29 +0000</pubDate>
      <link>https://dev.to/atymic/building-calndr-a-free-addevent-alternative-54h</link>
      <guid>https://dev.to/atymic/building-calndr-a-free-addevent-alternative-54h</guid>
      <description>&lt;p&gt;Coronavirus has impacted pretty much every business large and small, and has resulted in a massive surge in online events. Companies that previous provided solely physical services are switching to webinars and online courses, so the demand for tech in this area has spiked.&lt;/p&gt;

&lt;p&gt;In the last few months, I've had multiple different clients come to me with requests to help them schedule events online. There's some existing services that allow you to create add to calendar links out there, such as AddEvent, but they are expensive for what they do - essentially just generating a few links in a certain format (for different calendar software).&lt;/p&gt;

&lt;p&gt;After integrating a simple version into one of my client's applications, I decided to create &lt;a href="https://calndr.link/"&gt;Calndr.link&lt;/a&gt;. It's a super simple service that allows you to generate calendar links for all the major providers in a couple of clicks. Enter the event details (title, location/meeting link, date, etc) and hit generate and you'll be provided with some HTML to copy/paste directly into your newsletter, website, email signature, etc. You can also copy/paste the direct links as well, if you prefer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8dLmG_Od--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DUNVN7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8dLmG_Od--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DUNVN7z.png" alt="Options"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ILVCt9hD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hoZ1T2P.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ILVCt9hD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hoZ1T2P.png" alt="Add to Calendar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech
&lt;/h2&gt;

&lt;p&gt;I decided to have some fun when building this, using Interia.js (totally overkill for moment, but fun!). On the back end, it's running Laravel 7.&lt;/p&gt;

&lt;p&gt;It's deployed on Google Cloud using Cloud Run, so it's running completely serverless. I love the flexibility and ease of use, just whip up a simple docker container, push it to the image registry and hit deploy!&lt;/p&gt;

&lt;p&gt;Since you're only charged for requests that actually hit the server, it's extremely cheap. It's basically Lambda, but you can run anything (since it runs custom docker containers).&lt;/p&gt;

&lt;p&gt;I'm planning on writing a guide on how to deploy Laravel on GCR serverless, so keep an eye out for that!&lt;/p&gt;

&lt;p&gt;Feel free to leave a comment if you've got any questions or suggestions for &lt;a href="https://calndr.link/"&gt;Calndr.link&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>laravel</category>
      <category>php</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Custom Eloquent Casts in Laravel 7</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Sun, 01 Mar 2020 05:20:48 +0000</pubDate>
      <link>https://dev.to/atymic/using-custom-eloquent-casts-in-laravel-7-3pal</link>
      <guid>https://dev.to/atymic/using-custom-eloquent-casts-in-laravel-7-3pal</guid>
      <description>&lt;p&gt;Laravel 7 is pretty much upon us (scheduled for release on 3rd March) and brings a bunch of awesome new features &amp;amp; improvements. One of these ones I'm most looking forward to is &lt;a href="https://github.com/laravel/framework/pull/31035"&gt;Custom Eloquent Casts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Historically you've been limited to the default set of casts provided by Laravel, which cover basic language type plus dates. While there are some existing packages out there implementing custom casting they have a major drawback.  Because they override the &lt;code&gt;setAttribute&lt;/code&gt; and &lt;code&gt;getAttribute&lt;/code&gt; methods through a trait, they can't be used with any other &lt;br&gt;
packages that also override those methods. &lt;/p&gt;

&lt;p&gt;Now that Laravel 7 supports these casts natively there won't be compatibility issues between libraries!&lt;/p&gt;
&lt;h2&gt;
  
  
  How custom eloquent casts work
&lt;/h2&gt;

&lt;p&gt;Any object implementing the new &lt;code&gt;CastsAttributes&lt;/code&gt; contract provided by Laravel can now be used in the &lt;code&gt;$casts&lt;/code&gt; property on your model. When accessing properties on your model, Eloquent will first check if there's a custom cast to transform the value with before returning the value to you. &lt;/p&gt;

&lt;p&gt;Keep in mind that your cast will be called on &lt;strong&gt;every single get and set&lt;br&gt;
operation on the model&lt;/strong&gt;, so consider caching intensive operations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating Custom Casts
&lt;/h2&gt;

&lt;p&gt;A popular feature suggestion for Laravel has been allowing selective encryption of model properties, and with custom &lt;br&gt;
casts this is super simple to implement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Illuminate\Contracts\Database\Eloquent\CastsAttributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EncryptCast&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CastsAttributes&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&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;In your model, you can then assign a property to the cast we just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestModel&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The attributes that should be cast to native types.
     *
     * @var array
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$casts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'secret'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;EncryptCast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;class&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;Now that we've set it up, let's test it out! Because the &lt;code&gt;encrypt&lt;/code&gt;/&lt;code&gt;decrypt&lt;/code&gt; functions serialize input, you can store &lt;br&gt;
pretty much anything (but you should probably stick to the simple built-in types).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TestModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Hello World'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Plain text value&lt;/span&gt;

&lt;span class="c1"&gt;// Encrypted value (which will be saved to the database)&lt;/span&gt;
&lt;span class="c1"&gt;// Raw Value: eyJpdiI6InV4Q25ZN0FZUW5YSEZkRCtZSGlVXC9BPT0iLCJ2Y...&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getAttributes&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;'secret'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Save &amp;amp; reload the model from the DB&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Hello World&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Because custom casts are just objects, they can be as simple or complex as required. There's a couple of additional features in the implementation, including "Inbound" casts (that only cast set values) and the ability to accept config from the deceleration in the model's &lt;code&gt;$casts&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For example, we could limit strings to a configurable length, but only when setting values (therefore any existing values would remain the same length).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LimitCaster&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CastsInboundAttributes&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;limit&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="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;length&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;In the model, separate the class name of the cast and the parameters with a colon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestModel&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$casts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'limited_str'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;LimitCaster&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;':10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Limit to 10 characters&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;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;I'm really excited to see some of the awesome casts the community comes up with once Laravel 7 is out. It's super easy to publish custom casts as in packages as well, so hopefully we'll see a bunch of packages proving custom casts in the future.&lt;/p&gt;

&lt;p&gt;One of the ones I've missed on previous projects is a &lt;code&gt;DateInterval&lt;/code&gt; or &lt;code&gt;CarbonInterval&lt;/code&gt; cast, so I've &lt;a href="https://github.com/atymic/laravel-dateinterval-cast"&gt;published a package&lt;/a&gt;&lt;br&gt;
in case anyone else has come across the same problem.&lt;/p&gt;

&lt;p&gt;Got any questions? Feel free to comment below and I'll do my best to answer them!&lt;/p&gt;

&lt;h3&gt;
  
  
  Further Reading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/laravel/framework/pull/31035"&gt;Framework PR&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/atymic/laravel-dateinterval-cast"&gt;DateInterval Cast PR&lt;/a&gt;  &lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>database</category>
    </item>
    <item>
      <title>Submitting sitemaps on deploy with Netlify</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Thu, 16 Jan 2020 09:15:49 +0000</pubDate>
      <link>https://dev.to/atymic/submitting-sitemaps-on-deploy-with-netlify-4kgn</link>
      <guid>https://dev.to/atymic/submitting-sitemaps-on-deploy-with-netlify-4kgn</guid>
      <description>&lt;p&gt;There's plenty of static site generators out there, and most support generating sitemaps. To make sure google indexes&lt;br&gt;
your pages as quickly as possible, it's a good idea to ping them with your sitemap when it's updated.&lt;/p&gt;

&lt;p&gt;Netlify provides some hooks which can be used to run functions once the deploy is complete. This allows us to ping&lt;br&gt;
google when production is deployed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/atymic/netlify-submit-sitemap" rel="noopener noreferrer"&gt;Github Repo with Code&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Submitting Site Maps Automatically
&lt;/h2&gt;

&lt;p&gt;Copy the &lt;code&gt;functions/deploy-succeeded.js&lt;/code&gt; file to your project. Keep in mind you can't change the name, otherwise&lt;br&gt;
Netlify won't detect it as a hook.&lt;/p&gt;

&lt;p&gt;Next, update your &lt;code&gt;netlify.toml&lt;/code&gt; to define your functions folder. This is where the compiled functions will be stored, and can&lt;br&gt;
be named anything (in this example, we use &lt;code&gt;lambda&lt;/code&gt;). These files should be committed to your VCS. &lt;br&gt;
Alternatively, you can update your build script to build them in CI (in which case, you can gitignore them),&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[build]
  publish = "dist"
  command = "yarn build"
  functions = "lambda" # netlify-lambda reads this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also want to install &lt;code&gt;netlify-lambda&lt;/code&gt; and &lt;code&gt;axios&lt;/code&gt; and compile the function (this bundles the dependencies &amp;amp; code into a single file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add netlify-lambda
./node_modules/.bin/netlify-lambda build functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, configure the &lt;code&gt;SITEMAP_URL&lt;/code&gt; environment variable inside Netlify's build settings, like so:&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/.%2Fimages%2Fzeit-now-dns-management%2Fenvs.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/.%2Fimages%2Fzeit-now-dns-management%2Fenvs.png" alt="Cofigure Enviroment Vars"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure your static site generator is updating the sitemap on each build.&lt;/p&gt;

&lt;p&gt;If all went well, on your next deploy Netlify will run the hook, and alert google that your sitemap has changed&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;10:14:21 AM: 2020-01-14T23:14:21.146Z 864f3fb2-1c2d-41a5-aa4f-0379008aa81c    INFO    Sending sitemap ping to google for &lt;a href="https://sitemap-submit.netlify.com/sitemap.xml" rel="noopener noreferrer"&gt;https://sitemap-submit.netlify.com/sitemap.xml&lt;/a&gt;&lt;br&gt;&lt;br&gt;
10:14:21 AM: Duration: 59.46 ms   Memory Usage: 75 MB Init Duration: 137.25 ms&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>netlify</category>
      <category>javascript</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Editing DNS records for ZEIT Now</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Thu, 16 Jan 2020 09:14:37 +0000</pubDate>
      <link>https://dev.to/atymic/editing-dns-records-for-zeit-now-57ca</link>
      <guid>https://dev.to/atymic/editing-dns-records-for-zeit-now-57ca</guid>
      <description>&lt;p&gt;ZEIT Now is awesome, but one of the features that's missing is a GUI interface to edit and configure DNS records. It's actually&lt;br&gt;
not even clear this is possible in the dashboard, but luckily there's a set of commands you can use with the CLI.&lt;/p&gt;

&lt;p&gt;First off, if you haven't already install and authenticated in the &lt;code&gt;now&lt;/code&gt; cli, you'll want to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; now &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; now &lt;span class="c"&gt;# Will prompt to login&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once you're logged in, you can use the commands in the &lt;code&gt;now dns&lt;/code&gt; namespace. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Records
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;now dns add &amp;lt;domain&amp;gt; &amp;lt;name&amp;gt; &amp;lt;record &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;value&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;mx_priority]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;domain&amp;gt;&lt;/code&gt; is the address owned by the user and previously registered withzeit.world by using the commands now domain add zeit.rocks or now alias&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; is the subdomain that will be prefixed to &lt;code&gt;&amp;lt;domain&amp;gt;&lt;/code&gt; (@ as a &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; refers to the domain without any prefix)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;record type&amp;gt;&lt;/code&gt; contains one of the supported record types shown above&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt; indicates the target of the record (like an IP address or a hostname)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[mx_priority]&lt;/code&gt; sets the priority of a certain MX record and can therefore only be used in conjunction with this record type&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Examples
&lt;/h1&gt;

&lt;p&gt;Here's a few examples of commands that might be helpful.&lt;/p&gt;

&lt;p&gt;Add a record for a subdomain&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ now dns add &amp;lt;DOMAIN&amp;gt; &amp;lt;SUBDOMAIN&amp;gt; &amp;lt;A | AAAA | ALIAS | CNAME | TXT&amp;gt;  &amp;lt;VALUE&amp;gt;
  $ now dns add zeit.rocks api A 198.51.100.100
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add a MX record (@ as a name refers to the domain)&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ now dns add &amp;lt;DOMAIN&amp;gt; '@' MX &amp;lt;RECORD VALUE&amp;gt; &amp;lt;PRIORITY&amp;gt;
  $ now dns add domain.com '@' MX mx1.improvmx.com 10 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add a SRV record&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ now dns add &amp;lt;DOMAIN&amp;gt; &amp;lt;NAME&amp;gt; SRV &amp;lt;PRIORITY&amp;gt; &amp;lt;WEIGHT&amp;gt; &amp;lt;PORT&amp;gt; &amp;lt;TARGET&amp;gt;
  $ now dns add zeit.rocks '@' SRV 10 0 389 zeit.party
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Remove a MX record&lt;/p&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ now dns ls # Show the records, and copy the ID you wish to remove&lt;br&gt;
  $ now dns rm rec_92bd1b1e4311ffd93e4a2cae # ID from previous step&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  API&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;If you're building a tool that integrates directly with Zeit Now, you could also manage the DNS records directly through the&lt;br&gt;
API. You can find the endpoint &lt;a href="https://zeit.co/docs/api#get-domain-records"&gt;documentation here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>devops</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Zero Downtime Laravel Deployment with Github Actions</title>
      <dc:creator>atymic</dc:creator>
      <pubDate>Fri, 03 Jan 2020 00:27:39 +0000</pubDate>
      <link>https://dev.to/atymic/zero-downtime-laravel-deployment-with-github-actions-22lo</link>
      <guid>https://dev.to/atymic/zero-downtime-laravel-deployment-with-github-actions-22lo</guid>
      <description>&lt;p&gt;A few months ago I &lt;a href="https://atymic.dev/blog/laravel-zero-downtime-deployment/"&gt;wrote a post&lt;/a&gt; on setting up zero downtime continuous deployment with Gitlab's free CI offering.&lt;br&gt;
Now that Github actions is out of beta I've moved most of my CI/CD pipelines over. &lt;/p&gt;

&lt;p&gt;In my experience Github Actions is a bit faster, but the it's not as user friendly in terms of actually building the pipelines.&lt;br&gt;
The definite advantage for me is simply the fact it's built into Github, meaning I only need to use a single service. The free tier is pretty generous as well.&lt;/p&gt;

&lt;p&gt;Just want to see the code and configuration? The &lt;a href="https://github.com/atymic/zero-downtime-laravel-demo/"&gt;example repo for this post is here&lt;/a&gt;.&lt;br&gt;
I've tried to make it pretty generic, so you should be able to copy/paste the &lt;a href="https://github.com/atymic/zero-downtime-laravel-demo/blob/master/.github/workflows/deploy.yml"&gt;workflow&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/atymic/zero-downtime-laravel-demo/blob/master/deploy.php"&gt;deploy.php&lt;/a&gt; into your own project with minimal changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Gitlab Actions
&lt;/h2&gt;

&lt;p&gt;For the purposes of this post, I'm going to start from scratch with a fresh Laravel installation and guide you through the process of setting up the actions which we'll use for testing, building and deploying our code to production.&lt;/p&gt;

&lt;p&gt;Github actions is configured using yaml files, placed in &lt;code&gt;.github/workflows&lt;/code&gt;. They do provide an editor, but in my experience it's much easier to create and edit the files manually.&lt;br&gt;
To create a new workflow you just need to create a new yaml file inside the aforementioned folder (for example, &lt;code&gt;deploy.yml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Inside this file, you can define your build stages, their dependencies, caching and many other features. For our application, we'll just use a simple three step build (github calls these jobs).&lt;/p&gt;
&lt;h3&gt;
  
  
  Scaffolding the workflow
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;.github/workflows&lt;/code&gt;, create a new workflow, &lt;code&gt;deploy.yml&lt;/code&gt;. We'll need to add some basic configuration to instruct actions to run our workflow on commits to the repo, and define a name.&lt;br&gt;
Under the &lt;code&gt;jobs&lt;/code&gt; sections, we'll need to define each &lt;code&gt;job&lt;/code&gt; of the build/deploy process. For the moment, we'll just set out the basic scaffold and we'll populate it as set up the three jobs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI-CD&lt;/span&gt; &lt;span class="c1"&gt;# Give your workflow a name&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt; &lt;span class="c1"&gt;# Run the workflow for each push event (i.e commit)&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Js/Css&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&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;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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
        &lt;span class="c1"&gt;# Build JS&lt;/span&gt;
  &lt;span class="na"&gt;test-php&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test/Lint PHP&lt;/span&gt;
      &lt;span class="na"&gt;runs-on&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;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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
        &lt;span class="c1"&gt;# Test and Lint PHP code&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Production&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="c1"&gt;# Note that this needs both of the other steps to have completed successfully&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build-js&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;test-php&lt;/span&gt;&lt;span class="pi"&gt;]&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="c1"&gt;# Deploy Code &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Building Javascript and CSS assets
&lt;/h3&gt;

&lt;p&gt;Laravel comes bundled with &lt;a href="https://laravel.com/docs/5.8/mix"&gt;Mix&lt;/a&gt;, which provides an easy interface to webpack to build and compile your front end assets (Javascript and CSS).&lt;/p&gt;

&lt;p&gt;It's worth noting here that we are using the &lt;a href="https://github.com/actions/upload-artifact"&gt;upload-artifact action&lt;/a&gt;. This is required so that our deployment job can deploy the compiled files, as each job operates on a fresh version of the source code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Js/Css&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&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;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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt; &lt;span class="c1"&gt;# Download the source code&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Yarn Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;yarn install&lt;/span&gt;
          &lt;span class="s"&gt;yarn prod&lt;/span&gt;
          &lt;span class="s"&gt;cat public/mix-manifest.json # See asset versions in log&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload build files&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assets&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Running PHP linting and tests
&lt;/h3&gt;

&lt;p&gt;Most applications will have some sort of automated linting and (hopefully) tests. Ensuring that the code is valid and works before deploying to production is a good idea, for obvious reasons. &lt;br&gt;
If the CI build finds issues such as a syntax error or failing test, it won't proceed to deployment. &lt;/p&gt;

&lt;p&gt;In our action, we'll define a &lt;code&gt;test-php&lt;/code&gt; job. Laravel has some sample tests built in, which we'll run. You could also run limiting and static analysis tools in this stage.&lt;br&gt;
You can use the &lt;a href="https://github.com/shivammathur/setup-php"&gt;setup-php action&lt;/a&gt; to use a specific PHP version, or add additional extensions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test-php&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test/Lint PHP&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-js&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@master&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7.3&lt;/span&gt; &lt;span class="c1"&gt;# Use your PHP version&lt;/span&gt;
        &lt;span class="na"&gt;extension-csv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mbstring, bcmath&lt;/span&gt; &lt;span class="c1"&gt;# Setup any required extensions for tests&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Composer install&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Tests&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./vendor/bin/phpunit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploying the code
&lt;/h3&gt;

&lt;p&gt;Now that we've made sure that our code works &amp;amp; have built our assets, we can move on to the actual deployment.&lt;/p&gt;

&lt;p&gt;Head over to your repository settings, and select the &lt;code&gt;Secrets&lt;/code&gt; option in the sidebar.&lt;/p&gt;

&lt;p&gt;My biggest gripe with Github actions is the secret management. For some reason, you can't edit secrets, so to update anything you need to delete and then re-create the entire secret. Hopefully they this fix this at some point.&lt;/p&gt;

&lt;p&gt;We'll need to add a private key with access to the server so that deployer can SSH in. &lt;br&gt;
It's a good idea to generate a new key specifically for deployment (ideally on a specific deployment user with minimal permissions). You'll want to add it under the variable &lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You'll also need to add your Laravel &lt;code&gt;.env&lt;/code&gt; file as &lt;code&gt;DOT_ENV&lt;/code&gt;, so it can be deployed along with the code (you should never store secrets in git). &lt;/p&gt;

&lt;p&gt;Since every CI build/deployment starts from a fresh slate, the containers &lt;code&gt;~/.ssh/known_hosts&lt;/code&gt; file won't be populated. &lt;br&gt;
To ensure there aren't any MITM attacks, we need to our server's SSH fingerprint as a variable, &lt;code&gt;SSH_KNOWN_HOSTS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can find you server's host fingerprint by running &lt;code&gt;ssh-keyscan rsa -t &amp;lt;server IP&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gL1xWzpW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/v1/./images/github-actions-laravel-ci-cd/secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gL1xWzpW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/v1/./images/github-actions-laravel-ci-cd/secrets.png" alt="Configuring github actions secrets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that we have defined both of the other jobs in the &lt;code&gt;needs&lt;/code&gt; section. Both &lt;code&gt;build-js&lt;/code&gt; and &lt;code&gt;test-php&lt;/code&gt; should run asynchronously (this has been inconsistent for me, often they run one at a time), and once both complete the deployment will commence.&lt;/p&gt;

&lt;p&gt;We've also added a conditional &lt;code&gt;if&lt;/code&gt; rule to ensure that only the master branch is deployed (we want tests/linting to run for all branches).&lt;/p&gt;

&lt;p&gt;The job first downloads the compiled javascript/css from the &lt;code&gt;build-js&lt;/code&gt; and applies that to current working tree.&lt;/p&gt;

&lt;p&gt;Before we can deploy, we need to set up ssh (start &lt;code&gt;ssh-agent&lt;/code&gt; with the provided private key, update the &lt;code&gt;known_hosts&lt;/code&gt;) and install deployer. To make this nice and simple, I've created an action to handle everything &lt;br&gt;
for you, but you could run all of the shell commands manually instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/atymic/deployer-php-action"&gt;atymic/deployer-php-action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The job finally runs &lt;code&gt;dep deploy&lt;/code&gt;, which uses deployer to carry out the zero downtime deployment (we'll set it up in the next section).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Production&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build-js&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;test-php&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/master'&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download build assets&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v1&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assets&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@master&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7.3&lt;/span&gt;
      &lt;span class="na"&gt;extension-csv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mbstring, bcmath&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Composer install&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Deployer&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atymic/deployer-php-action@master&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ssh-private-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;ssh-known-hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_KNOWN_HOSTS }}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Prod&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DOT_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOT_ENV }}&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dep deploy production --tag=${{ env.GITHUB_REF }} -vvv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Putting it all together
&lt;/h3&gt;

&lt;p&gt;Now that we've got all of our jobs configured your workflow should look something like the one below. &lt;br&gt;
Feel free to copy and paste this into your own project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI-CD&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Js/Css&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&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;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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Yarn Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;yarn install&lt;/span&gt;
          &lt;span class="s"&gt;yarn prod&lt;/span&gt;
          &lt;span class="s"&gt;cat public/mix-manifest.json # See asset versions in log&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload build files&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assets&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
  &lt;span class="na"&gt;test-php&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test/Lint PHP&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-js&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7.3&lt;/span&gt; &lt;span class="c1"&gt;# Use your PHP version&lt;/span&gt;
          &lt;span class="na"&gt;extension-csv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mbstring, bcmath&lt;/span&gt; &lt;span class="c1"&gt;# Setup any required extensions for tests&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Composer install&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./vendor/bin/phpunit&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Production&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build-js&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;test-php&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/master'&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download build assets&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assets&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7.3&lt;/span&gt;
          &lt;span class="na"&gt;extension-csv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mbstring, bcmath&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Composer install&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Deployer&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atymic/deployer-php-action@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ssh-private-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;ssh-known-hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_KNOWN_HOSTS }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Prod&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DOT_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOT_ENV }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dep deploy production --tag=${{ env.GITHUB_REF }} -vvv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Deployer
&lt;/h2&gt;

&lt;p&gt;Now that we've got the CI set up, it's time to install deployer in our project and configure it. &lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require deployer/deployer deployer/recipes 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you've got it installed, run &lt;code&gt;./vendor/bin/dep init&lt;/code&gt; and follow the prompts, selecting Laravel as your framework. A &lt;code&gt;deploy.php&lt;/code&gt; config file will be generated and placed in the root of your project.&lt;/p&gt;

&lt;p&gt;By default, deployer uses GIT for deployments (by SSHing into the server, running &lt;code&gt;git pull&lt;/code&gt; and executing your build steps) however since we are running it as part of a CI/CD pipeline (our project has been built and is ready for deployment) we'll use &lt;code&gt;rsync&lt;/code&gt; to copy the files directly to the server instead.&lt;/p&gt;

&lt;p&gt;Open your &lt;code&gt;deploy.php&lt;/code&gt; in a code editor. Copy and paste the code below into your &lt;code&gt;deploy.php&lt;/code&gt; above the hosts section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Deployer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Include the Laravel &amp;amp; rsync recipes&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'recipe/laravel.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'recipe/rsync.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dep-demo'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ssh_multiplexing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Speed up deployment&lt;/span&gt;

&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rsync_src'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// If your project isn't in the root, you'll need to change this.&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Configuring the rsync exclusions. &lt;/span&gt;
&lt;span class="c1"&gt;// You'll want to exclude anything that you don't want on the production server.  &lt;/span&gt;
&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rsync'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'exclude'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'.git'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'/.env'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'/storage/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'/vendor/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'/node_modules/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'.github'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'deploy.php'&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="c1"&gt;// Set up a deployer task to copy secrets to the server. &lt;/span&gt;
&lt;span class="c1"&gt;// Grabs the dotenv file from the github secret&lt;/span&gt;
&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy:secrets'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/.env'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DOT_ENV'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.env'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy_path'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/shared'&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;Next, we need to set up our hosts. In this example, we'll only use a single host but deployer supports as many as required. Copy the code below into your &lt;code&gt;deploy.php&lt;/code&gt;, replacing the existing hosts block. You'll need to customise it for your specific server configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hosts&lt;/span&gt;
&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'production.app.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Name of the server&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'178.128.84.15'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Hostname or IP address&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'production'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Deployment stage (production, staging, etc)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// SSH user&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy_path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/var/www'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Deploy path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we'll set up the steps that deployer will execute as part of our deployment. These can be customised to your needs, for example you could use the &lt;code&gt;artisan:horizon:terminate&lt;/code&gt; task to restart your horizon queues. Copy the block below into your &lt;code&gt;deploy.php&lt;/code&gt;, replacing the tasks section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy:failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'deploy:unlock'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Unlock after failed deploy&lt;/span&gt;

&lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Deploy the application'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:info'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:prepare'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:lock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:release'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'rsync'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Deploy code &amp;amp; built assets&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:secrets'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Deploy secrets&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:shared'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:vendors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:writable'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'artisan:storage:link'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// |&lt;/span&gt;
    &lt;span class="s1"&gt;'artisan:view:cache'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// |&lt;/span&gt;
    &lt;span class="s1"&gt;'artisan:config:cache'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// | Laravel specific steps &lt;/span&gt;
    &lt;span class="s1"&gt;'artisan:optimize'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// |&lt;/span&gt;
    &lt;span class="s1"&gt;'artisan:migrate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// |&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:symlink'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'deploy:unlock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'cleanup'&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;h2&gt;
  
  
  Configuring your Server
&lt;/h2&gt;

&lt;p&gt;We'll also need to make a few changes the nginx or apache configuration files on the server. &lt;/p&gt;

&lt;p&gt;You want to set your web server &lt;code&gt;root&lt;/code&gt; to  &lt;code&gt;deploy_path&lt;/code&gt;  (set in your &lt;code&gt;deploy.php&lt;/code&gt;) + &lt;code&gt;/current/public&lt;/code&gt;. For example, in our case this is &lt;code&gt;/var/www/current/public&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You'll also need to make sure that your deploy user has the correct permissions to write to the deployment path. Deployer's &lt;code&gt;deploy:writable&lt;/code&gt; task will ensure that the folders have the correct permissions so your web server user can write them.&lt;/p&gt;

&lt;h2&gt;
  
  
  First deployment 😎
&lt;/h2&gt;

&lt;p&gt;Now that everything's set up, it's time to test our pipelines! &lt;/p&gt;

&lt;p&gt;Commit your workflow and&lt;code&gt;deploy.php&lt;/code&gt; as well as your composer json/lock and push! If all goes well, you can head over to the &lt;code&gt;Actions&lt;/code&gt; tab on your Github repo and watch your build/deployment progress. &lt;/p&gt;

&lt;p&gt;If anything goes wrong, check the CI logs. The logs from github actions can sometimes be a bit opaque, but as long as your yaml if structured correctly it's usually fairly obvious as to what went wrong (missing SSH keys/fingerprints, invalid server config, etc).&lt;/p&gt;

&lt;p&gt;If everything went well, you'll have the latest version of your project deployed. Any new commits will be built and deployed without any interruption for you users.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://github.com/atymic/zero-downtime-laravel-demo/runs/355488210"&gt;first deployment of my test repo&lt;/a&gt;, and &lt;a href="https://github.com/atymic/zero-downtime-laravel-demo/runs/355492803"&gt;one with a migration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T3SDDoqa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/v1/./images/github-actions-laravel-ci-cd/success.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T3SDDoqa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/v1/./images/github-actions-laravel-ci-cd/success.png" alt="Deployment"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This post outlines a basic CI/CD pipeline, but there's plenty of improvements and additions that could be made, such as adding staging environments, release notifications, multi server deployment (for load balanced server groups).&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this post and it helped you improve your builds &amp;amp; deployments, or migrate existing ones to github actions.&lt;/p&gt;

&lt;p&gt;If you have any questions, leave a comment below &amp;amp; i'll do my best to respond to them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/atymic/zero-downtime-laravel-demo"&gt;Example Repo&lt;/a&gt; (&lt;a href="https://github.com/atymic/zero-downtime-laravel-demo"&gt;Code&lt;/a&gt; + &lt;a href="https://github.com/atymic/zero-downtime-laravel-demo/blob/master/.github/workflows/deploy.yml"&gt;Workflow&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deployer.org/docs"&gt;Deployer Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://help.github.com/en/actions"&gt;Github Actions Documentation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>devops</category>
      <category>github</category>
    </item>
  </channel>
</rss>
