<?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: Mitchell</title>
    <description>The latest articles on DEV Community by Mitchell (@verystrongfingers).</description>
    <link>https://dev.to/verystrongfingers</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%2F575780%2F8c8670ea-5eb5-4af3-9699-de09598a271e.png</url>
      <title>DEV Community: Mitchell</title>
      <link>https://dev.to/verystrongfingers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/verystrongfingers"/>
    <language>en</language>
    <item>
      <title>[Part 4/100] Security? Who is she</title>
      <dc:creator>Mitchell</dc:creator>
      <pubDate>Tue, 09 Aug 2022 14:44:00 +0000</pubDate>
      <link>https://dev.to/verystrongfingers/part-4100-security-who-is-she-4dac</link>
      <guid>https://dev.to/verystrongfingers/part-4100-security-who-is-she-4dac</guid>
      <description>&lt;p&gt;Ignoring the 11 months since Part 3 was posted, it would be nice to share a tale between yours truly &amp;amp; Laravel, whomst've have tried their best avoiding a line of code that I believe is exploitable,  present for over &lt;strong&gt;6 years&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The year was 2021; upon discovering what I perceived to be an exploitable piece of code that existed within Laravel since &lt;em&gt;at least 2016&lt;/em&gt;, I did what any good Samaritan would do. &lt;br&gt;
Attempted to get a CVE issued against the framework.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Exploit
&lt;/h2&gt;

&lt;p&gt;Is a line of code within Scheduler's Command Builder that does not escape shell arguments.&lt;/p&gt;

&lt;p&gt;I would never call myself an expert on security, but to my untrained eyes it appeared to resonate the profile of an command injection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://owasp.org/www-community/attacks/Command_Injection" rel="noopener noreferrer"&gt;https://owasp.org/www-community/attacks/Command_Injection&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;windows_os&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
   &lt;span class="s1"&gt;'sudo -u '&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;' -- sh -c \''&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'\''&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; 
   &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(Formatted for readability) &lt;a href="https://github.com/illuminate/console/blob/6678d8f63c1f0dd8fb1c98010d9893673f444e84/Scheduling/CommandBuilder.php#L73" rel="noopener noreferrer"&gt;https://github.com/illuminate/console/blob/6678d8f63c1f0dd8fb1c98010d9893673f444e84/Scheduling/CommandBuilder.php#L73&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With assistance of this line of code, using the Laravel Framework we're able to schedule the execution of &lt;em&gt;any&lt;/em&gt; arbitrary shell command (Outside of Laravel). &lt;/p&gt;
&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Feb 2021
&lt;/h3&gt;

&lt;p&gt;I submit a vulnerability @ Snyk with details surrounding the supposed exploit&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response&lt;/strong&gt; (excerpt)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I recognise that the lines you pointed to show unescaped inputs. However, your description does not clarify how an attacker can alter these input parameters in the context of this package and how it's implemented by its users.&lt;/p&gt;

&lt;p&gt;It would be helpful if you could provide the details showing the possible attack path, or, better yet, a working PoC of an injected command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair enough.&lt;/p&gt;
&lt;h3&gt;
  
  
  6 hours later
&lt;/h3&gt;

&lt;p&gt;Created &amp;amp; replied with this PoC&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/VeryStrongFingers" rel="noopener noreferrer"&gt;
        VeryStrongFingers
      &lt;/a&gt; / &lt;a href="https://github.com/VeryStrongFingers/laravel-shell-escape-poc" rel="noopener noreferrer"&gt;
        laravel-shell-escape-poc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Laravel - Shell Escape PoC&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Proof of Concept to prove Laravel package vendors can exploit the command scheduler to run self-scheduled arbitrary shell commands.&lt;/p&gt;

&lt;p&gt;Meaning the &lt;strong&gt;host&lt;/strong&gt; can execute an arbitrary command in a child process shell, invoked by the Laravel scheduler.&lt;/p&gt;

&lt;p&gt;This package uses &lt;code&gt;head -n 1 /etc/passwd &amp;gt; /tmp/really-cool.log&lt;/code&gt; for the example.&lt;/p&gt;

&lt;p&gt;Affecting all Laravel versions above 5.4 (Lumen is untested) when the scheduler &lt;code&gt;artisan schedule:run&lt;/code&gt; is used.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installation&lt;/h3&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://laravel.com/docs/8.x/installation#installation-via-composer" rel="nofollow noopener noreferrer"&gt;Setup fresh laravel project&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;composer create-project laravel/laravel example-app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Include this package&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;composer require shell-escape/poc:dev-master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Due to the nature of this package I will not be adding it to packagist.&lt;br&gt;You will have to add the repository manually &lt;a href="https://getcomposer.org/doc/05-repositories.md#repository" rel="nofollow noopener noreferrer"&gt;https://getcomposer.org/doc/05-repositories.md#repository&lt;/a&gt;
&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Running&lt;/h3&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; php artisan schedule:run
Running scheduled command: sudo -u YOURUSER &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;$(&lt;/span&gt;cat /etc/passwd &lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; /tmp/really-cool.log &lt;span class="pl-k"&gt;||&lt;/span&gt; true&lt;span class="pl-pds"&gt;)&lt;/span&gt;&lt;/span&gt; -- sh -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;/usr/bin/php&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt; &lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;artisan&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt; the:poc &amp;gt; &lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;/dev/null&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
Process finished with &lt;span class="pl-c1"&gt;exit&lt;/span&gt; code&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/VeryStrongFingers/laravel-shell-escape-poc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
(Technical details are inside ^ README)&lt;/p&gt;

&lt;h3&gt;
  
  
  Later in Feb 2021
&lt;/h3&gt;

&lt;p&gt;Received a follow up&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've verified the PoC you attached and have contacted the maintainers. I’ll let you know how the disclosure progresses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  March 2021
&lt;/h3&gt;

&lt;p&gt;Received a follow up&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The guys at Laravel responded as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But, isn't users installing a malicious internal package always a security vulnerability. &lt;em&gt;ANY&lt;/em&gt; package can just list a file to be autoloaded in their composer.json file that for example deletes your entire database, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think the point they're making is that they cannot (or rather, should not have to) protect against malicious vendors. In that sense, I tend to agree with them.&lt;/p&gt;


&lt;/blockquote&gt;

&lt;p&gt;--&lt;br&gt;
I feel it's relevant to provide OWASP's definition of a 'vulnerability' here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A vulnerability is a weakness in an application (frequently a broken or missing control) that enables an attack to succeed."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  March 2021
&lt;/h3&gt;

&lt;p&gt;I reply with a mini essay expressing my disagreement, but ultimately concede become I am not the security expert.&lt;/p&gt;

&lt;p&gt;The reported vulnerability case is closed with no further action. &lt;/p&gt;
&lt;h3&gt;
  
  
  April 2022
&lt;/h3&gt;

&lt;p&gt;Factoring in Laravel have stated their views on the issue, and not fixed it a year onward - I figure the 'type' of issue this would fall under is an Improvement, or Bug fix - which I would report as an issue to their GitHub repo.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/laravel/framework/issues/42014" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Task scheduler does not escape shell arguments
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#42014&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/VeryStrongFingers" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F7744427%3Fv%3D4" alt="VeryStrongFingers avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/VeryStrongFingers" rel="noopener noreferrer"&gt;VeryStrongFingers&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/laravel/framework/issues/42014" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 17, 2022&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      

&lt;ul&gt;
&lt;li&gt;Laravel Version: 8+&lt;/li&gt;
&lt;li&gt;PHP Version: 7.4+&lt;/li&gt;
&lt;li&gt;Database Driver &amp;amp; Version: *&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description:&lt;/h3&gt;
&lt;p&gt;Originally submitted this as a vulnerability report over a year ago, but it was dismissed due to "malicious package vendors being out of the control of the framework." [paraphrased]
Reporting it as a bug now, instead of security issue.&lt;/p&gt;
&lt;p&gt;Task Scheduler unnecessarily allows 'command injection', there is no command argument escaping in the &lt;a href="https://github.com/illuminate/console/blob/c5c8b2bb0dfaf7e832ae1a9cab2a9a5db680143f/Scheduling/CommandBuilder.php#L73" rel="noopener noreferrer"&gt;CommandBuilder&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was close to submitting a PR/fix &lt;a href="https://github.com/VeryStrongFingers/framework/commit/2b8aa5ea57a59f8a167583ab617f4283443ad10e" rel="noopener noreferrer"&gt;https://github.com/VeryStrongFingers/framework/commit/2b8aa5ea57a59f8a167583ab617f4283443ad10e&lt;/a&gt; (haven't confirmed if it's fixed on Windows), but unfortunately lost interest due to perceived shared concern was lacking.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Steps To Reproduce:&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/VeryStrongFingers/laravel-shell-escape-poc" rel="noopener noreferrer"&gt;https://github.com/VeryStrongFingers/laravel-shell-escape-poc&lt;/a&gt; has a detailed explanation and demo&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/laravel/framework/issues/42014" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I made the mistake of listing the affected Laravel version as "Laravel 8+"&lt;/p&gt;

&lt;p&gt;tl;dr response&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unfortunately we don't support this version anymore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;????&lt;/p&gt;

&lt;h3&gt;
  
  
  April, 1 day later
&lt;/h3&gt;

&lt;p&gt;Copy pasting the same issue content, but explicitly included "Laravel 9" this time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/laravel/framework/issues/42037" rel="noopener noreferrer"&gt;https://github.com/laravel/framework/issues/42037&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't see any big issues here. I don't see how this could lead to security issues. Please also do not publicly submit security issues, see our readme.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Let's disregard the contradictory statements.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In Closing
&lt;/h2&gt;

&lt;p&gt;After 3 attempts of reporting what I believe is an unnecessarily existing attack vector existing within Laravel's control, I will concede;&lt;/p&gt;

&lt;p&gt;However, genuinely curious to hear the thoughts of you lovely external unbiased readers perspectives.&lt;/p&gt;

&lt;p&gt;Have I been pushing a non-issue? &lt;br&gt;
or are Command escape vulnerabilities not as easily recognised because this is a &lt;em&gt;web&lt;/em&gt; framework?'&lt;/p&gt;

&lt;p&gt;Will this remain forever as is because changing it could be perceived as acknowledging it as a security issue?&lt;/p&gt;

&lt;p&gt;P.S. In case you missed it, vector details are provided in the README of &lt;a href="https://github.com/VeryStrongFingers/laravel-shell-escape-poc" rel="noopener noreferrer"&gt;https://github.com/VeryStrongFingers/laravel-shell-escape-poc&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>security</category>
      <category>command</category>
      <category>gobli</category>
    </item>
    <item>
      <title>[Part 3/100] Eloquent sucks</title>
      <dc:creator>Mitchell</dc:creator>
      <pubDate>Sun, 29 Aug 2021 00:00:31 +0000</pubDate>
      <link>https://dev.to/verystrongfingers/part-3-100-eloquent-sucks-1k45</link>
      <guid>https://dev.to/verystrongfingers/part-3-100-eloquent-sucks-1k45</guid>
      <description>&lt;p&gt;As the README says:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/illuminate" rel="noopener noreferrer"&gt;
        illuminate
      &lt;/a&gt; / &lt;a href="https://github.com/illuminate/database" rel="noopener noreferrer"&gt;
        database
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      [READ ONLY] Subtree split of the Illuminate Database component (see laravel/framework)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Illuminate Database&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style ORM, and schema builder. It currently supports MySQL, Postgres, SQL Server, and SQLite. It also serves as the database layer of the Laravel PHP framework.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Usage Instructions&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;First, create a new "Capsule" manager instance. Capsule aims to make configuring the library for usage outside of the Laravel framework as easy as possible.&lt;/p&gt;
&lt;div class="highlight highlight-text-html-php notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;use&lt;/span&gt; &lt;span class="pl-v"&gt;Illuminate&lt;/span&gt;\&lt;span class="pl-v"&gt;Database&lt;/span&gt;\&lt;span class="pl-v"&gt;Capsule&lt;/span&gt;\&lt;span class="pl-v"&gt;Manager&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-v"&gt;Capsule&lt;/span&gt;
&lt;span class="pl-s1"&gt;&lt;span class="pl-c1"&gt;$&lt;/span&gt;capsule&lt;/span&gt; = &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;Capsule&lt;/span&gt;;

&lt;span class="pl-s1"&gt;&lt;span class="pl-c1"&gt;$&lt;/span&gt;capsule&lt;/span&gt;-&amp;gt;&lt;span class="pl-en"&gt;addConnection&lt;/span&gt;([
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;driver&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;mysql&lt;/span&gt;'&lt;/span&gt;,
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;host&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;localhost&lt;/span&gt;'&lt;/span&gt;,
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;database&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;database&lt;/span&gt;'&lt;/span&gt;,
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;username&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;root&lt;/span&gt;'&lt;/span&gt;,
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;password&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;password&lt;/span&gt;'&lt;/span&gt;,
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;charset&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;utf8&lt;/span&gt;'&lt;/span&gt;,
    &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;collation&lt;/span&gt;'&lt;/span&gt; =&amp;gt; &lt;span class="pl-s"&gt;'&lt;span class="pl-s"&gt;utf8_unicode_ci&lt;/span&gt;'&lt;/span&gt;,
    &lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/illuminate/database" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style ORM, and schema builder. It currently supports MySQL, Postgres, SQL Server, and SQLite. It also serves as the database layer of the Laravel PHP framework.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part of this toolkit available is called Eloquent, which is the ORM for the Laravel Framework.&lt;/p&gt;

&lt;p&gt;tl;dr, it sucks because...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Record itself sucks&lt;/li&gt;
&lt;li&gt;their actual implementation of Active Record is also bad and exposes a lot&lt;/li&gt;
&lt;li&gt;models represent a table row, but can do &lt;em&gt;so much beyond that&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;there's like 10 different ways to access model properties&lt;/li&gt;
&lt;li&gt;expectations aren't managed around null vs '0' vs false &lt;/li&gt;
&lt;li&gt;the illuminate/database code quality is low&lt;/li&gt;
&lt;li&gt;mutators are gross (&lt;code&gt;get&amp;lt;X&amp;gt;Attribute&lt;/code&gt; is unobviously a proxy method)&lt;/li&gt;
&lt;li&gt;and of course it sucks because artisan code comes at an unknown cost to the unsuspecting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bad Active Record implementation
&lt;/h2&gt;

&lt;p&gt;For those unfamiliar with the design pattern, &lt;a href="https://en.wikipedia.org/wiki/Active_record_pattern" rel="noopener noreferrer"&gt;Active Record is a design pattern&lt;/a&gt; commonly associated with ORMs for relational databases.&lt;/p&gt;

&lt;p&gt;It's often seen as subjective to dislike Active Record, but it objectively mixes concerns; which is something all developers should at very least understand what they're trading off.&lt;/p&gt;

&lt;p&gt;Even better, Laravel's implementation of Active Record publicly exposes implementation details which is unnecessarily encouraging poor development choices to the unaware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&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;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Get the phone associated with the user.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;phone&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Phone&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&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="nv"&gt;$phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;^ Standard innocent usage of Eloquent, fetching a related model.&lt;/p&gt;

&lt;p&gt;In order for &lt;code&gt;$user&lt;/code&gt; to resolve the relational 1:1 model &lt;code&gt;Phone&lt;/code&gt;, it needs to perform a database query. Again, standard for Active Record - but in other words, &lt;code&gt;$user&lt;/code&gt; has been tightly coupled with your actual database connection.&lt;/p&gt;

&lt;p&gt;With that in mind, and a single Eloquent model (&lt;code&gt;$user&lt;/code&gt;) here's a list of functionality you can perform &lt;strong&gt;directly from your model instance&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;You could... create a fresh QueryBuilder for subsequent unrelated queries&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&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="nv"&gt;$freshQueryBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnrelatedModel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$freshQueryBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'table_name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or, you could access the schema manager and drop your database... from the model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&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="nv"&gt;$user&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDoctrineSchemaManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dropDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$post&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDatabaseName&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;or, you could build a query, and execute in &lt;a href="https://github.com/doctrine/dbal" rel="noopener noreferrer"&gt;&lt;strong&gt;Doctrine DBAL&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&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="nv"&gt;$statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;newQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'is_activated'&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toSql&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDoctrineConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;executeStatement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$statement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or, you could serialize something into JSON... from your model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getGrammar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prepareBindingForJsonContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;JsonSerializable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nv"&gt;$coolData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'something'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'cool'&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="n"&gt;jsonSerialize&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;coolData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// $json =&amp;gt; '{"something": "cool"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;obviously no one will ever do this (please don't). The point is this functionality &lt;strong&gt;should not be exposed&lt;/strong&gt;.&lt;br&gt;
Active Record is bad enough without some unstable APIs sprinkled on top.&lt;/p&gt;

&lt;p&gt;I'm not going to start on Entity Mapping vs Active Record today, but if you value testability and separating concerns I would recommend Laravel-doctrine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.laraveldoctrine.org/" rel="noopener noreferrer"&gt;http://www.laraveldoctrine.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Cyclomatic complexity
&lt;/h2&gt;

&lt;p&gt;After seeing the shear size of &lt;a href="//Connection.php"&gt;https://github.com/illuminate/database/blob/427babd19deffaf7a288abd7cdbbcd2aaa86144d/Connection.php&lt;/a&gt; I decided to run a &lt;a href="https://phpmd.org/rules/codesize.html" rel="noopener noreferrer"&gt;PHPMD Codesize scan&lt;/a&gt; on the whole illuminate/database repository&lt;/p&gt;

&lt;p&gt;Highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The class Model has 2073 lines of code.&lt;/li&gt;
&lt;li&gt;The class Model has 112 public methods and attributes.&lt;/li&gt;
&lt;li&gt;The class Builder has 1653 lines of code.&lt;/li&gt;
&lt;li&gt;The class Connection has 35 public methods.&lt;/li&gt;
&lt;li&gt;The class Connection has 43 non-getter and setter-methods.&lt;/li&gt;
&lt;li&gt;The class Connection has 70 public methods and attributes.&lt;/li&gt;
&lt;li&gt;The class Connection has 1385 lines of code.&lt;/li&gt;
&lt;li&gt;The method addCastAttributesToArray() has an NPath complexity of 865.&lt;/li&gt;
&lt;li&gt;The method withAggregate() has an NPath complexity of 1304.&lt;/li&gt;
&lt;li&gt;The class Factory has 37 non-getter- and setter-methods.
... and much much more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;don't forget the many magic method usages too.&lt;/p&gt;

&lt;p&gt;I would strongly recommend looking into PHPMD, and what NPath &amp;amp; cyclomatic complexity means if it's new to you.&lt;/p&gt;

&lt;p&gt;Eloquent and associated database code is essentially complex, hard to work with bloated code. On a high level what this means for framework users is that your underlying database will find it hard to introduce more functionality, and find it hard to improve existing functionality, but will also be more likely to introduce new bugs, because complexity is already high.&lt;/p&gt;
&lt;h2&gt;
  
  
  Magical schema-awareness
&lt;/h2&gt;

&lt;p&gt;If you have a table of people, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Persons&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;PersonID&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;isOverweight&lt;/span&gt; &lt;span class="nb"&gt;TINYINT&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="n"&gt;City&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&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;and a model of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&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;Person&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The table associated with the model.
     *
     * @var string
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Persons'&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="n"&gt;getIsOverweight&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;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&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="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;bool&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="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;Laravel just miraculously happens know how to resolve the values from table columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&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="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;City&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isOverweight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Nullable BOOL column&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's very neat 'artisan' code isn't it? The cost of this magic is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDE auto-completion&lt;/li&gt;
&lt;li&gt;Loss of static analysis&lt;/li&gt;
&lt;li&gt;magic methods usage &lt;a href="https://github.com/illuminate/database/blob/08990b30e16f031d9edccc02fa4c947de64a4622/Eloquent/Model.php#L1942-L1945" rel="noopener noreferrer"&gt;&lt;code&gt;__get&lt;/code&gt;&lt;/a&gt; = slower code&lt;/li&gt;
&lt;li&gt;Unknown types (is it a string? bool? null? what does a non-existent column return? it's unobvious without deep diving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't forget you can also access model values via...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'isOverweight'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'isOverweight'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttributes&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;'isOverweight'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'isOverweight'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;attributesToArray&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;'City'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getOriginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'City'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRawOriginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'City'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you feel safe enough to trust Laravel will run the mutator (type cast '1' to true) with &lt;strong&gt;all of these methods&lt;/strong&gt;? A sane person would say no, I don't trust like that.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://laravel.com/docs/8.x/eloquent-mutators" rel="noopener noreferrer"&gt;Mutators&lt;/a&gt; themselves deeper the wound of model accessors, not knowing what types to expect, and handling null vs 0, etc.&lt;/p&gt;

&lt;p&gt;Allowing such dynamic functionality, and duplicate ways to 'get' something is another hidden cost that may be forever unobvious to those choosing to stick within the Laravel ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redaction
&lt;/h2&gt;

&lt;p&gt;After writing this post I came across a tweet by Laravel creator, which actually justifies some of my pain points&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-818952001748926465-157" src="https://platform.twitter.com/embed/Tweet.html?id=818952001748926465"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-818952001748926465-157');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=818952001748926465&amp;amp;theme=dark"
  }



&lt;br&gt;
&lt;iframe class="tweet-embed" id="tweet-818959093150912512-433" src="https://platform.twitter.com/embed/Tweet.html?id=818959093150912512"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-818959093150912512-433');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=818959093150912512&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/False_equivalence" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/False_equivalence&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>eloquent</category>
      <category>orm</category>
      <category>database</category>
    </item>
    <item>
      <title>4 extraordinary traits of an amazing manager</title>
      <dc:creator>Mitchell</dc:creator>
      <pubDate>Sun, 13 Jun 2021 03:35:01 +0000</pubDate>
      <link>https://dev.to/verystrongfingers/4-extraordinary-traits-of-an-amazing-manager-3641</link>
      <guid>https://dev.to/verystrongfingers/4-extraordinary-traits-of-an-amazing-manager-3641</guid>
      <description>&lt;h2&gt;
  
  
  Good communicator
&lt;/h2&gt;

&lt;p&gt;A good manager will be able to keep tabs on each team members interests &amp;amp; career aspirations.&lt;/p&gt;

&lt;p&gt;Communication ultimately is the key to a successful and high-functioning team. Managers being in a position of power (per se) is able to influence team dynamics and processes by using their voice for the power of good.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6tvcd4Bo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb5wtkpkqq58ocney7we.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6tvcd4Bo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb5wtkpkqq58ocney7we.png" alt="Manager managing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Will not micromanage
&lt;/h2&gt;

&lt;p&gt;A good manager will always have a backbone of trust within the team dynamics.&lt;/p&gt;

&lt;p&gt;Micromanagement will reduce employee confidence &amp;amp; independence. It will cost also business with the loss of individual minutes that could have been more productively spent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kj9aFlu7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oo3jxrzzy9uce7p9tx65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kj9aFlu7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oo3jxrzzy9uce7p9tx65.png" alt="Great manager"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Does not play favorites
&lt;/h2&gt;

&lt;p&gt;Teams typically are compromised of individuals with differing strengths.&lt;/p&gt;

&lt;p&gt;From a business perspective there may be some pressure on rapid iteration, but your interests as a manager should not be isolated to business interests.&lt;br&gt;
Your team is your responsibility, their sanity &amp;amp; personal development are in your hands to a degree, if team members #1 has an interest in an area that team member #2 is exceptional in, you still should put in the time and effort to further team member's #1 skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good managers show empathy
&lt;/h2&gt;

&lt;p&gt;Being a good manager means being able to resolve conflicts, deal with stress, understanding &amp;amp; relating to the emotions of others, and much more.&lt;/p&gt;




&lt;h1&gt;
  
  
  It's just a prank, bro.
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Something that is posted online that has agreeable and/or relatable words makes it valid, right?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The amount of dev.to and Medium posts stating "&lt;em&gt;N ways to be a good manager&lt;/em&gt;", that proceed to describe traits that boil down to: &lt;strong&gt;treat the people in your team as human beings&lt;/strong&gt; is just crazy.&lt;/p&gt;

&lt;p&gt;Managers that manage to manage in the interest of both their people &amp;amp; the business praise-worthy. &lt;/p&gt;

&lt;p&gt;Communication (including empathy), organisational skills, personal development, not taking credit, ability to communicate are all part of the expectation &amp;amp; duties for a manager.&lt;br&gt;
What should be the baseline is now being labelled as "great"?&lt;/p&gt;

</description>
      <category>manager</category>
      <category>productivity</category>
      <category>managing</category>
      <category>good</category>
    </item>
    <item>
      <title>[Part 2/100] Laravel encourages poor programming practices</title>
      <dc:creator>Mitchell</dc:creator>
      <pubDate>Thu, 10 Jun 2021 13:36:57 +0000</pubDate>
      <link>https://dev.to/verystrongfingers/part-2-100-laravel-encourages-poor-programming-practices-682</link>
      <guid>https://dev.to/verystrongfingers/part-2-100-laravel-encourages-poor-programming-practices-682</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 2 (of 100)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Skip to the last half overall if you just want to see specifics.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Poor programming"?
&lt;/h2&gt;

&lt;p&gt;Obviously the meaning behind &lt;em&gt;"poor programming practices"&lt;/em&gt; is subjective, there is no clear-cut laws of "good programming".&lt;br&gt;
However, to a degree there some level of mutual agreement throughout the internet with what defines "bad programming".&lt;/p&gt;

&lt;p&gt;My perspective of how "poor programming" can occur is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When someone understands their tooling more than the underlying language&lt;/li&gt;
&lt;li&gt;Implementations using frameworks that lack consistency &lt;/li&gt;
&lt;li&gt;Code has been writing to make something work (instead of writing code that enables functionality)&lt;/li&gt;
&lt;li&gt;Codebase developed with tight coupling&lt;/li&gt;
&lt;li&gt;Complex code is being developed (typically &lt;em&gt;looks&lt;/em&gt; impressive, but sucks to work with)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why is Laravel a popular framework?
&lt;/h2&gt;

&lt;p&gt;Could it be due to...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ease of use? (Small learning curve)&lt;/li&gt;
&lt;li&gt;Due to good or lucky SEO for "PHP Framework"&lt;/li&gt;
&lt;li&gt;It's not, PHP is popular and Laravel has the most internet points&lt;/li&gt;
&lt;li&gt;Because it is a proven, mature, general purpose (web) framework&lt;/li&gt;
&lt;li&gt;Something else, or a combination of above?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I speculate the reason Laravel has gained so much traction is due to a combination of PHP being on the forefront of web development (plus its loosly typed nature), combined with Laravel being a general-purpose framework that really is easy to learn and build with.&lt;/p&gt;
&lt;h2&gt;
  
  
  Laravel ❤️'s poor programming
&lt;/h2&gt;

&lt;p&gt;In the current state of the world in technology, it's very clear that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web technology &amp;amp; development has really blown up in the last 10 years&lt;/li&gt;
&lt;li&gt;PHP is &lt;strong&gt;relatively&lt;/strong&gt; very easy to run, write and learn (compared to other languages)&lt;/li&gt;
&lt;li&gt;Laravel is popular and commonly associated with PHP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re encountering a wave development where we have lots of people becoming Laravel developers. Not &lt;strong&gt;PHP developers&lt;/strong&gt;, not "developers”. Just Laravel specialists.&lt;/p&gt;

&lt;p&gt;Laravel provides a wrapper for every conceivable &amp;amp; commonly used data type, and abstract concept so that these developers never have to leave the ecosystem.&lt;br&gt;
We have thousands of composer packages that exist for Laravel specifically, instead of being a package that provides an optional Laravel service provider.&lt;/p&gt;

&lt;p&gt;Obviously for any given  framework this is somewhat expected, but Laravel is in a league of its own and does not really teach developers anything beyond using magical clan code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Specifics
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Facades
&lt;/h3&gt;

&lt;p&gt;Facades are an absolute sin. They should never have been.&lt;/p&gt;

&lt;p&gt;We know with certainty that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global state is bad&lt;/li&gt;
&lt;li&gt;service-locators are an anti-pattern&lt;/li&gt;
&lt;li&gt;magic sucks and is not reliable&lt;/li&gt;
&lt;li&gt;phpDoc type-hinting is not necessarily true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Facades violate all four. When you use a facade, you are asking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Please oh magic eight ball, magically find my Cache service and fetch this cached item&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my-cache-key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, you're lazy and have just admitted that you have absolutely no idea where your cache service lives.&lt;/p&gt;

&lt;p&gt;Justified as “beautiful code”, facades have an unseen cost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code has become extremely less testable&lt;/li&gt;
&lt;li&gt;you have unnecessarily tightly coupled yourself with the framework&lt;/li&gt;
&lt;li&gt;your reliance on the framework greatly increases due to requiring test helpers to manipulate implementations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Magical service resolution is not a good idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helpers functions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://laravel.com/docs/8.x/helpers#available-methods"&gt;https://laravel.com/docs/8.x/helpers#available-methods&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any global helper function that can provide application specific insights is pure magic evil.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;session()&lt;/code&gt; function may be used to get or set session values:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request()&lt;/code&gt; returns the current request instance or obtains an input field's value from the current request&lt;/li&gt;
&lt;li&gt;there are many more... I'm too lazy to reference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How can a function that has no context of your bootstrapped application resolve things?&lt;br&gt;
By using static variables.&lt;/p&gt;

&lt;p&gt;Static variables essentially bring forward most of the points from Facades.&lt;br&gt;
Beautiful code comes at a cost.&lt;/p&gt;

&lt;p&gt;P.S. &lt;code&gt;dd()&lt;/code&gt; is not a debugging tool.&lt;br&gt;
P.P.S. &lt;code&gt;tap()&lt;/code&gt; is really dumb and I refuse to understand it&lt;/p&gt;
&lt;h3&gt;
  
  
  Chained methods
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$paginator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$paginator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCollection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transform&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="kt"&gt;ProcessAppointment&lt;/span&gt; &lt;span class="nv"&gt;$apptService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$appointment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$apptService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;scheduleAppointment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'appt_time_requested'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$apptService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;bookIt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$person&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;🤮 We're all aware that Laravel &lt;a href="https://laravel.com/docs/8.x/helpers#method-with"&gt;fully&lt;/a&gt; &lt;a href="https://laravel.com/docs/8.x/helpers#method-fluent-str-tap"&gt;embraces&lt;/a&gt; &lt;a href="https://laravel.com/docs/8.x/queues#job-chaining"&gt;the&lt;/a&gt; &lt;a href="https://laravel.com/docs/8.x/responses#attaching-headers-to-responses"&gt;usage&lt;/a&gt; &lt;a href="https://laravel.com/docs/8.x/eloquent-relationships#chaining-orwhere-clauses-after-relationships"&gt;of&lt;/a&gt; callbacks &amp;amp; &lt;a href="https://laravel.com/docs/8.x/mix#introduction"&gt;chained methods&lt;/a&gt; (ie. &lt;code&gt;return $this;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Chained methods... look sort of cool I guess? Maybe slightly nicer to read at a glance?&lt;br&gt;
Go back to 2008 and write some JavaScript if you want to see where this story ends up.&lt;/p&gt;

&lt;p&gt;Chained methods result in complicated code (lots of function calls) that is not fun to work with, it makes testing harder and offers nothing of value other than writing less code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Late Intermission
&lt;/h2&gt;

&lt;p&gt;If you're into "Wave" music (you're probably not), check out &lt;a href="https://soundcloud.com/YEDGAR"&gt;Yedgar&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="100%" height="80px" src="https://open.spotify.com/embed/track/1j3Xof38kNljpJm0vqFVNb"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This 100 part series is quite literally revolved around "Laravel bad", which to be honest is pretty dumb because Laravel isn't "bad" - I just disagree with some of the principles &amp;amp; features on offer.&lt;/p&gt;

&lt;p&gt;Laravel exists to be easy, and there's absolutely demand for it. The issue is that Laravel developers are increasingly turning into some weird cult. &lt;/p&gt;

&lt;p&gt;You shouldn't be proud to master a framework that exists to be easily mastered.&lt;br&gt;
Nor should you be defensive of your precious framework just because you've hinged your entire career off it.&lt;/p&gt;

&lt;p&gt;At very least be open minded enough to consider alternatives, learn why parts of Laravel is (or isn't) bad, don't assume anything, especially do not assume the documented Laravel way is the right way.&lt;/p&gt;

&lt;p&gt;Just because it’s original doesn’t mean it’s good.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>succ</category>
      <category>php</category>
      <category>idk</category>
    </item>
    <item>
      <title>[Introductory] Laravel sucks. Here's 100 reasons why</title>
      <dc:creator>Mitchell</dc:creator>
      <pubDate>Tue, 08 Jun 2021 13:14:55 +0000</pubDate>
      <link>https://dev.to/verystrongfingers/introductory-laravel-sucks-here-s-100-reasons-why-4olm</link>
      <guid>https://dev.to/verystrongfingers/introductory-laravel-sucks-here-s-100-reasons-why-4olm</guid>
      <description>&lt;p&gt;I am going to assume you're reading this because you're pondering "But, how could one of the most popular frameworks in PHP suck?".&lt;/p&gt;

&lt;p&gt;Well, fellow reader, that is I am here today; to assume things, waste my own time, waste your time, type some words that most people don't care about or don't want to know, just so I can feel like I have a voice in some weird attempt to validate my beliefs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming topics
&lt;/h2&gt;

&lt;p&gt;Each post in this 100 part series will be on a single topic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel encourages poor programming practices (&lt;em&gt;BuT iTs A fEaTuRE&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Painful type-hinting (both phpDoc &amp;amp; literals) &lt;/li&gt;
&lt;li&gt;Why Taylor Otwell's twitter followers are the worst&lt;/li&gt;
&lt;li&gt;How developers learn "Laravel", not PHP&lt;/li&gt;
&lt;li&gt;Laravel tinker&lt;/li&gt;
&lt;li&gt;Why we should not build "REST" APIs with Laravel&lt;/li&gt;
&lt;li&gt;Lumen should not exist&lt;/li&gt;
&lt;li&gt;Test helpers not helping&lt;/li&gt;
&lt;li&gt;Taylor Otwell's tweets&lt;/li&gt;
&lt;li&gt;Macros&lt;/li&gt;
&lt;li&gt;The generosity of Laracon organisers&lt;/li&gt;
&lt;li&gt;Obvious pain points with the artisan scheduler&lt;/li&gt;
&lt;li&gt;The insulting lack of credit where it's due&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not an exhaustive list above. Just providing an idea on the subjects to come.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this series will &lt;strong&gt;not&lt;/strong&gt; do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Talk about any of the positives&lt;/strong&gt; to do with Laravel. We honestly have enough advocates and blogs on that already, go read those instead.&lt;/p&gt;

&lt;p&gt;I'm going to be real and say I might get bored of writing these. It's &lt;strong&gt;very&lt;/strong&gt; possible I will never make it to part 100, but here's to being 'ambitious'.&lt;/p&gt;

&lt;h2&gt;
  
  
  Post formatting
&lt;/h2&gt;

&lt;p&gt;Sometimes posts in this series will be extremely technical, others will be more abstract or conceptual. Sometimes I will strawman, sometimes it might be blatantly rude, or wrong. I will try and tag them appropriately so you can filter what is boring to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment
&lt;/h2&gt;

&lt;p&gt;Obviously with a series named "100 reasons Laravel sucks" my stance is very clear. You can try and argue and change my mind in the comments, but you'll be wasting your own time - just as I am here writing this.&lt;/p&gt;




&lt;p&gt;I'm calling it a day here at post #1. We've covered the overview &amp;amp; introduction which serves part 1's purpose.&lt;/p&gt;

&lt;p&gt;Part #2 will be covering "Laravel encourages poor programming practices" - coming later this week.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>sucks</category>
      <category>badly</category>
    </item>
    <item>
      <title>Installing ErpNEXT with k3d v4</title>
      <dc:creator>Mitchell</dc:creator>
      <pubDate>Thu, 11 Feb 2021 14:44:57 +0000</pubDate>
      <link>https://dev.to/verystrongfingers/installing-erpnext-with-k3d-v4-51oh</link>
      <guid>https://dev.to/verystrongfingers/installing-erpnext-with-k3d-v4-51oh</guid>
      <description>&lt;p&gt;&lt;a href="https://erpnext.com/"&gt;ERPNext is a beautiful piece of open source&lt;/a&gt; (GPL licensed) work - intended to offer an alternative to the encumbered and very enterprise SAP &amp;amp; ERP solutions that typically dominate the business world.&lt;/p&gt;

&lt;p&gt;However, given the nature of an OSS ERP project, a slow but natural rate of adoption &amp;amp; popularity will ensure, but for now the kubernetes/helm documentation are quite raw. Community and developer resources are also lacking due to reasons that will likely just resolve, as the project matures and gets more widely adopted.  &lt;/p&gt;

&lt;p&gt;We've had a very recent release of K3d v4.0 (mid-Jan 2021) which I had been waiting keenly to try out. It just felt like a perfect matching, but understandably lacking resources pairing the two.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;br&gt;
If you just want to blindly copy+paste commands, getting your instance operational ASAP:&lt;br&gt;

  
  &lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;&lt;/a&gt;
      &lt;a href="https://github.com/VeryStrongFingers"&gt;
        VeryStrongFingers
      &lt;/a&gt; / &lt;a href="https://github.com/VeryStrongFingers/erpnext-k3s"&gt;
        erpnext-k3s
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Tonights plan
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;create a local k3s cluster by using k3d&lt;/li&gt;
&lt;li&gt;add some namespaces to said cluster&lt;/li&gt;
&lt;li&gt;prepare kubernetes resources &amp;amp; helm values to our filesytem&lt;/li&gt;
&lt;li&gt;install some helm charts to said cluster&lt;/li&gt;
&lt;li&gt;declare a persistent volume claim &amp;amp; secret for our cluster&lt;/li&gt;
&lt;li&gt;run a kubernetes job to create ERPNext site&lt;/li&gt;
&lt;li&gt;setup an ingress to route our ERPNext site through the LoadBalancer&lt;/li&gt;
&lt;li&gt;suss out the joys of ERPNext&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Install Tooling
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;It is assumed you already have kubectl &amp;amp; Docker installed, and that you're running a Unix based OS.&lt;/em&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  k3d
&lt;/h3&gt;

&lt;p&gt;k3d is a helper that lets you easily create k3s clusters, using a docker daemon&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;v4.10 was latest at time of writing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;references&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rancher/k3d#get"&gt; k3d github - README&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  helm
&lt;/h3&gt;

&lt;p&gt;We will be installing the ERPNext stack by using their official helm chart&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;v3.5.1 was latest at time of writing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;references&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://helm.sh/docs/intro/install/"&gt;helm - install docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  optionally
&lt;/h3&gt;

&lt;p&gt;Consider using &lt;a href="https://github.com/ahmetb/kubectx"&gt;kubectx&lt;/a&gt; or setting your own zsh/bash aliases to easily switch kubectl context&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the cluster
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k3d cluster create erpnext &lt;span class="nt"&gt;-v&lt;/span&gt; /opt/local-path-provisioner:/opt/local-path-provisioner
kubectl config use-context k3d-erpnext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A volume is required for the cluster because we will be using the k3s built-in local-path persistence&lt;/p&gt;

&lt;p&gt;Your kubecontext gets updated with the second command, meaning now when we run &lt;code&gt;kubectl&lt;/code&gt; it will default to our new K3s cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare resources &amp;amp; environment
&lt;/h2&gt;

&lt;p&gt;Firstly we'll work within a newly created directory because &lt;code&gt;~&lt;/code&gt; is already a mess&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;erpnext-stuff &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;erpnext-stuff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Helm repos
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add frappe https://helm.erpnext.com
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Namespaces
&lt;/h2&gt;

&lt;p&gt;Create two namespaces to separate our database from ERPNext.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns mariadb
kubectl create ns erpnext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Namespaces are a good way to separate concerns, but not a hard-requirement &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Save each Kubernetes resource inside the recently created &lt;code&gt;erpnext-stuff&lt;/code&gt; directory&lt;br&gt;&lt;br&gt;
&lt;em&gt;Do not run kubectl apply for now&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pvc.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;erpnext&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;erpnext-pvc&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;erpnext&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4Gi&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local-path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite the ERPNext helm chart creating a PVC, we actually want to avoid using their PVC &lt;a href="https://github.com/frappe/helm/blob/1081bbea689b4b1f6161453577b7077a53685c1c/erpnext/templates/pvc.yaml#L13"&gt;because the &lt;code&gt;accessMode&lt;/code&gt; is hardcoded to &lt;code&gt;RWX - ReadWriteMany&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When using K3s, &lt;code&gt;RWX&lt;/code&gt; is not possible with the &lt;a href="https://rancher.com/docs/k3s/latest/en/storage/"&gt;built-in storage controller 'local-path'&lt;/a&gt;. It only supports &lt;code&gt;RWO - ReadWriteOnce&lt;/code&gt; or below, and this is sufficient for our needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dumbing down what &lt;code&gt;RWO - ReadWriteOnce&lt;/code&gt; is, the volume can be mounted to support writes &lt;strong&gt;one node at a time&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Given we've provisioned our K3s cluster with a single node (default) &lt;code&gt;RWO&lt;/code&gt; will suffice for our needs in place of &lt;code&gt;RWX&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes &lt;a href="https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner"&gt;provide a list of possible storage classes&lt;/a&gt; along with their supported access modes, but this is mostly unrelated for our goal today.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;site-ingress.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;minimal-ingress&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;ingress.kubernetes.io/ssl-redirect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;/&lt;/span&gt;
        &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;service&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;erpnext&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ingress resource makes our built-in ingress-controller (ie. web server) traefik aware of a routing rule.&lt;/p&gt;

&lt;p&gt;In our case we're telling traefik that &lt;code&gt;http://localhost/&lt;/code&gt; will route through a service called &lt;code&gt;erpnext:80&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;erpnext&lt;/code&gt; being a service which will be provisioned when we install the ERPNext helm chart.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Still unsure what an Ingress is? think of it like a virtualhost when using Apache, or an nginx &lt;code&gt;server{}&lt;/code&gt; configuration&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;code&gt;erpnext-db-secret.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c29tZVNlY3VyZVBhc3N3b3Jk&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;mariadb-root-password&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This secret will hold the password of the database user which ERPNext will use to create sites, and perform queries with.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;c29tZVNlY3VyZVBhc3N3b3Jk&lt;/code&gt; is base64 for &lt;code&gt;someSecurePasword&lt;/code&gt; and it's the same password MariaDB will be told to use as root password, when we install later.&lt;/p&gt;

&lt;p&gt;Feel free to change as you see fit, and obviously do not use my defaults in a real or production environment.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;create-site-job.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Job&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;create-erp-site&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backoffLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;supplementalGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;1000&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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;create-site&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frappe/erpnext-worker:v12.17.0&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&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;sites-dir&lt;/span&gt;
            &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/frappe/frappe-bench/sites&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SITE_NAME"&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DB_ROOT_USER"&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MYSQL_ROOT_PASSWORD"&lt;/span&gt;
            &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&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;mariadb-root-password&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADMIN_PASSWORD"&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bigchungus"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSTALL_APPS"&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;erpnext"&lt;/span&gt;
      &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Never&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&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;sites-dir&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;erpnext-pvc&lt;/span&gt;
            &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned before, ERPNext is multi-tenanted. You can run many sites, and sites can have many companies.&lt;br&gt;&lt;br&gt;
One database is created per site, and there's also some configuration files created for the ERPNext setup to resolve sites to databases and beyond.&lt;/p&gt;

&lt;p&gt;The 'create-site' job is the recommended way to provision a new 'site' to your ERPNext setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paths of interest&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spec.template.spec.containers[0].image&lt;/code&gt; - should match version used in helm chart&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec.template.spec.containers[0].volumeMounts&lt;/code&gt; - volume required for ERPNext to resolve hostnames to databases, and other meta&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec.template.spec.containers[0].env[0]&lt;/code&gt; - &lt;code&gt;SITE_NAME&lt;/code&gt; is the FQDN where this ERPNext site is destined for&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec.template.spec.containers[0].env[3]&lt;/code&gt; - &lt;code&gt;ADMIN_PASSWORD&lt;/code&gt; we will use this to login with later&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec.template.spec.volumes[0]&lt;/code&gt; - volume mount based on our &lt;code&gt;pvc.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://helm.erpnext.com/kubernetes-resources/create-new-site-job"&gt;erpnext helm docs - creating new site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/frappe/erpnext-worker"&gt;dockerhub - frappe/erpnext-worker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;code&gt;maria-db-values.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rootPassword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;someSecurePassword"&lt;/span&gt;

&lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;[mysqld]&lt;/span&gt;
    &lt;span class="s"&gt;character-set-client-handshake=FALSE&lt;/span&gt;
    &lt;span class="s"&gt;skip-name-resolve&lt;/span&gt;
    &lt;span class="s"&gt;explicit_defaults_for_timestamp&lt;/span&gt;
    &lt;span class="s"&gt;basedir=/opt/bitnami/mariadb&lt;/span&gt;
    &lt;span class="s"&gt;plugin_dir=/opt/bitnami/mariadb/plugin&lt;/span&gt;
    &lt;span class="s"&gt;port=3306&lt;/span&gt;
    &lt;span class="s"&gt;socket=/opt/bitnami/mariadb/tmp/mysql.sock&lt;/span&gt;
    &lt;span class="s"&gt;tmpdir=/opt/bitnami/mariadb/tmp&lt;/span&gt;
    &lt;span class="s"&gt;max_allowed_packet=16M&lt;/span&gt;
    &lt;span class="s"&gt;bind-address=0.0.0.0&lt;/span&gt;
    &lt;span class="s"&gt;pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid&lt;/span&gt;
    &lt;span class="s"&gt;log-error=/opt/bitnami/mariadb/logs/mysqld.log&lt;/span&gt;
    &lt;span class="s"&gt;character-set-server=utf8mb4&lt;/span&gt;
    &lt;span class="s"&gt;collation-server=utf8mb4_unicode_ci&lt;/span&gt;

    &lt;span class="s"&gt;[client]&lt;/span&gt;
    &lt;span class="s"&gt;port=3306&lt;/span&gt;
    &lt;span class="s"&gt;socket=/opt/bitnami/mariadb/tmp/mysql.sock&lt;/span&gt;
    &lt;span class="s"&gt;default-character-set=utf8mb4&lt;/span&gt;
    &lt;span class="s"&gt;plugin_dir=/opt/bitnami/mariadb/plugin&lt;/span&gt;

    &lt;span class="s"&gt;[manager]&lt;/span&gt;
    &lt;span class="s"&gt;port=3306&lt;/span&gt;
    &lt;span class="s"&gt;socket=/opt/bitnami/mariadb/tmp/mysql.sock&lt;/span&gt;
    &lt;span class="s"&gt;pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ERPNext indicate your MariaDB instance should explicitly use &lt;a href="https://helm.erpnext.com/prepare-kubernetes/mariadb"&gt;this configuration&lt;/a&gt;. I'm going to assume they're primarily wanting you to have a &lt;code&gt;utf8mb4&lt;/code&gt; happy setup. &lt;/p&gt;

&lt;p&gt;You may notice the ERPNext helm chart instructions at &lt;a href="https://helm.erpnext.com/prepare-kubernetes/mariadb"&gt;https://helm.erpnext.com/prepare-kubernetes/mariadb&lt;/a&gt; are slightly different to my values above.&lt;/p&gt;

&lt;p&gt;This is because their documentation revolves around an older mariadb chart version. The newer chart version does not enable slave by default now too, so our config is simplified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://helm.erpnext.com/prepare-kubernetes/mariadb"&gt;erpnext helm documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ERPNext
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;erpnext-values.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;replicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;mariadbHost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mariadb.mariadb.svc.cluster.local"&lt;/span&gt;

&lt;span class="na"&gt;persistence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;existingClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;erpnext-pvc"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/frappe/helm/blob/master/erpnext/values.yaml"&gt;erpnext helm chart default values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.lovia.life/docs/infrastructure/erpnext/"&gt;rando handbook with useful information&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/frappe/helm/tree/master/erpnext"&gt;frappe/helm - github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.erpnext.com/kubernetes-resources/"&gt;erpnext helm docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bringing it all together
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Declare the PVC to your cluster. By default the PVC will not be provisioned until required (ie. container mounts it)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;--namespace&lt;/span&gt; erpnext &lt;span class="nt"&gt;-f&lt;/span&gt; pvc.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install MariaDB with our specific server configuration, and root password. &lt;code&gt;--wait&lt;/code&gt; will force the process to hang until all pods &amp;amp; services are healthy
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;mariadb &lt;span class="nt"&gt;--namespace&lt;/span&gt; mariadb bitnami/mariadb &lt;span class="nt"&gt;--version&lt;/span&gt; 9.3.1 &lt;span class="nt"&gt;-f&lt;/span&gt; maria-db-values.yaml &lt;span class="nt"&gt;--wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install ERPNext. All services and pods will be deployed essentially as scaffholding, for any sites provisioned afterward.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;erpnext &lt;span class="nt"&gt;--namespace&lt;/span&gt; erpnext frappe/erpnext &lt;span class="nt"&gt;--version&lt;/span&gt; 2.0.11 &lt;span class="nt"&gt;-f&lt;/span&gt; erpnext-values.yaml &lt;span class="nt"&gt;--wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Declare the MariaDB user account password secret for our upcoming job
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;--namespace&lt;/span&gt; erpnext &lt;span class="nt"&gt;-f&lt;/span&gt; erpnext-db-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the 'create site' job and stream the job's pod until completion
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;--namespace&lt;/span&gt; erpnext &lt;span class="nt"&gt;-f&lt;/span&gt; create-site-job.yaml &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; kubectl logs &lt;span class="nt"&gt;--namespace&lt;/span&gt; erpnext job/create-erp-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A successful completion will look something like:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; kubectl apply --namespace erpnext -f create-site-job.yaml &amp;amp;&amp;amp; kubectl logs --namespace erpnext job/create-erp-site

Attempt 1 to connect to mariadb.mariadb.svc.cluster.local:3306
Attempt 1 to connect to erpnext-redis-queue:12000
Attempt 1 to connect to erpnext-redis-cache:13000
Attempt 1 to connect to erpnext-redis-socketio:11000
Connections OK
Created user _334389048b872a53
Created database _334389048b872a53
Granted privileges to user _334389048b872a53 and database _334389048b872a53
Starting database import...
Imported from database /home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/framework_mariadb.sql

Installing frappe...
Updating DocTypes for frappe        : [========================================]
Updating country info               : [========================================]

Installing erpnext...
Updating DocTypes for erpnext       : [========================================]
Updating customizations for Address
*** Scheduler is disabled ***
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your ERPNext instance is operational, and you have a site setup. The final step is to declare the ingress, so we can route our ERPNext site's name to the ERPNext service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; site-ingress.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward --namespace kube-system svc/traefik 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now visit &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt; and you should be prompted with the ERPNext login page.&lt;/p&gt;

&lt;p&gt;u: &lt;code&gt;administrator&lt;/code&gt;&lt;br&gt;&lt;br&gt;
p: &lt;code&gt;bigchungus&lt;/code&gt;&lt;/p&gt;

</description>
      <category>erpnext</category>
      <category>kubernetes</category>
      <category>k3d</category>
      <category>k3s</category>
    </item>
  </channel>
</rss>
