<?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: Vincent Boon</title>
    <description>The latest articles on DEV Community by Vincent Boon (@vincentbean).</description>
    <link>https://dev.to/vincentbean</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%2F2686038%2F4d36e16c-7464-4596-8bbe-1b514ca06274.png</url>
      <title>DEV Community: Vincent Boon</title>
      <link>https://dev.to/vincentbean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vincentbean"/>
    <language>en</language>
    <item>
      <title>Docker environment for Laravel/Statamic package development</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Mon, 06 Apr 2026 19:38:07 +0000</pubDate>
      <link>https://dev.to/vincentbean/docker-environment-for-laravelstatamic-package-development-6en</link>
      <guid>https://dev.to/vincentbean/docker-environment-for-laravelstatamic-package-development-6en</guid>
      <description>&lt;p&gt;I’ve recently added a new feature to Vigilant, health checks. Vigilant is an open source monitoring application that is designed to monitor all aspects of a website. With health checks we can verify if critical processes are running such as a Laravel scheduler or Redis connection. Additionaly alongside these boolean type of checks I’ve added metrics which allow us to expose basic system statistics such as cpu, memory and disk space. We can even add the size of the log files in the app to get notified when these values peak!&lt;/p&gt;

&lt;p&gt;I created a &lt;a href="https://github.com/govigilant/vigilant-healthchecks-base" rel="noopener noreferrer"&gt;healthcheck-base&lt;/a&gt; package to define a common interface. Framework-specific packages build on top of this and integrate seamlessly, while remaining usable without Vigilant.&lt;/p&gt;

&lt;p&gt;Then separate packages which add Laravel or Statamic specific checks at:&lt;/p&gt;

&lt;p&gt;These are designed to work without Vigilant but integrate seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package development
&lt;/h2&gt;

&lt;p&gt;The usual method for package development would be to add the directory as a composer repository in an existing project and let composer make a symlink. This is documented &lt;a href="https://getcomposer.org/doc/05-repositories.md#path" rel="noopener noreferrer"&gt;here&lt;/a&gt; and a good way for development.&lt;/p&gt;

&lt;p&gt;I recently saw that one of Statamic’s core developer &lt;a href="https://github.com/duncanmcclean" rel="noopener noreferrer"&gt;Duncan McClean&lt;/a&gt; created an opinionated script called &lt;a href="https://github.com/duncanmcclean/tether" rel="noopener noreferrer"&gt;tether&lt;/a&gt; do manually do these symlinks. I’ve personally done the same with a simpler script that I wrote to avoid having to manually edit the composer json every time I need to work on a package, here is the script:&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Link a local package in a Laravel project&lt;/span&gt;
&lt;span class="c"&gt;# Usage: lpackage.sh &amp;lt;package-name&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;projects_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/code
&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;composer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which composer&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;vendorTarget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find vendor &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 2 &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="nv"&gt;$package&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$vendorTarget&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Package not found in vendor directory"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nv"&gt;packagePath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="nv"&gt;$projects_path&lt;/span&gt; &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="nv"&gt;$package&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$packagePath&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Package not found in projects directory"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Linking &lt;/span&gt;&lt;span class="nv"&gt;$packagePath&lt;/span&gt;&lt;span class="s2"&gt; to &lt;/span&gt;&lt;span class="nv"&gt;$vendorTarget&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$vendorTarget&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$packagePath&lt;/span&gt; &lt;span class="nv"&gt;$vendorTarget&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Reinstalling package dependencies"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$packagePath&lt;/span&gt;/composer.lock &lt;span class="nv"&gt;$packagePath&lt;/span&gt;/vendor
&lt;span class="nv"&gt;$composer&lt;/span&gt; &lt;span class="nt"&gt;--working-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$packagePath&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &amp;amp;&amp;gt; /dev/null
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In combination with an alias &lt;code&gt;alias lpackage="sh ~/dotfiles/scripts/lpackage.sh"&lt;/code&gt; this can be ran from a project directory to link any package with the prerequisite that the target package is already installed in the project. This script is intentionally minimal and tailored to my local setup&lt;/p&gt;

&lt;p&gt;The downside of this is that you have to have an pre existing project which isn’t a clean project so you always run the risk of something in the project affecting your package. A small example is calling &lt;code&gt;Model::unguard(&lt;/code&gt;) in the project's service provider which is easy to miss in a package but will causes issues when the package is included in another project.&lt;/p&gt;

&lt;p&gt;Don’t get me wrong, these methods are perfectly fine but sometimes it’s nice to have a clean install for a package so you are absolutely sure it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s build a Docker environment
&lt;/h2&gt;

&lt;p&gt;This environment needs to setup a clean application and install our package. Preferably symlink to our package so that our changes are effective immediately. I’m going to use Docker compose to setup additional services like MySQL and Redis. I also want to setup things like an admin account in the case of Statamic so we don’t have to create one each time the stack starts. Let’s start with the Dockerfile, to reduce overhead we’ll be using &lt;code&gt;artisan serve&lt;/code&gt; to handle requests and &lt;code&gt;schedule:work&lt;/code&gt; instead of cron. To run these we'll use supervisor.&lt;/p&gt;

&lt;p&gt;So what do we have to do?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Pick a base image for our container&lt;/li&gt;
&lt;li&gt; Add the required dependencies&lt;/li&gt;
&lt;li&gt; Install composer and create a new Laravel / Statamic project&lt;/li&gt;
&lt;li&gt; Install our package&lt;/li&gt;
&lt;li&gt; Run the required services&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s start with the base image, I’ve chosen to use &lt;code&gt;php:8.5-cli&lt;/code&gt; which is a Debian based image that includes PHP 8.5. So the start of our Dockerfile looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:8.5-cli WORKDIR /srv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to install our dependencies such as supervisor and PHP extensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; git unzip libzip-dev supervisor &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-php-ext-install zip pcntl &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pecl &lt;span class="nb"&gt;install &lt;/span&gt;redis &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-php-ext-enable redis &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get composer and create the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer:2 /usr/bin/composer /usr/bin/composer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer create-project &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt; laravel/laravel app
// Or for Statamic:
&lt;span class="k"&gt;RUN &lt;/span&gt;composer create-project &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt; statamic/statamic app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we’ve got a project setup we can copy our package and install it along with some other setup stuff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /srv/package&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /srv/app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer config minimum-stability dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer config prefer-stable &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer config repositories.statamic-healthchecks path /srv/package &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer require &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt; govigilant/statamic-healthchecks:dev-main laravel/horizon &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php artisan key:generate &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php artisan horizon:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we can copy the supervisor config and start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/www/html &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; /srv/app /var/www/html &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/log/supervisor
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/html&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; devenv/supervisord.conf /etc/supervisor/conf.d/healthchecks.conf&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["supervisord", "-n"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the final Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:8.5-cli&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /srv&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; git unzip libzip-dev supervisor &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-php-ext-install zip pcntl &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pecl &lt;span class="nb"&gt;install &lt;/span&gt;redis &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-php-ext-enable redis &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer:2 /usr/bin/composer /usr/bin/composer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer create-project &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt; laravel/laravel app
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /srv/package&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /srv/app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer config minimum-stability dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer config prefer-stable &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer config repositories.statamic-healthchecks path /srv/package &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer require &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt; govigilant/statamic-healthchecks:dev-main laravel/horizon &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php artisan key:generate &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php artisan horizon:install
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/www/html &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; /srv/app /var/www/html &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/log/supervisor
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/html&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; devenv/supervisord.conf /etc/supervisor/conf.d/healthchecks.conf&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["supervisord", "-n"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Statamic user
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For statamic we need a user to login to the control panel, for this I’ve created a&lt;/em&gt; &lt;a href="https://github.com/govigilant/statamic-healthchecks/tree/main/devenv/users" rel="noopener noreferrer"&gt;&lt;em&gt;users directory&lt;/em&gt;&lt;/a&gt; &lt;em&gt;in the package repository. Then in the Dockerfile I copy that into the Statamic folder:&lt;/em&gt; &lt;code&gt;_COPY devenv/users/ /srv/app/users/_&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The supervisor config looks like this, to avoid stdout being spammed I’ve put the logs in separate files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[supervisord]&lt;/span&gt;
&lt;span class="py"&gt;nodaemon&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;[program:php]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;php artisan serve --host=0.0.0.0 --port=8000&lt;/span&gt;
&lt;span class="py"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/www/html&lt;/span&gt;
&lt;span class="py"&gt;autostart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;autorestart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;stdout_logfile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/supervisor/php.log&lt;/span&gt;
&lt;span class="py"&gt;stderr_logfile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/supervisor/php.err.log&lt;/span&gt;
&lt;span class="nn"&gt;[program:horizon]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;php artisan horizon&lt;/span&gt;
&lt;span class="py"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/www/html&lt;/span&gt;
&lt;span class="py"&gt;autostart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;autorestart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;stdout_logfile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/supervisor/horizon.log&lt;/span&gt;
&lt;span class="py"&gt;stderr_logfile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/supervisor/horizon.err.log&lt;/span&gt;
&lt;span class="nn"&gt;[program:scheduler]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;php artisan schedule:work&lt;/span&gt;
&lt;span class="py"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/www/html&lt;/span&gt;
&lt;span class="py"&gt;autostart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;autorestart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;stdout_logfile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/supervisor/scheduler.log&lt;/span&gt;
&lt;span class="py"&gt;stderr_logfile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/supervisor/scheduler.err.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this down quickly, it starts the following services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  php artisan serve — host=0.0.0.0 — port=8000 — For handling HTTP requests&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://laravel.com/docs/12.x/horizon#running-horizon" rel="noopener noreferrer"&gt;php artisan horizon&lt;/a&gt; — For verifying if the Horizon checks in the package work&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://laravel.com/docs/12.x/scheduling#running-the-scheduler-locally" rel="noopener noreferrer"&gt;php artisan schedule:work&lt;/a&gt; — For verifying if the scheduler check in the package works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don’t know, supervisor is a program which lets you run other programs. When something crashes, supervisor will start it again. It’s a useful tool to keep services running with relatively simple configuration.&lt;/p&gt;

&lt;p&gt;Now that we have a Dockerfile we can create a compose file. This is what we use to compose the stack together, it defines the services we need including our clean application. With this file we can start the entire stack with a single command and because it’s all containerized we can be sure it’s the same on each host system.&lt;/p&gt;

&lt;p&gt;Notice that instead of an image for the app container we pass our Dockerfile. We map the package and environment file to the app container. This env file contains some basic configuration for the database, redis and package configuration.&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;services&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="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;..&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devenv/Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;8000:8000"&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="s"&gt;../:/srv/package&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./app.env:/var/www/html/.env&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
  &lt;span class="na"&gt;mysql&lt;/span&gt;&lt;span class="pi"&gt;:&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;mysql:8.4&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&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;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;laravel&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;laravel&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&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;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql-data:/var/lib/mysql&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&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;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&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;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt;&lt;span class="pi"&gt;]&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="s"&gt;./redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mysql-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it, we now have a dedicated clean environment to develop and test our package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further possibilities
&lt;/h2&gt;

&lt;p&gt;Right now I’m using this for development and I run these on a test server (not accessible from the internet) for testing the server side of healthchecks. I have a small script which updates each one by pulling the git changes and rebuilding the containers.&lt;/p&gt;

&lt;p&gt;It might also be a nice addition to spin up this Docker image in a pipeline to run an integration test for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading to Statamic 6
&lt;/h2&gt;

&lt;p&gt;When Statamic 6 was released, I needed to upgrade the &lt;a href="https://github.com/govigilant/statamic-healthchecks" rel="noopener noreferrer"&gt;statamic-healthchecks&lt;/a&gt; addon to be compatible with it. This meant updating the &lt;code&gt;statamic/cms&lt;/code&gt; constraint in &lt;code&gt;composer.json&lt;/code&gt; from &lt;code&gt;^5.0&lt;/code&gt; to &lt;code&gt;^6.0&lt;/code&gt;, updating the CI matrix, and migrating the Vue 2 frontend assets to Vue 3. Once the code changes were done, I needed to verify it all works end-to-end and this is exactly where the Docker environment proved its value.&lt;/p&gt;

&lt;p&gt;Without a dedicated environment I would have had to manually update an existing Statamic project, install the package there, and test it. With the Docker setup, I just rebuilt the image. The build process runs &lt;code&gt;composer create-project statamic/statamic&lt;/code&gt; from scratch every time, so a rebuild gave me a clean Statamic 6 install with the updated package installed.&lt;/p&gt;

&lt;p&gt;The container started cleanly and the health check endpoint returned the expected response including the Statamic-specific &lt;code&gt;statamic_stache&lt;/code&gt; check. The upgrade was verified against a clean Statamic 6 install without touching any existing project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should all packages ship an environment like this?
&lt;/h2&gt;

&lt;p&gt;Absolutely not. It depends on the package and the goals. The goal here is to have a clean install to test the package and to spin up a testing environment. The ‘traditional’ way of symlinking a package in a project is in most cases still the preferable way in most cases. So for most packages symlinking is enough. But when, a clean, reproducible setup is needed, Docker is hard to beat.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/docker-environment-for-package-development" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on April 6, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>docker</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to catch CVE’s on time</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Tue, 31 Mar 2026 17:53:43 +0000</pubDate>
      <link>https://dev.to/vincentbean/how-to-catch-cves-on-time-1982</link>
      <guid>https://dev.to/vincentbean/how-to-catch-cves-on-time-1982</guid>
      <description>&lt;h2&gt;
  
  
  What is a CVE?
&lt;/h2&gt;

&lt;p&gt;Common Vulnerabilities and Exposures (CVE) is the industry standard way of publishing security vulnerabilities in software. It dates back to 1999 and was funded by the US government.&lt;/p&gt;

&lt;p&gt;CVE’s are published as numbers starting with CVE and the year followed by a number: &lt;code&gt;CVE-{year}-{number}&lt;/code&gt;. For example &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-55182" rel="noopener noreferrer"&gt;CVE-2025-55182&lt;/a&gt;. Each CVE is for one vulnerability, a description is added explaining the issue and a score indicates how urgent it is. For example, &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-55182" rel="noopener noreferrer"&gt;CVE-2025-55182&lt;/a&gt; has a severity score of &lt;strong&gt;10&lt;/strong&gt;, this is very bad and needs to patched as fast as possible.&lt;/p&gt;

&lt;p&gt;The severity is typically measured using the CVSS (Common Vulnerability Scoring System). A high score, such as 10, indicates a critical vulnerability that should be patched immediately, while a lower score suggests the issue is harder to exploit or has less severe consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a CVE is published for software I use?
&lt;/h2&gt;

&lt;p&gt;When a CVE is published for software you use, it means a publicly known security vulnerability has been identified in one of your dependencies, services, or systems. At that point, attackers may begin analysing the vulnerability, and in some cases exploits become available very quickly.&lt;/p&gt;

&lt;p&gt;Typically, the software vendor will release a security advisory and, if possible, a patched version or mitigation guidance. Your responsibility is to determine whether you are affected, assess the severity of the issue, and decide how urgently it needs to be addressed.&lt;/p&gt;

&lt;p&gt;If the vulnerable component is exposed to the internet or runs with high privileges, even a medium-severity CVE can be a serious risk. On the other hand, a critical CVE might be less urgent if the affected feature is not enabled or reachable in your environment. Understanding the CVE details and your own setup is essential for determining the right action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find CVE’s
&lt;/h2&gt;

&lt;p&gt;One of the primary sources for CVE information is the &lt;strong&gt;National Vulnerability Database (NVD)&lt;/strong&gt;. The NVD aggregates CVE data and enriches it with severity scores, affected products, and additional metadata. It serves as the authoritative reference for many tools and security teams, even though it may not always be the fastest source when a vulnerability is first disclosed.&lt;/p&gt;

&lt;p&gt;Many other organizations and governments consume and republish this same data. For example, the &lt;strong&gt;Dutch National Cyber Security Centre (NCSC)&lt;/strong&gt; publishes CVE information and security advisories based on NVD data, often adding local context and guidance for organizations in the Netherlands. Similar initiatives exist in other countries, providing region-specific recommendations while relying on the same underlying CVE identifiers.&lt;/p&gt;

&lt;p&gt;Because most of these platforms are derived from the same source, they tend to contain largely overlapping information. The main differences are in how the data is presented, enriched, or prioritized, rather than in the CVEs themselves.&lt;/p&gt;

&lt;p&gt;While the NVD is a comprehensive and authoritative source, manually browsing it is cumbersome and inefficient. Hundreds of new or updated CVEs are added every week, many of which will never apply to your software, infrastructure, or configuration.&lt;/p&gt;

&lt;p&gt;Searching through the database, reading individual entries, and determining relevance requires significant time and expertise. Even then, it’s easy to miss important details such as affected versions, specific configurations, or whether a vulnerability is actually exploitable in your environment.&lt;/p&gt;

&lt;p&gt;For small environments this may be manageable, but as your software stack grows, manual CVE tracking quickly becomes impractical. This is why most teams move away from manual monitoring and rely on automation, filtering, and tooling to surface only the vulnerabilities that truly matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method One: Hope you find it
&lt;/h2&gt;

&lt;p&gt;The least effective way to catch CVEs is to simply hope you stumble across them. This usually means finding out about a vulnerability through news articles, social media, blog posts, or after someone else points it out.&lt;/p&gt;

&lt;p&gt;While vulnerabilities with high CVSS scores sometimes make headlines, most CVEs never do. Many serious issues are quietly disclosed in vendor advisories or databases like the NVD, without widespread attention. Relying on chance means you’re likely to learn about vulnerabilities late or not at all.&lt;/p&gt;

&lt;p&gt;This approach also tends to be reactive rather than proactive. By the time a vulnerability is trending online, attackers may already be exploiting it. In short, hoping to “hear about it” is unreliable and risky and doesn’t scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method Two: Mailing lists
&lt;/h2&gt;

&lt;p&gt;Mailing lists are a more proactive way to learn about new CVEs. Many software vendors, open-source projects, and security communities publish vulnerability disclosures through dedicated security mailing lists. Subscribing to these can give you early visibility into issues that affect the software you use.&lt;/p&gt;

&lt;p&gt;Examples include vendor-specific security lists, open-source project announcements, and broader security lists such as OSS-Security. These often contain detailed technical information, mitigation steps, and links to patches, making them valuable for understanding the real impact of a vulnerability.&lt;/p&gt;

&lt;p&gt;However, mailing lists come with their own challenges. They can be noisy, unstructured, and difficult to filter. Important alerts may get buried among less relevant messages, and keeping track of multiple lists quickly becomes overwhelming. While mailing lists are a significant improvement over hoping to find a CVE by chance, they still require manual effort and constant attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method Three: Automated tools
&lt;/h2&gt;

&lt;p&gt;Automated tools are by far the most reliable way to track CVEs at scale. These tools continuously monitor CVE databases and advisories, match vulnerabilities against your software stack, and alert you when something relevant appears. This removes most of the manual work and significantly reduces the chance of missing critical issues.&lt;/p&gt;

&lt;p&gt;Enterprise security platforms like &lt;strong&gt;CrowdStrike Spotlight&lt;/strong&gt; offer deep integration, asset awareness, and prioritization based on real-world exploitability. The downside is cost, these solutions are powerful, but often out of reach for smaller teams or individual developers.&lt;/p&gt;

&lt;p&gt;There are also more accessible alternatives. &lt;a href="https://www.opencve.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;OpenCVE&lt;/strong&gt;&lt;/a&gt;, for example, is an open-source and self-hostable platform that tracks CVEs and allows you to subscribe to specific vendors or products. It provides a good balance between control and automation, especially if you’re comfortable running your own tooling.&lt;/p&gt;

&lt;p&gt;Another option is &lt;a href="https://govigilant.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Vigilant&lt;/strong&gt;&lt;/a&gt;, which focuses specifically on website monitoring and includes CVE monitoring. It provides alerts and filtering to help you focus on vulnerabilities that matter to you, without requiring a full enterprise security stack. Tools like this are well-suited for teams that want timely notifications without the overhead of heavy infrastructure.&lt;/p&gt;

&lt;p&gt;Automated tools turn CVE tracking from a manual, error-prone task into a continuous background process. While no tool completely replaces human judgment, automation is essential if you want to catch CVEs on time and respond before they become incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose the right method
&lt;/h2&gt;

&lt;p&gt;The right way to track CVEs depends on what you are responsible for securing. For general software projects or libraries, a tool like &lt;a href="https://www.opencve.io/" rel="noopener noreferrer"&gt;OpenCVE&lt;/a&gt; can be sufficient, as it allows you to subscribe to specific products and vendors.&lt;/p&gt;

&lt;p&gt;However, website owners face a unique set of challenges. Websites are built from components that change over time. CMS platforms, themes, plugins, and third-party dependencies and keeping all of these safe involves more than just checking for raw CVE publications. Vigilant’s integrated monitoring means you can track vulnerabilities alongside uptime, broken links, performance, certificates, and DNS issues, all in one dashboard and with minimal setup.&lt;/p&gt;

&lt;p&gt;As soon as security becomes an ongoing responsibility rather than an occasional task, manual tracking stops working. For website owners, tools that focus on real exposure, automatic detection, and clear alerts are essential.&lt;/p&gt;

&lt;p&gt;Ultimately, the best method is the one that tells you &lt;em&gt;when your website is actually at risk&lt;/em&gt;, without overwhelming you with irrelevant information. For website owners, that means using a tool built specifically for monitoring websites.&lt;/p&gt;

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

&lt;p&gt;CVE tracking is essential for keeping software and websites secure, but the sheer volume of vulnerabilities published each week makes manual monitoring impractical. From hoping to stumble across CVEs online, to following mailing lists, to using automated tools, there are clear trade-offs in effectiveness, effort, and coverage.&lt;/p&gt;

&lt;p&gt;For website owners, automated platforms like Vigilant provide a practical solution. By monitoring your site’s technologies and alerting you to relevant vulnerabilities, they turn CVE tracking from a time-consuming task into a continuous, actionable process. Combined with broader monitoring features, these tools help ensure that you catch critical issues before they can be exploited.&lt;/p&gt;

&lt;p&gt;Ultimately, staying on top of CVEs requires a combination of the right tools, smart prioritization, and consistent attention. Using automation tailored to your environment lets you focus on what really matters: keeping your systems and websites secure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/how-to-catch-cves-on-time" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on March 30, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
      <category>cve</category>
    </item>
    <item>
      <title>How To Prevent Website Downtime</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Thu, 26 Mar 2026 19:59:00 +0000</pubDate>
      <link>https://dev.to/vincentbean/how-to-prevent-website-downtime-1i6l</link>
      <guid>https://dev.to/vincentbean/how-to-prevent-website-downtime-1i6l</guid>
      <description>&lt;p&gt;Downtime can happen for many reasons: infrastructure failures, network issues, software bugs, or simple configuration mistakes. Some of these problems are unavoidable, but most can be prevented by understanding how websites work and where failures typically occur.&lt;/p&gt;

&lt;p&gt;In this article, we’ll break down how downtime happens, how traffic reaches your website, and what can go wrong along the way. More importantly, we’ll look at practical ways to reduce risk through better architecture, multiple environments, and effective monitoring. The goal is not just to react faster to outages, but to prevent them from happening in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Downtime Happens
&lt;/h2&gt;

&lt;p&gt;Downtime occurs when your website is unable to respond correctly to incoming requests. This doesn’t always mean the server is completely offline. A website can be considered “down” when it returns errors, times out, or becomes so slow that users effectively can’t use it.&lt;/p&gt;

&lt;p&gt;In many cases, downtime is the result of a chain of failures rather than a single event. A small configuration change, an unexpected spike in traffic, or a failing dependency can cascade into a full outage. Because modern websites are made up of multiple components, a problem in any one of them can make the entire site unreachable.&lt;/p&gt;

&lt;p&gt;It’s also important to distinguish between partial and total downtime. Partial downtime might affect only certain pages, users, or regions, while total downtime means no one can access the site at all. Both are damaging, but partial outages are often harder to detect without proper monitoring in place.&lt;/p&gt;

&lt;p&gt;Understanding how downtime happens is the first step toward preventing it. To do that, it helps to look at how traffic actually reaches your website and how many points of failure exist along that path.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Traffic Reaches Your Website
&lt;/h2&gt;

&lt;p&gt;When a user visits your website, a lot happens behind the scenes before a page is displayed. It normally goes so fast that you probably don’t even notice how many things are happening. Each step in this process introduces a potential point of failure, which is why understanding the request flow is essential for preventing downtime.&lt;/p&gt;

&lt;p&gt;It starts with a DNS lookup. When someone types your domain name in their browser a lookup takes place. The user’s browser must know where to go to reach your website. This is done through the Domain Name System (DNS) which returns the IP address of your server.&lt;br&gt;
If DNS is misconfigured or unavailable, the request never reaches your infrastructure at all. Incorrect changes can also leave your site unavailable as DNS updates are slow and can take hours.&lt;/p&gt;

&lt;p&gt;Once the correct IP address is found, the browser establishes a network connection to your server. This connection passes through multiple networks and routers before reaching your hosting provider. Even if your server is healthy, network issues along this path can prevent users from connecting.&lt;/p&gt;

&lt;p&gt;After the connection is established, the web server processes the request. This may involve application logic, databases, external APIs, or background services. If any of these components fail or respond too slowly, the request can time out or return an error.&lt;/p&gt;

&lt;p&gt;Finally, the server sends a response back to the user’s browser. If everything works as expected, the page loads. If not, the user experiences an error, a blank page, or excessive loading times which are often perceived as downtime.&lt;/p&gt;

&lt;p&gt;Because traffic depends on so many interconnected systems, preventing downtime means identifying which parts are most likely to fail and preparing for those failures in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Things Can Go Wrong
&lt;/h2&gt;

&lt;p&gt;Now that we understand how traffic reaches your website, it becomes clear how many things can fail along the way. Even simple websites depend on multiple layers of infrastructure and software, and a problem in any of these layers can result in downtime.&lt;/p&gt;

&lt;p&gt;Most website outages fall into three broad categories: server failures, network failures, and website code failures. While the symptoms may look similar to users, errors, timeouts, or blank pages the underlying causes are very different. Identifying which category a problem belongs to is crucial for preventing it from happening again.&lt;/p&gt;

&lt;p&gt;Let’s look at each of these failure types in more detail, starting with server-related issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Failures
&lt;/h2&gt;

&lt;p&gt;Server failures are one of the most common causes of website downtime. Even when your code is correct and your network is stable, the underlying machine running your website can still fail.&lt;/p&gt;

&lt;p&gt;A server can go down for many reasons. Hardware issues, operating system crashes, or forced reboots by the hosting provider can instantly make your website unavailable. In virtualized or cloud environments, servers may also be terminated or migrated without warning if resource limits are exceeded.&lt;/p&gt;

&lt;p&gt;Resource exhaustion is another frequent cause. When a server runs out of CPU, memory, or disk space, it may slow down dramatically or stop responding altogether. This often happens during traffic spikes, background jobs running at the wrong time, or memory leaks in long-running processes.&lt;/p&gt;

&lt;p&gt;Misconfiguration can be just as damaging. Incorrect web server settings, broken service dependencies, or failed updates can prevent the server from starting properly. In these cases, the server may be online, but unable to serve requests correctly.&lt;/p&gt;

&lt;p&gt;Because servers are a single point of execution for your application, failures at this level tend to have an immediate and visible impact. Reducing downtime means not only choosing reliable infrastructure, but also monitoring server health and preparing for failure rather than assuming stability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Failures
&lt;/h2&gt;

&lt;p&gt;Network failures occur when users are unable to reach your server, even if the server itself is running correctly. These issues are often harder to diagnose because the problem may exist outside of your direct control.&lt;/p&gt;

&lt;p&gt;One common cause is DNS-related problems. Incorrect DNS records, expired domains, or outages at DNS providers can prevent traffic from resolving to your server’s IP address. When this happens, users never reach your infrastructure at all.&lt;/p&gt;

&lt;p&gt;Routing issues between networks can also cause downtime. Traffic on the internet passes through multiple autonomous systems before reaching your hosting provider. If a routing path is misconfigured or temporarily unavailable, users in certain regions may experience outages while others do not. This is why it’s important to monitor from multiple locations around the world.&lt;/p&gt;

&lt;p&gt;Firewalls and security rules are another frequent source of network-related downtime. Overly strict rules, accidental IP blocks, or misconfigured rate limits can block legitimate traffic. From a user’s perspective, this looks exactly like the website is down.&lt;/p&gt;

&lt;p&gt;Because network failures can be regional or intermittent, they are easy to miss without external monitoring. Preventing these issues requires careful configuration, redundancy where possible, and visibility into how your website is accessed from different locations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Website Code Failures
&lt;/h2&gt;

&lt;p&gt;Website code failures happen when the application itself is unable to handle requests correctly. Unlike server or network issues, these failures often occur immediately after a change is made, such as a deployment or configuration update. But with aggressive caching it may also take some time for issues to reach users.&lt;/p&gt;

&lt;p&gt;Bugs in application logic can cause errors that prevent pages from loading or APIs from responding. An unhandled exception, infinite loop, or missing dependency can crash a process or return repeated server errors. In some cases, a single faulty request can degrade performance for all users.&lt;/p&gt;

&lt;p&gt;Changes to code or configuration are a common trigger. Deployments that haven’t been properly tested may introduce breaking changes, incompatible library versions, or invalid settings. Even small changes can have large effects if they impact core request handling or database access.&lt;/p&gt;

&lt;p&gt;External dependencies can also cause code-related downtime. If your application relies on third-party APIs, payment providers, or authentication services, failures in those systems can cascade into your own application. Without proper timeouts and fallbacks, your website may hang or fail entirely.&lt;/p&gt;

&lt;p&gt;Because code failures are often self-inflicted, they are also among the most preventable. Clear deployment practices, proper testing, and good error handling significantly reduce the risk of taking a website offline through software changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Prevent Each Type of Problem
&lt;/h2&gt;

&lt;p&gt;Preventing website downtime starts with addressing each failure category directly. While no system can be made completely failure-proof, you can significantly reduce both the frequency and impact of outages by designing for failure and adding safeguards at every layer.&lt;/p&gt;

&lt;p&gt;Instead of relying on a single fix, effective prevention combines infrastructure choices, configuration discipline, and operational practices. The goal is to detect problems early, limit their blast radius, and recover quickly when something does go wrong.&lt;/p&gt;

&lt;p&gt;Below, we’ll look at practical steps to prevent downtime caused by server issues, network problems, and website code failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Server Failures
&lt;/h2&gt;

&lt;p&gt;Reducing server-related downtime begins with eliminating single points of failure. Relying on one server means that any crash, reboot, or resource issue will immediately take your website offline. Using multiple servers or managed platforms with built-in redundancy greatly improves resilience.&lt;/p&gt;

&lt;p&gt;Monitoring server health is equally important. Tracking CPU usage, memory consumption, disk space, and running processes helps identify problems before they cause outages. Automated alerts allow you to respond quickly when thresholds are exceeded.&lt;/p&gt;

&lt;p&gt;Automated recovery can further reduce downtime. Restarting crashed services, replacing unhealthy instances, or scaling resources during traffic spikes can often resolve issues without manual intervention. These mechanisms ensure that short-lived problems don’t turn into prolonged outages.&lt;/p&gt;

&lt;p&gt;Finally, keep servers predictable. Apply updates carefully, document configuration changes, and avoid unnecessary complexity. Stable, well-understood systems fail less often and are easier to recover when they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Network Failures
&lt;/h2&gt;

&lt;p&gt;Preventing network-related downtime is about ensuring users can reach your website reliably, even when parts of the network experience issues. While you can’t control every network between your server and users, careful configuration and monitoring can reduce risks.&lt;/p&gt;

&lt;p&gt;Start with a solid DNS setup. Use reputable DNS providers and ensure redundancy with multiple authoritative name servers. Correct configuration and &lt;a href="https://govigilant.io/feature/dns-monitoring" rel="noopener noreferrer"&gt;monitoring DNS records&lt;/a&gt; help to know when something is modified.&lt;/p&gt;

&lt;p&gt;Pay close attention to firewall and security rules. Overly strict rules, accidental IP blocks, or misconfigured access controls can prevent legitimate users from reaching your site. Logging and monitoring network traffic helps identify these issues before they affect users.&lt;/p&gt;

&lt;p&gt;Routing problems within your own network or hosting provider can also cause outages. Redundant network interfaces, proper routing configurations, and regular connectivity checks help ensure traffic continues to flow even when part of the network fails. Choose a reliable hosting provider for this.&lt;/p&gt;

&lt;p&gt;Finally, &lt;a href="https://govigilant.io/feature/uptime-monitor" rel="noopener noreferrer"&gt;monitor accessibility from multiple locations&lt;/a&gt;. Network failures can be regional, and relying on a single test point may not reveal them. Multi-location monitoring ensures you catch problems that affect only some users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Website Code Failures
&lt;/h2&gt;

&lt;p&gt;Preventing downtime caused by website code requires discipline in development, testing, and deployment. Unlike server or network issues, these failures are often within your control, which makes them highly preventable.&lt;/p&gt;

&lt;p&gt;The first step is thorough testing. Unit tests, integration tests, and automated end-to-end tests help catch errors before they reach production. Testing should cover both expected use cases and edge cases that could cause failures under load or unusual conditions.&lt;/p&gt;

&lt;p&gt;Staged deployments are also critical. Deploying changes first to a development or staging environment allows you to verify functionality and performance before affecting real users. This minimizes the risk of introducing breaking changes directly to production.&lt;/p&gt;

&lt;p&gt;Version control and rollback strategies provide safety nets. If a deployment introduces an unexpected bug, having a tested rollback plan allows you to restore the previous working version quickly, minimizing downtime.&lt;/p&gt;

&lt;p&gt;Finally, implement robust error handling in your code. Timeouts, retries, and graceful degradation prevent single points of failure from crashing the entire application. Even when something goes wrong, your website can continue serving users without a full outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Multiple Environments
&lt;/h2&gt;

&lt;p&gt;One of the most effective ways to prevent downtime is to use multiple environments for your website: development, staging, and production. Each environment serves a distinct purpose and provides a controlled space to test changes before they affect real users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development environments&lt;/strong&gt; are where new features and bug fixes are built. Developers can experiment, run tests, and identify issues without risking production downtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staging environments&lt;/strong&gt; mirror production as closely as possible. This is where you validate deployments, test integrations, and simulate real-world traffic. Staging allows you to catch configuration mistakes, performance issues, or code regressions before they reach your live site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production&lt;/strong&gt; is where your website serves real users. By the time code reaches this environment, it should already be thoroughly tested and verified in development and staging.&lt;/p&gt;

&lt;p&gt;Using multiple environments creates a safety net. Problems are discovered earlier, deployments become more predictable, and the risk of downtime caused by human error or unforeseen bugs is greatly reduced. It also supports better monitoring and rollback strategies, since you can test fixes in staging before applying them to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring: The Key to Prevention
&lt;/h2&gt;

&lt;p&gt;Even with strong infrastructure and careful development practices, issues can still happen. That’s why &lt;strong&gt;monitoring isn’t just a safety net. It’s a core part of preventing downtime&lt;/strong&gt;. Monitoring gives you visibility into your site’s health so you can detect and respond to problems &lt;em&gt;before your users notice&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One powerful tool for this is &lt;strong&gt;Vigilant&lt;/strong&gt;, a monitoring platform designed to watch your website from outside your infrastructure and alert you instantly when something goes wrong. Unlike simple “is it online” checks, Vigilant helps you spot real problems that affect your users and your business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instant Uptime Alerts
&lt;/h2&gt;

&lt;p&gt;Vigilant continuously checks your website from multiple global locations and alerts you &lt;em&gt;as soon as&lt;/em&gt; your site stops responding. Because it verifies outages from more than one location before notifying you, you avoid false alarms and only act on real issues. This ensures you know about actual downtime quickly before customers notice or conversions are lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Historical Data and Reliability Metrics
&lt;/h2&gt;

&lt;p&gt;Knowing &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;how often&lt;/em&gt; downtime happens is critical to improving reliability. Vigilant stores detailed historical data with timestamps that help you identify patterns, measure uptime percentages, and track whether your prevention efforts are working over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Uptime
&lt;/h2&gt;

&lt;p&gt;Good monitoring goes beyond simple reachability. Vigilant includes additional checks that help catch the kinds of failures that don’t always look like downtime but still harm user experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Link issue detection&lt;/strong&gt; to catch broken internal URLs&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;DNS monitoring&lt;/strong&gt; so misconfigurations or unexpected changes don’t take your site offline&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Certificate monitoring&lt;/strong&gt; to warn you before HTTPS certificates expire and trigger browser warnings&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Performance tracking (Lighthouse)&lt;/strong&gt; to spot slowdowns that lead to churn
These extended checks broaden your visibility and help prevent subtle failures from becoming major outages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Smart, customizable Notifications
&lt;/h2&gt;

&lt;p&gt;A monitoring system is only useful if you actually see its alerts. Vigilant supports notifications through the channels you already use. Email, Slack, and others so you can react in &lt;em&gt;minutes instead of hours&lt;/em&gt; when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy to Set Up and Use
&lt;/h2&gt;

&lt;p&gt;Vigilant is designed to get started quickly. You simply add your site and configure the monitors you want, and Vigilant begins tracking uptime and other metrics almost immediately. Its sensible defaults mean you don’t need deep expertise to begin protecting your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Options
&lt;/h2&gt;

&lt;p&gt;For websites with high traffic or strict uptime requirements, there are additional strategies that go beyond basic monitoring and environment separation. These advanced options help your site remain available even under unusual conditions or extreme load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed or Multi-Region Architecture
&lt;/h2&gt;

&lt;p&gt;Deploying your website across multiple regions reduces the impact of a single server or datacenter failing. If one region experiences an outage, traffic can be routed to another region, keeping your site online for most users. This approach is especially useful for global websites where downtime in one area can affect a large number of users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load Balancers &amp;amp; Failovers
&lt;/h2&gt;

&lt;p&gt;Load balancers distribute incoming traffic across multiple servers, preventing any single server from becoming a bottleneck. They can automatically reroute traffic to healthy servers if one fails, providing seamless failover. Combined with redundant infrastructure, this ensures that individual failures don’t result in visible downtime.&lt;br&gt;
Do ensure you have multiple load balancers, otherwise you’re just moving the single point of failure ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-Scaling to Handle Traffic Spikes
&lt;/h2&gt;

&lt;p&gt;Unexpected spikes in traffic are a common cause of downtime, especially for events, product launches, or viral content. Auto-scaling automatically adds server capacity when load increases and scales down during quieter periods. This ensures your site remains responsive without over-provisioning resources all the time.&lt;/p&gt;

&lt;p&gt;These advanced techniques require careful planning and investment, but they significantly increase resilience and reduce the risk of downtime. They are most beneficial for sites where availability is critical to revenue, reputation, or user trust.&lt;/p&gt;

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

&lt;p&gt;Website downtime can happen to anyone, but most outages are preventable. By understanding how traffic reaches your website and where failures can occur, you can take proactive steps to protect your site.&lt;/p&gt;

&lt;p&gt;Start with solid infrastructure and multiple environments, implement careful deployment and testing practices, and use vigilant monitoring to catch issues before they affect users. For high-traffic or mission-critical websites, advanced strategies like multi-region deployments, load balancing, and auto-scaling provide an additional layer of resilience.&lt;/p&gt;

&lt;p&gt;Ultimately, preventing downtime isn’t about avoiding every possible problem, it’s about designing systems, processes, and monitoring in a way that allows you to detect issues quickly, respond effectively, and keep your website online and performing for your users.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/how-to-prevent-website-downtime" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on March 25, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The 7 Levels of Website Monitoring -Learn how to monitor your entire website</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:26:03 +0000</pubDate>
      <link>https://dev.to/vincentbean/the-7-levels-of-website-monitoring-learn-how-to-monitor-your-entire-website-530i</link>
      <guid>https://dev.to/vincentbean/the-7-levels-of-website-monitoring-learn-how-to-monitor-your-entire-website-530i</guid>
      <description>&lt;h2&gt;
  
  
  Why monitor your website?
&lt;/h2&gt;

&lt;p&gt;When your business depends on your website it is essential that it’s functioning correctly. Downtime, slow pages or important functions, such as an add to cart button, not working can have serious consequences.&lt;br&gt;
You can of course manually verify if these things are working but that’s not an effective use of your time and you will probably find out when the damage is already done.&lt;/p&gt;

&lt;p&gt;This is where monitoring your website comes in, it will automate these checks for you and notify you when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 0 - No monitoring
&lt;/h2&gt;

&lt;p&gt;At level 0 there is no automated monitoring at all. The only way you know if something is wrong is by manually checking your website or hope a user tells you.&lt;/p&gt;

&lt;p&gt;This usually looks like:&lt;/p&gt;

&lt;p&gt;The problem isn’t that manual checks don’t work. It’s that they are a waste of your valuable time and they’re not continuous.&lt;/p&gt;

&lt;p&gt;Websites don’t break on your schedule. They break at 3 AM, during deployments, after dependency updates, or when traffic spikes. A quick check in the morning tells you nothing about what happened overnight.&lt;/p&gt;

&lt;p&gt;Even worse, manual checks are reactive. You only notice issues &lt;em&gt;after&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;For hobby projects or internal tools with zero impact, level 0 might be acceptable. For anything even remotely business-critical, it’s a risk you’re probably taking without realizing it.&lt;/p&gt;

&lt;p&gt;Level 0 isn’t really a strategy. It’s just hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1 - Uptime &amp;amp; Latency Monitoring
&lt;/h2&gt;

&lt;p&gt;Level 1 is where monitoring actually begins.&lt;/p&gt;

&lt;p&gt;At this level you automatically check whether your website is reachable and how long it takes to respond. A monitoring service such as &lt;a href="https://govigilant.io" rel="noopener noreferrer"&gt;Vigilant&lt;/a&gt; sends a request to your site at a fixed interval and verifies that it gets a successful response back.&lt;/p&gt;

&lt;p&gt;This answers two fundamental questions:&lt;/p&gt;

&lt;p&gt;If the check fails or the response time crosses a threshold, you get notified.&lt;/p&gt;

&lt;p&gt;This already removes a huge blind spot compared to level 0. Instead of finding out from users or random manual checks, you’re alerted as soon as your website becomes unreachable.&lt;/p&gt;

&lt;p&gt;However, it’s important to understand what uptime monitoring really measures and what it doesn’t.&lt;/p&gt;

&lt;p&gt;An uptime check usually:&lt;/p&gt;

&lt;p&gt;That means your server could be returning a 200 response while:&lt;/p&gt;

&lt;p&gt;From the monitor’s perspective, everything looks fine.&lt;/p&gt;

&lt;p&gt;Latency monitoring helps a bit by catching slow responses early. A site that takes 8 seconds to respond is technically “up”, but for users it might as well be broken.&lt;/p&gt;

&lt;p&gt;Level 1 is essential. It gives you basic awareness and fast alerts when your site goes completely down or becomes very slow.&lt;/p&gt;

&lt;p&gt;But it’s still a blunt instrument. It tells you &lt;em&gt;that&lt;/em&gt; something is wrong, not &lt;em&gt;what&lt;/em&gt; is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1.5 - Worldwide Uptime &amp;amp; Latency Monitoring
&lt;/h2&gt;

&lt;p&gt;Level 1.5 builds directly on basic uptime and latency monitoring, but fixes one of its biggest blind spots: &lt;strong&gt;location&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With standard uptime monitoring, all checks come from a single region. If your site works from that location, the monitor reports everything as healthy even if users elsewhere can’t reach your site at all. What’s even worse is monitoring from the same datacenter or server that your website is running on, the monitoring service will then never go over the internet!&lt;/p&gt;

&lt;p&gt;At this level, the same uptime and latency checks are executed from multiple geographic locations around the world.&lt;/p&gt;

&lt;p&gt;This helps answer new, very real questions:&lt;/p&gt;

&lt;p&gt;This matters more often than people expect.&lt;/p&gt;

&lt;p&gt;Problems that worldwide monitoring catches:&lt;/p&gt;

&lt;p&gt;From your perspective everything might look fine. From a user in another continent, your site could be completely down.&lt;/p&gt;

&lt;p&gt;Latency data becomes much more useful at this level as well. A response time of 300 ms in Europe might be 2.5 seconds in Asia. That’s not a backend issue, it’s an infrastructure and delivery problem you wouldn’t see from a single location.&lt;/p&gt;

&lt;p&gt;Level 1.5 doesn’t add new types of checks, but it adds &lt;strong&gt;context&lt;/strong&gt;. When an alert fires, you can immediately tell whether:&lt;/p&gt;

&lt;p&gt;For websites with an international audience, level 1.5 isn’t a luxury. It’s the difference between thinking your site is healthy and knowing that your users can actually reach it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2 - Certificate Monitoring
&lt;/h2&gt;

&lt;p&gt;At level 2, monitoring goes beyond availability and speed and starts covering security basics specifically, SSL/TLS certificates.&lt;/p&gt;

&lt;p&gt;Every website that uses HTTPS (which should be all of them) relies on a certificate to encrypt traffic and prove identity. When a certificate expires or is misconfigured, users get scary browser warnings, which can instantly erode trust and probably stop them from visiting your site.&lt;/p&gt;

&lt;p&gt;Certificate monitoring automates the task of keeping an eye on:&lt;/p&gt;

&lt;p&gt;Without monitoring, you might only notice a certificate problem when a user reports a warning or your own browser blocks access. That’s reactive and by that time, your site’s reputation can already take a hit.&lt;/p&gt;

&lt;p&gt;With certificate monitoring in place, you can:&lt;/p&gt;

&lt;p&gt;Nowadays most renewals are automatic but automatic processes can fail causing your certificates to break. Better to prevent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 3 - Broken Link Monitoring
&lt;/h2&gt;

&lt;p&gt;At level 3, monitoring starts to look &lt;em&gt;inside&lt;/em&gt; your website instead of just at the front door.&lt;/p&gt;

&lt;p&gt;Broken link monitoring works by crawling your website and following links, similar to how a search engine bot does. It checks whether the pages and resources your site points to actually return valid responses.&lt;/p&gt;

&lt;p&gt;This helps you catch:&lt;/p&gt;

&lt;p&gt;From an uptime monitor’s point of view, your site can be perfectly healthy while large parts of it are effectively broken for users.&lt;/p&gt;

&lt;p&gt;A few common ways broken links appear:&lt;/p&gt;

&lt;p&gt;For users, this creates friction and frustration. For search engines, it’s a quality signal. Too many broken links can hurt crawl efficiency and SEO performance.&lt;/p&gt;

&lt;p&gt;Broken link monitoring turns this into a proactive process. Instead of stumbling upon issues months later, you get a report or alert when new errors appear.&lt;/p&gt;

&lt;p&gt;This level shifts monitoring from “is my site up?” to “is my site usable?”&lt;/p&gt;

&lt;p&gt;It’s also the first level where monitoring becomes &lt;em&gt;comprehensive&lt;/em&gt;, because it touches many pages instead of a single URL.&lt;/p&gt;

&lt;p&gt;Once you have this in place, the next logical step is not just whether pages load but how well they load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 4 - Performance Monitoring
&lt;/h2&gt;

&lt;p&gt;At level 4, monitoring focuses on how your website &lt;em&gt;feels&lt;/em&gt; to users, not just whether pages return a response.&lt;/p&gt;

&lt;p&gt;Performance monitoring measures things like:&lt;/p&gt;

&lt;p&gt;A common way to do this is by running tools like &lt;a href="https://govigilant.io/feature/lighthouse-monitoring" rel="noopener noreferrer"&gt;Google Lighthouse&lt;/a&gt; on your pages at regular intervals.&lt;/p&gt;

&lt;p&gt;This level catches issues that uptime and link checks completely miss:&lt;/p&gt;

&lt;p&gt;Performance problems are dangerous because they don’t feel like “downtime.” Your site is up, links work, nothing is broken but users bounce, conversions drop, and SEO rankings slowly decline.&lt;/p&gt;

&lt;p&gt;By monitoring performance over time, you get:&lt;/p&gt;

&lt;p&gt;This also makes performance actionable. Instead of running Lighthouse once during development and forgetting about it, you treat performance as a living part of your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 5 - Synthetic Monitoring
&lt;/h2&gt;

&lt;p&gt;Synthetic monitoring runs scripted user flows in real browsers. Instead of checking a single page, it simulates actions such as:&lt;/p&gt;

&lt;p&gt;These flows are executed on a schedule, from start to finish, and every step is verified.&lt;/p&gt;

&lt;p&gt;This level catches problems that all previous levels miss:&lt;/p&gt;

&lt;p&gt;From the outside, your site can look perfectly healthy:&lt;/p&gt;

&lt;p&gt;And yet, your core business flow is dead.&lt;/p&gt;

&lt;p&gt;Synthetic monitoring gives you confidence in what actually matters: that users can complete the actions you depend on.&lt;/p&gt;

&lt;p&gt;This used to be difficult for non technical people to setup as it required code to write the test cases. But with the rise of AI it is now possible to control a browser with English sentences. An example of that is &lt;a href="https://govigilant.io/feature/flows" rel="noopener noreferrer"&gt;Vigilant’s Flows&lt;/a&gt; feature allowing everyone to easily write and understand these flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 6 -Application Health Monitoring
&lt;/h2&gt;

&lt;p&gt;Level 6 focuses on the internal health of your application, not just what’s visible from the outside.&lt;/p&gt;

&lt;p&gt;Application health monitoring relies on &lt;a href="https://govigilant.io/feature/healthchecks" rel="noopener noreferrer"&gt;health checks&lt;/a&gt;: dedicated endpoints or signals that report whether critical parts of your system are functioning correctly. Instead of asking “does this page load?”, you ask “is the application actually healthy?”&lt;/p&gt;

&lt;p&gt;A proper health check can verify things like:&lt;/p&gt;

&lt;p&gt;From the outside, your site might still return a 200 OK, while internally it’s already in trouble. For example:&lt;/p&gt;

&lt;p&gt;Uptime monitors won’t catch this. Synthetic flows might not hit the affected path. Health checks surface these issues &lt;em&gt;before&lt;/em&gt; they turn into user-facing failures.&lt;/p&gt;

&lt;p&gt;Level 7 represents a mindset shift. Monitoring is no longer just observing outcomes, it’s observing &lt;em&gt;system state&lt;/em&gt;. You gain confidence not only that your application works right now, but that it’s operating within safe boundaries.&lt;/p&gt;

&lt;p&gt;With this level in place, monitoring becomes deeply tied to how your application is built and operated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 7 - Security Monitoring
&lt;/h2&gt;

&lt;p&gt;At level 7, monitoring shifts from availability and functionality to risk.&lt;/p&gt;

&lt;p&gt;Security monitoring continuously checks your website and its dependencies for known vulnerabilities, misconfigurations, and exposures. This often includes scanning for:&lt;/p&gt;

&lt;p&gt;The key difference at this level is intent. Everything before this level focuses on “is my site working?”&lt;br&gt;
Security monitoring asks: &lt;em&gt;“Is my site safe from attackers?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Security issues rarely announce themselves. There’s no downtime, no broken page, no failed request. Your site keeps working until it doesn’t, or until data is leaked, defaced, or abused.&lt;/p&gt;

&lt;p&gt;Without monitoring, you’re relying on:&lt;/p&gt;

&lt;p&gt;With security monitoring, you get:&lt;/p&gt;

&lt;p&gt;This level doesn’t replace proper security practices, but it adds a crucial feedback loop. It helps you move from reactive patching to proactive risk management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: DNS Monitoring
&lt;/h2&gt;

&lt;p&gt;DNS monitoring sits slightly outside the main levels, but it plays a critical supporting role.&lt;/p&gt;

&lt;p&gt;DNS is the entry point to your website. If it breaks, nothing else matters, uptime checks, health checks, and synthetic tests all fail at once.&lt;/p&gt;

&lt;p&gt;DNS monitoring focuses on observing your DNS records and detecting unexpected changes or resolution issues.&lt;/p&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;p&gt;DNS issues are surprisingly common and often hard to diagnose:&lt;/p&gt;

&lt;p&gt;Without DNS monitoring, you usually find out the hard way when your entire site is unreachable for hours.&lt;/p&gt;

&lt;p&gt;What makes DNS monitoring especially valuable is how &lt;em&gt;quiet&lt;/em&gt; DNS failures are. There’s no error page from your application. There’s just… nothing. And if you’re unsure, it’s always DNS.&lt;/p&gt;

&lt;p&gt;By monitoring DNS, you get:&lt;/p&gt;

&lt;p&gt;DNS monitoring doesn’t replace other levels, but it reinforces all of them. It ensures that users can even reach the infrastructure you’ve worked so hard to protect.&lt;/p&gt;

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

&lt;p&gt;Website monitoring isn’t a single tool or a checkbox you tick once. It’s a progression.&lt;/p&gt;

&lt;p&gt;Each level adds a new layer of visibility: from basic uptime, to global reach, to performance, functionality, system health, and security. You don’t need to start at the highest level on day one, but you do need to be intentional about what you &lt;em&gt;don’t&lt;/em&gt; see yet. Software like &lt;a href="https://govigilant.io" rel="noopener noreferrer"&gt;Vigilant&lt;/a&gt; do make it easy for you to reach all levels with minimal effort.&lt;/p&gt;

&lt;p&gt;Monitoring turns unknown failures into known signals. It replaces guesswork with insight and reaction with prevention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Great websites don’t just wor, they’re observed, understood, and cared for. Monitoring is how you get there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/the-7-levels-of-website-monitoring" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on March 19, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>website</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Self Hostable Multi-Location Uptime Monitoring</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Sat, 15 Nov 2025 09:46:47 +0000</pubDate>
      <link>https://dev.to/vincentbean/self-hostable-multi-location-uptime-monitoring-54be</link>
      <guid>https://dev.to/vincentbean/self-hostable-multi-location-uptime-monitoring-54be</guid>
      <description>&lt;p&gt;I’ve recently implemented the ability to monitor services from multiple locations in Vigilant. Vigilant is a open source and self hostable monitoring application that is made to monitor websites specifically. But the uptime monitoring works on any service.&lt;/p&gt;

&lt;p&gt;Up until now the implementation of the uptime / latency monitor was simple, the main application sends a request to the service and stores the result. This means that when the main application has a slight network hiccup it will see the application as unavailable and send a notification. This is a false positive! Not what we want because when you check what’s wrong, you notice that nothing is wrong! These false positives are not acceptable and have to be prevented. How? By checking from multiple locations across the globe.&lt;/p&gt;

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

&lt;p&gt;I had a few requirements for this as I want to be able to purchase a cheap VPS via sites like &lt;a href="https://lowendbox.com/" rel="noopener noreferrer"&gt;lowendbox &lt;/a&gt;or &lt;a href="https://lowendtalk.com/" rel="noopener noreferrer"&gt;lowendtalk&lt;/a&gt; and deploy quickly to these machines with minimal configuration. This allows me to setup multiple monitoring locations with a small budget. The ideal workflow would be to ssh into the machine, setup a docker compose file with an .env file and run it. The environment file would contain the URL to Vigilant. This requires the remote uptime containers to register themselves with the main application and this must be done in a secure way. Because Vigilant is open source and self hostable, I must also take into consideration the ease of setup for people who self-host Vigilant.&lt;/p&gt;

&lt;p&gt;To summarize, the requirements for remote uptime monitoring in Vigilant are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Able to run on random remote servers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quick to setup using Docker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatic registering with Vigilant&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy to use and understand for self-hosters&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Approach
&lt;/h2&gt;

&lt;p&gt;My initial idea was to leverage the main application’s queue worker by deploying a queue worker remotely and setting up a secure connection between them using something like &lt;a href="https://www.wireguard.com/" rel="noopener noreferrer"&gt;Wireguard&lt;/a&gt;. Vigilant is written in &lt;a href="https://php.net" rel="noopener noreferrer"&gt;PHP &lt;/a&gt;using the &lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; framework, for queuing it uses &lt;a href="https://laravel.com/docs/master/horizon" rel="noopener noreferrer"&gt;Laravel Horizon&lt;/a&gt;. This is a queuing system built on top of &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;. All monitoring tasks in Vigilant are executed on this queue, it allows for multiple queues to exist identified by an unique name. Vigilant has queues for uptime, dns, lighthouse etc, this way the different monitors don’t block each other and resources can be managed per type of monitor.&lt;br&gt;
To start a queue worker we can run a single command in a container, exactly like the &lt;code&gt;horizon&lt;/code&gt; container does now in the current Docker compose setup. &lt;br&gt;
So my first try was to deploy a remote Horizon worker that just handles one queue and name it something like uptime:de or uptime:us to specify the location. Horizon already runs in a separate Docker container.&lt;/p&gt;

&lt;p&gt;I quickly realized that this will not work well, Horizon requires Redis and does not support multiple Redis servers. Redis is single threaded meaning only one operation at a time. With a VPN across the world the latency from the worker would block Redis for too long affecting the entire application.&lt;/p&gt;

&lt;p&gt;So what about a proxy? Setup a socks5 proxy and run the checks via that? Well, first of all they aren’t auto-registering and Vigilant also requires latency information from the endpoint to the service which is not possible when using a proxy like this. We will have to write our own proxy to get the exact data we need.&lt;/p&gt;

&lt;p&gt;I’ve done something similar for the Lighthouse checks, they run in a separate Docker container using a Go application. This app provides a HTTP server which Vigilant calls with the URL to run Lighthouse on. When it is done it will send the result back to Vigilant. Something similar for uptime would be good, send the request to this Go application but then directly return the response.&lt;/p&gt;

&lt;p&gt;Why Go and not PHP? Vigilant is written in PHP so the obvious choice to write these external applications in would also be PHP, right? While PHP is great is does not for example have an inbuilt web server that supports multiple connections, the ability to schedule things on it’s own or to run things on startup. Other languages like Go and Python do which is why I choose not to write this in PHP. If written in PHP it would need a separate web server and a mechanism to detect when the container starts and stops to run a specific piece of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the outpost
&lt;/h2&gt;

&lt;p&gt;I’ve decided to call these containers ‘outposts’ as they are remotely located from the main application.&lt;br&gt;
These are the things that the outpost needs to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-registration&lt;/strong&gt;&lt;br&gt;
When the container starts, it immediately registers itself with Vigilant without requiring additional configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-unregistration&lt;/strong&gt;&lt;br&gt;
Before shutting down (or when the container is stopped), the outpost sends a unregistration signal to remove itself from the active outpost list. This prevents stale entries or “ghost” outposts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP server for uptime checks&lt;/strong&gt;&lt;br&gt;
The outpost exposes a small HTTP API that Vigilant can call to perform checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check execution&lt;/strong&gt;&lt;br&gt;
It supports both &lt;strong&gt;HTTP&lt;/strong&gt; and &lt;strong&gt;Ping&lt;/strong&gt; checks, measuring round-trip latency and returning the raw timing data back to Vigilant.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ve implemented these things into the Go application, once it starts up it will find it’s own public IP address and select a random port to listen to. It will then send those details to Vigilant and starts a HTTP server.&lt;br&gt;
Via a Github action it is built into a Docker image and hosted on the Github container registry. The resulting docker compose file looks like this and is all that is required to setup an outpost:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  outpost:
    image: ghcr.io/govigilant/vigilant-uptime-outpost:latest
    restart: always
    network_mode: host
    environment:
      - VIGILANT_URL=https://&amp;lt;Vigilant instance URL here&amp;gt;
      - OUTPOST_SECRET=&amp;lt;random secret string here&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can view the source for the outpost here: &lt;a href="https://github.com/govigilant/vigilant-uptime-outpost" rel="noopener noreferrer"&gt;https://github.com/govigilant/vigilant-uptime-outpost&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running uptime checks via outposts
&lt;/h2&gt;

&lt;p&gt;Vigilant tracks which outposts are available and every time an uptime check it due it will select an outpost to run the check on. When an outpost registers itself, Vigilant will determine the lat/long and country code of the outpost. &lt;br&gt;
The lat/long is then also determined for the monitored service to find the closest outpost. This is then used to ensure that most of the uptime checks are done from the closest outpost.&lt;/p&gt;

&lt;p&gt;When a request to an outpost fails it will try another and mark that outpost as unavailable to prevent another process from using it. Then a background task periodically checks the unavailable outposts, removes them if they are unreachable or marks them as available when they work.&lt;/p&gt;

&lt;p&gt;This also allows us to check from multiple locations when a service is down with a small tradeoff that the detection takes a bit longer due to the additional checks. When an outpost responds with the message that a service is down it will check two additional outposts to ensure this is really the case. The small time tradeoff is worth it here to reduce the amount of false positives.&lt;br&gt;
For those who are interested, &lt;a href="https://github.com/govigilant/vigilant/blob/develop/packages/uptime/src/Actions/CheckUptime.php" rel="noopener noreferrer"&gt;here&lt;/a&gt; is the source code for an uptime check.&lt;/p&gt;

&lt;p&gt;Now that we also have a country per result we can incorporate that into the dashboard and notifications. When latency increases from a specific country Vigilant will now add that to the notification.&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  Securing the outposts
&lt;/h2&gt;

&lt;p&gt;For security there are two concerns, how to prevent anyone from registering an outpost and how to ensure the communication from and to the outposts are encrypted.&lt;/p&gt;

&lt;p&gt;Vigilant itself runs on HTTPS, at least it should (I can’t speak for self-hosted instances). But we consider the initial request from the outpost to Vigilant as secure because it goes over HTTPS with the secret token. To ensure we can trust the outpost, Vigilant and the outpost both have an API token. Vigilant sends the same key when running uptime checks, without this token you are unable to register an outpost or run an uptime check.&lt;/p&gt;

&lt;p&gt;But the outposts start their own webserver on a random port and don’t have a certificate which causes the traffic to go via HTTP, not good. We need a way to run the outposts webserver on HTTPS. Should we setup DNS and use a reverse proxy to handle this? While that would be possible it goes against our minimal configuration principle. A self signed certificate then? Yes possible, but our main application wouldn’t be able to verify the certificate.&lt;/p&gt;

&lt;p&gt;The first thing an outpost does is register itself, what if it gets a certificate from Vigilant? Vigilant can return a signed certificate which the outpost can use. Only Vigilant needs to be able to verify the certificate. This is exactly how I’ve implemented it, Vigilant acts as the root CA and generates 30 day valid certificates for an outpost. The outpost then starts a HTTPS server which causes the communication from Vigilant to the outposts to be encrypted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The single point of failure
&lt;/h2&gt;

&lt;p&gt;Outposts make it possible to monitor from multiple geographical regions but all requests are initiated from Vigilant, if Vigilant the application or the network it runs on becomes unavailable then no more checks are ran. This is not something that I want to get into now but I do monitor my monitoring service using &lt;a href="https://github.com/louislam/uptime-kuma" rel="noopener noreferrer"&gt;Uptime Kuma&lt;/a&gt;. &lt;br&gt;
But in order to solve this, Vigilant needs to run on multiple machines at the same time which is another layer of complexity where this project isn’t big enough for currently. Vigilant runs in Docker so something like Docker Swarm would be possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Location Monitoring for Self Hosters
&lt;/h2&gt;

&lt;p&gt;When you host your own applications, for fun, privacy or to save costs it takes some effort to setup monitoring from different locations. Most self hostable monitoring tools only support a single location, you could place a monitoring application like the popular open source option &lt;a href="https://github.com/louislam/uptime-kuma" rel="noopener noreferrer"&gt;Uptime Kuma&lt;/a&gt; on multiple servers but then you run into situations like location A is saying your service is down while location B says it’s up. Ideally you’d have one application which checks this but it really depends on your use case. For a homelab it is overkill but for a business it’s essential, I’m personally still running Uptime Kuma in my homelab but my other sites are monitored with Vigilant.&lt;/p&gt;

&lt;p&gt;So to start monitoring from multiple location with Vigilant you can follow the &lt;a href="https://govigilant.io/documentation/multi-location-uptime-monitoring" rel="noopener noreferrer"&gt;multi location uptime monitoring &lt;/a&gt;guide in the documentation.&lt;/p&gt;

&lt;p&gt;Vigilant now makes it possible to self host uptime monitoring from multiple locations but it requires managing multiple servers. As I’m attempting to make Vigilant self sustainable I also offer a hosted version for which I am setting up this infrastructure. I’m toying with the idea to make this accessible for self hosters by letting them connect to Vigilant’s hosted outpost for a small yearly price. That way you remain in control of your own data but do not have to worry about the worldwide infrastructure. I’d love to get feedback on this idea and/or hear if anyone is interested in this, please send me an email on &lt;a href="mailto:vincent@govigilant.io"&gt;vincent@govigilant.io&lt;/a&gt; and let’s chat!&lt;/p&gt;

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

&lt;p&gt;Adding outposts to Vigilant not only solves the false positive issue but also opens the door to a more scalable and resilient architecture. With outposts, monitoring is no longer tied to a single server but it becomes distributed. The self-registering design keeps the setup experience simple for self-hosters (and for me hosting govigilant), while the secure certificate exchange ensures that communication remains trusted without requiring much configuration or DNS management.&lt;/p&gt;

&lt;p&gt;Vigilant can now confidently say it doesn’t just monitor uptime; it verifies it from around the world.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://govigilant.io/articles/self-hostable-multi-location-uptime-monitoring" rel="noopener noreferrer"&gt;https://govigilant.io&lt;/a&gt; on November 15, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>docker</category>
      <category>laravel</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My SaaS homepage design journey as a backend developer</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Thu, 06 Nov 2025 15:05:11 +0000</pubDate>
      <link>https://dev.to/vincentbean/my-saas-homepage-design-journey-as-a-backend-developer-32d4</link>
      <guid>https://dev.to/vincentbean/my-saas-homepage-design-journey-as-a-backend-developer-32d4</guid>
      <description>&lt;p&gt;I’ve built Vigilant, a monitoring tool designed to monitor all aspects of your website. From basic uptime checks to more advanced checks like performance. user flows and public infrastructure. It started as an open source project and is now a SaaS using the open core structure.&lt;/p&gt;

&lt;p&gt;As a backend developer, design is not my strongest point. In this article I’m sharing the different revisions of the homepage and the feedback I’ve got from generous internet users with which I was able to improve the homepage a few times to what it is now. I’ll share where I got the feedback and what I’ve changed.&lt;/p&gt;

&lt;p&gt;I won’t make you scroll down the whole article to see the before/after, so here it is the initial version (January 2025) and (June 2025):&lt;/p&gt;

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

&lt;p&gt;And now (November 2025):&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The choice for dark mode
&lt;/h2&gt;

&lt;p&gt;I had used a dark theme in one of my previous projects and decided that I wanted the same for Vigilant. Dark just has a cooler vibe to it in my opinion. I later learned that it is easier to make thing look nice on light mode, but I think I would still have made the choice for dark mode as it is my personal preference. Light mode would be cool to add in the future though. The color scheme I’m using is called &lt;a href="https://github.com/kepano/flexoki" rel="noopener noreferrer"&gt;Flexoki&lt;/a&gt;, which is open source and looks good in my opinion.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Initial Version
&lt;/h2&gt;

&lt;p&gt;The initial version of the homepage was simple. There were two colours, black and red, a list of features and a screenshot. The goal of this website was just to have something, the most important part was the stay updated e-mail input. The project wasn’t a SaaS at this point and if you wanted to use it you’d have to host it yourself (which is still possible today!).&lt;/p&gt;

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

&lt;p&gt;This was fine when I started out, clean and simple but definitely not suitable for a SaaS homepage.&lt;/p&gt;

&lt;h2&gt;
  
  
  First redesign for SaaS
&lt;/h2&gt;

&lt;p&gt;When I launched the SaaS version I wanted to make the homepage more appealing. I figured adding an animation would be cool, so I did. I’ve created this Lottie animation symbolizing how Vigilant works.&lt;br&gt;
Dev.to doesn’t support lottie animations so you can view it on my original post: &lt;a href="https://govigilant.io/articles/my-saas-homepage-design-journey-as-a-backend-developer" rel="noopener noreferrer"&gt;https://govigilant.io/articles/my-saas-homepage-design-journey-as-a-backend-developer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had also changed the hero text to “Detect Website Issues Before They Cost You” and added the list of features in a bento grid with screenshots. Over time I’ve tried many different hero texts, I don’t remember all of them but have learned that there are many good options and it’s difficult to choose the right one 😅&lt;/p&gt;

&lt;p&gt;Up until this point I’ve not had any feedback and it shows. Two colors, reused the same logo image multiple times and hard to read screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  First feedback
&lt;/h2&gt;

&lt;p&gt;I came across a post on Reddit where someone was offering to review landing pages. Ofcourse I commented Vigilant and he made a video looking at the homepage and sharing his thoughts.&lt;br&gt;
He &lt;a href="https://www.reddit.com/r/SaaS/comments/1jl0b0f/comment/mk5fx8y/" rel="noopener noreferrer"&gt;commented&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;As independent buy says — it seems like a website built by a developer with in mind what they like. some work to do, but the tool itself has potential.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This of course motivated me greatly, the video can be found &lt;a href="https://govigilant.io/articles/As%20independent%20buy%20says%20-%20it%20seems%20like%20a%20website%20built%20by%20a%20developer%20with%20in%20mind%20what%20they%20like.%20some%20work%20to%20do,%20but%20the%20tool%20itself%20has%20potential." rel="noopener noreferrer"&gt;here&lt;/a&gt;, but I’ll here are his feedback points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Design is rather simple but doesn’t look bad&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Colors are missing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Animation is funny but not professional&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CTA is too generic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copywriting is about the product, should be about the customer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Anwser these questions: For who is Vigilant and what does Vigilant solve?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Screenshot doesn’t tell anything&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use cases are missing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Around the same time I was chatting with another founder essentially told me the same, add more colors because there is no emotion now on the homepage. And the texts are about the product and not about the customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying the feedback
&lt;/h2&gt;

&lt;p&gt;This feedback got me thinking. I started with the hero, I changed it to this and adjusted the text on the CTA button:&lt;/p&gt;

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

&lt;p&gt;I went overboard with using colors in text, hoping that certain parts would stand out but in the end it was just too much.&lt;/p&gt;

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

&lt;p&gt;I had created an image that had to explain how Vigilant’s flows feature works. I did so by arrows, small images and text. I thought that it was clear but after I looked at it a few hours later I noticed that it was actually very confusing. Would you understand this? No, you’d not waste your brainpower on this.&lt;/p&gt;

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

&lt;p&gt;I had added colors, but just in the text. I’ve gotten feedback again on Reddit after making these changes which said that the red/green/blue were just too much and overwhelming. I also got the same feedback &lt;strong&gt;again&lt;/strong&gt;, not enough color. It seems that I had failed in my mission to add color.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding color &amp;amp; animations
&lt;/h2&gt;

&lt;p&gt;Instead of adding color on the page I changed the background color on some sections. Not too much, just a lighter base color from Flexoki but it looks much better like this in my opinion.&lt;/p&gt;

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

&lt;p&gt;I removed the image with all the arrows for the Flows feature and replaced it with an animated GIF showing how it works. Because this is one of the most unique features of Vigilant I have made the background here blue so that it really stands out.&lt;/p&gt;

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

&lt;p&gt;I had also added sections that highlight the notification channels and use cases.&lt;/p&gt;

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

&lt;p&gt;To make it all a little nicer I added animations on three places. First of all the header, where I added moving stripes:&lt;/p&gt;

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

&lt;p&gt;Then I added a little stripes animation to break up the page:&lt;/p&gt;

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

&lt;p&gt;And finally I added little animated circles expanding behind the notifications, kind of simulating that you’re receiving a notification.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The final solution: AI
&lt;/h2&gt;

&lt;p&gt;The current version of the homepage is drastically better than the first version, we learn and improve. It’s been a nice journey learning what to do and the feedback I’ve gotten from strangers on the internet has helped me enormously. I’d like to thank the people that took the time to comment and talk to me about the design, but realistically I know that they probably won’t read this.&lt;/p&gt;

&lt;p&gt;Anyway, my design skills are skill mediocre so I experimented with AI. I used &lt;a href="https://github.com/features/copilot/cli/" rel="noopener noreferrer"&gt;Copilot CLI &lt;/a&gt;and entered this prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This statamic website has been designed by a backend developer that does not have any experience in design. It looks terrible, you are an expert designer and are improving the design of this website. You may alter the colors slightly. Let’s start by redesigning the homepage&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then I took it component for component and page by page through the AI. It looked much better and I even was able to add some nice face in animations and a cool hover effect on the hero.&lt;/p&gt;

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

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

&lt;p&gt;In &lt;a href="https://govigilant.io/release/2025-11" rel="noopener noreferrer"&gt;release 2025.11&lt;/a&gt; I’ve also implemented this redesign in the application itself.&lt;/p&gt;

&lt;p&gt;And that’s it! It has been a journey but I’m very satisfied with the result.&lt;/p&gt;

&lt;p&gt;I’m always open for feedback, if you see something that could be better, please e-mail it to me at &lt;a href="mailto:vincent@govigilant.io"&gt;vincent@govigilant.io&lt;/a&gt; or join the &lt;a href="https://discord.gg/MG3aV8uFt5" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://govigilant.io/articles/my-saas-homepage-design-journey-as-a-backend-developer" rel="noopener noreferrer"&gt;https://govigilant.io&lt;/a&gt; on November 6, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>design</category>
    </item>
    <item>
      <title>Every Developer Should Try Vim</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Thu, 19 Jun 2025 15:16:56 +0000</pubDate>
      <link>https://dev.to/vincentbean/every-developer-should-try-vim-5f17</link>
      <guid>https://dev.to/vincentbean/every-developer-should-try-vim-5f17</guid>
      <description>&lt;h1&gt;
  
  
  What is Vim?
&lt;/h1&gt;

&lt;p&gt;Vim is a highly efficient, keyboard-driven text editor that prioritizes speed, precision, and control. It’s been around for decades, but it’s far from outdated. In recent years, Vim has seen a resurgence, thanks largely to &lt;a href="https://neovim.io/" rel="noopener noreferrer"&gt;Neovim&lt;/a&gt;, a modern refactor of Vim that brings a faster core, better plugin support, and a vibrant, forward-looking community.&lt;/p&gt;

&lt;p&gt;Many developers avoid Vim because of its steep learning curve and unfamiliar interface. But that’s changing, as Vim’s community has grown rapidly. This shows that other developers are seeing Vim as a serious upgrade to their workflow. Of course, it can be said that these developers just don’t know how to quit Vim.&lt;br&gt;
Luckily for us, someone has taken the time to compile a list of &lt;a href="https://github.com/hakluke/how-to-exit-vim" rel="noopener noreferrer"&gt;ways to exit Vim&lt;/a&gt;. I’ve ultimately chosen for &lt;strong&gt;The Acceptance Way.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You do not need to use Vim directly in your terminal, most modern editors have a Vim plugin so that you can keep using the editor you are familiar with and get the benefits of Vim. This saves you hours of configuration and makes the barrier to get started much lower.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why use Vim?
&lt;/h1&gt;

&lt;p&gt;Vim isn’t just an editor, it’s a mindset. I’ve started to use Vim because it offers unmatched speed, muscle-memory efficiency, and a sense of flow that’s hard to replicate. You’re no longer using your mouse or the arrow keys to navigate, you’re using your keyboard to go blazing fast.&lt;/p&gt;

&lt;p&gt;At its core, Vim is a modal editor which is a little different than regular editors. It means you switch between modes and each mode has its own use cases. The modes in Vim are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Normal mode&lt;/strong&gt; — for moving around and making changes&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Insert mode&lt;/strong&gt; — for typing text&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Visual mode&lt;/strong&gt; — for selecting and manipulating blocks&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Command mode&lt;/strong&gt; — for things like saving, quitting, or searching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a regular editor you would only have insert mode. This separation of concerns sounds odd at first, but it allows you to perform complex edits with just a few keystrokes. Once it clicks, it’s hard to go back.&lt;/p&gt;

&lt;p&gt;Here are a few examples that show how Vim feels in action, these are all executed in normal mode:&lt;/p&gt;

&lt;h1&gt;
  
  
  Learning Vim is hard
&lt;/h1&gt;

&lt;p&gt;I’ve personally struggled with learning Vim, it was intimidating at first. The reason for this is that modal editing was unfamiliar for me and I was so used to my previous JetBrains editor and its functions that I had a hard time being productive. I admit, the first time I tried it I gave up. To really get started learning Vim you have to change your mindset about how you write code, and that’s hard after all these years of programming.&lt;/p&gt;

&lt;p&gt;I didn’t understand it well enough, I knew how to get into insert mode and use &lt;code&gt;:wq&lt;/code&gt; to exit. With that little skill in Vim it feels cumbersome and slow to use. I hadn't even found out about Vimtutor, if you don't know, Vimtutor is a tool that teaches you the basics of Vim in about 20-30 minutes.&lt;/p&gt;

&lt;p&gt;But there is a light at the end of the tunnel! As with any new skill, it’s only hard at the beginning. If you can push through the initial awkwardness, you unlock a tool that grows with you for the rest of your career. And you will be able to say: “I use Vim by the way”.&lt;/p&gt;

&lt;h1&gt;
  
  
  Start in your own editor
&lt;/h1&gt;

&lt;p&gt;As I started learning more things about Vim using tools like Vimtutor and YouTube videos I decided that I would give it another go. Instead of using Neovim I installed Ideavim. This allowed me to get started with Vim in an environment that I was comfortable with. The first weeks were slow, I felt like my productivity had declined by 50%. But after that initial drop I noticed that I got faster, things started to feel natural and became muscle memory.&lt;/p&gt;

&lt;p&gt;I no longer had to think about &lt;em&gt;how&lt;/em&gt; to do something. My fingers just knew. That’s when Vim clicked for me, not as a tool I was forcing myself to use, but as an extension of how I write code. It really gives you an extra dimension of editing code.&lt;/p&gt;

&lt;p&gt;Looking back, that short period of pain was more than worth it. Vim didn’t just change my editor, it changed how I like to write code. And that shift has made programming feel smoother, faster, and most importantly, a lot more fun.&lt;/p&gt;

&lt;p&gt;The nice thing with Vim is that you keep learning, you keep finding ways to get faster. There is even a website called &lt;a href="https://www.vimgolf.com/" rel="noopener noreferrer"&gt;VimGolf&lt;/a&gt; that has challenges for users to do certain tasks with as few keystrokes as possible.&lt;/p&gt;

&lt;p&gt;I stuck with my JetBrains editor and Ideavim for two years. After those two years I decided that I wanted to dig deeper, customize my editor more and really get the experience that I liked.&lt;/p&gt;

&lt;h1&gt;
  
  
  Switching to Neovim
&lt;/h1&gt;

&lt;p&gt;The switch was hard at first, especially file management. I was so used to the file tree on the left of my editor. I tried to replicate that in Neovim with &lt;a href="https://github.com/nvim-tree/nvim-tree.lua" rel="noopener noreferrer"&gt;nvim-tree&lt;/a&gt; but it didn’t feel the same. Until I came across &lt;a href="https://github.com/stevearc/oil.nvim" rel="noopener noreferrer"&gt;oil.nvim&lt;/a&gt;, this was a game changer for me as it made creating, renaming and moving so much easier. That was the moment that I felt I could switch. I had tried a few of the popular Neovim distros such as &lt;a href="https://www.lunarvim.org/" rel="noopener noreferrer"&gt;Lunarvim&lt;/a&gt; but found them too overwhelming.&lt;br&gt;
I personally prefer a basic setup with not too many bells and whistles (I know, I used JetBrains before but only used 10% of that editor’s capacity and had a minimal interface configured).&lt;/p&gt;

&lt;p&gt;I did not start with a blank Neovim install, I used &lt;a href="https://github.com/nvim-lua/kickstart.nvim" rel="noopener noreferrer"&gt;kickstart.nvim&lt;/a&gt;. It has such clear documentation which made it really easy to understand and get started. This &lt;a href="https://www.youtube.com/watch?v=TQn2hJeHQbM&amp;amp;list=PLep05UYkc6wTyBe7kPjQFWVXTlhKeQejM" rel="noopener noreferrer"&gt;YouTube playlist by TJ DeVries&lt;/a&gt; also helped me a lot to get started.&lt;/p&gt;

&lt;p&gt;It amazed me how limiting the JetBrains + Ideavim actually was, with Neovim it is so much easier to set up custom and more advanced keybinds using Lua.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Learning Vim isn’t just about picking up a new editor, it’s about changing how you interact with code. I believe that every developer should at least try Vim to see if it fits them. If you stick with it, Vim rewards you with a sense of control and fluency that’s hard to describe until you experience it yourself.&lt;/p&gt;

&lt;p&gt;You don’t have to go all-in on day one. Start where you are. Use a plugin in your current editor, explore Vimtutor, or just commit to learning a few motions at a time. You’ll be amazed how much power you can unlock with just a handful of commands.&lt;/p&gt;

&lt;p&gt;Vim won’t make you a better developer overnight, but it will change the way you think about writing code. And once it clicks, it becomes something you don’t want to work without.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/every-developer-should-try-vim" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on June 19, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>development</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why uptime monitoring isn’t enough for your website</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Tue, 17 Jun 2025 18:20:40 +0000</pubDate>
      <link>https://dev.to/vincentbean/why-uptime-monitoring-isnt-enough-for-your-website-1kha</link>
      <guid>https://dev.to/vincentbean/why-uptime-monitoring-isnt-enough-for-your-website-1kha</guid>
      <description>&lt;p&gt;Most uptime tools do the bare minimum: they ping your homepage every few minutes and look for a 200 OK response. If they get it, your site’s marked as “up.”&lt;/p&gt;

&lt;p&gt;But here’s the problem: just because the homepage loads doesn’t mean the rest of your site is working. Your login could be broken. The checkout process might fail. An API endpoint might be silently throwing errors. Uptime checks won’t catch any of that, they only confirm that &lt;em&gt;something&lt;/em&gt; responded.&lt;/p&gt;

&lt;p&gt;I run Vigilant, an open-source website monitoring tool, so I wanted to share some insights based on what we’ve learned on monitoring websites. &lt;/p&gt;

&lt;h1&gt;
  
  
  You Do Need Uptime Monitoring
&lt;/h1&gt;

&lt;p&gt;Don’t get me wrong, uptime monitoring is still important. You need to know if your site is reachable at all. If your server crashes or your hosting provider has issues, you want to be the first to know, not your users.&lt;/p&gt;

&lt;p&gt;But the uptime monitoring service can also have connectivity issues, when that happens the service may give false positives. The only way to effectively combat this is if the uptime monitoring services use multiple locations to check if the site is down.&lt;/p&gt;

&lt;p&gt;Most decent uptime tools also check latency, which gives you a sense of how responsive your site is over time. That’s useful. If response times start creeping up, it’s often an early sign something is wrong. Maybe a slow database query, maybe traffic spikes or high load on your server.&lt;/p&gt;

&lt;p&gt;And don’t forget your SSL certificate. A broken cert blocks access and erodes trust. Monitoring tools should alert you &lt;em&gt;before&lt;/em&gt; it expires.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next-Level Monitoring
&lt;/h1&gt;

&lt;p&gt;Once you’ve got basic uptime covered, the next step is to go deeper. Think of your site as more than just one page. It’s a system with moving parts, links, user flows, DNS, third-party services, and any of those can break without triggering a traditional uptime alert.&lt;/p&gt;

&lt;p&gt;Broken links are a good example. They don’t take your whole site down, but they chip away at trust. Clicking into a 404 is frustrating for users, it’s a sign something in your website is broken.&lt;/p&gt;

&lt;p&gt;Then there are key user flows. Logging in, signing up, resetting a password, placing an order, these are the things people actually come to your site to do. If one of those is broken, your site is technically up, but it’s failing its real job.&lt;/p&gt;

&lt;p&gt;DNS is another layer most people don’t think about until it’s too late. If your nameservers go down or your domain is misconfigured, your whole site becomes unreachable, even if the server is fine. It’s like unplugging the sign from your storefront.&lt;/p&gt;

&lt;p&gt;And finally performance, you need to know if a change on your website has a performance impact. Manually checking this with Google Lighthouse is time consuming and often forgotten.&lt;/p&gt;

&lt;h1&gt;
  
  
  Spotting Broken Links Before Your Users Do
&lt;/h1&gt;

&lt;p&gt;Broken links can quickly frustrate users and harm your site’s reputation. Catching them early is key.&lt;/p&gt;

&lt;p&gt;To find these you use a crawler. This is a tool that systematically clicks through your site, following every link it finds just like a user would. If the crawler hits a dead end, it flags the broken link.&lt;/p&gt;

&lt;p&gt;Running a crawler regularly helps you catch problems before your users do. That means less frustration, better SEO, and a smoother experience overall. And because the crawler works automatically it saves you a lot of time by not having to manually check every link.&lt;/p&gt;

&lt;h1&gt;
  
  
  Monitoring Critical Flows, Not Just Pages
&lt;/h1&gt;

&lt;p&gt;Synthetic monitoring isn’t new. It uses a real browser to simulate a user interacting with your site, logging in, checking out, filling forms to make sure those key flows actually work.&lt;/p&gt;

&lt;p&gt;The tricky part used to be setting it up. You had to specify CSS selectors and element paths, which often broke when the UI changed. It was tedious and time-consuming to maintain.&lt;/p&gt;

&lt;p&gt;Recently with the developments of AI, this is changing fast. Instead of wrestling with selectors, you can simply give instructions in plain English, like “click on the add to cart button” or “enter ‘Vincent Bean’ in the name input field”, and the AI figures out how to do it. That means less hassle setting up tests and more reliable synthetic monitoring.&lt;/p&gt;

&lt;h1&gt;
  
  
  Don’t overlook DNS
&lt;/h1&gt;

&lt;p&gt;It’s always DNS. Most people think “set it and forget it”, and for many sites, that works fine. But there are exceptions you don’t want to miss.&lt;/p&gt;

&lt;p&gt;For example, if you’re hosting a site but your client controls the DNS because it’s their domain, changes can happen without your knowledge. A simple tweak or mistake there can take your whole site offline for hours.&lt;/p&gt;

&lt;p&gt;Even when you make DNS changes yourself, it’s helpful to get confirmation when those changes actually propagate. That way, you know roughly when the outside world will start seeing the update.&lt;/p&gt;

&lt;h1&gt;
  
  
  Always Patch in Time, Never Miss a CVE
&lt;/h1&gt;

&lt;p&gt;When you’re running a website, especially a public one, security isn’t optional. The software you rely on can have vulnerabilities, known as CVEs, that malicious actors will try to exploit. If you have any experience hosting a site you will know that random bots try known exploits, just look at your access log.&lt;/p&gt;

&lt;p&gt;When a CVE pops up for any software you’re using, you want to hear about it immediately so you can decide how fast to patch it. To be honest, most of the time, you &lt;em&gt;should&lt;/em&gt; patch right away.&lt;/p&gt;

&lt;p&gt;The tricky part is keeping track of these alerts. Relying on random news posts, Slack messages, or word of mouth is a bad idea. Critical CVEs need a reliable, automated way to find you by monitoring them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ensure Changes Don’t Affect Performance
&lt;/h1&gt;

&lt;p&gt;Performance issues often slip in quietly, a new (improperly sized) image, a third-party script, a layout tweak, and suddenly your site is slower. The only way to catch this early is to track performance over time.&lt;/p&gt;

&lt;p&gt;Running Google Lighthouse often is great for this. It audits your pages and gives you real metrics like First Contentful Paint, Time to Interactive, and more, all using a real web browser. When tracked over time, you get a clear picture of how changes are affecting the user experience.&lt;/p&gt;

&lt;p&gt;Performance doesn’t just affect how fast your site feels, it impacts SEO, conversions, and user trust. So it’s worth watching closely.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Good websites don’t just work, they are cared for. But you can’t fix what you don’t know is broken.&lt;br&gt;
That’s exactly why I built &lt;a href="https://govigilant.io/" rel="noopener noreferrer"&gt;Vigilant&lt;/a&gt;. It’s designed to monitor your entire website, from uptime to user flows, DNS health to security vulnerabilities. Because keeping your site healthy means watching all the moving parts, not just the homepage.&lt;/p&gt;

&lt;p&gt;You can self-host Vigilant for free, it’s open source software. Or you can try the hosted version for free.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/why-uptime-monitoring-isnt-enough-for-your-website" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on June 17, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Getting my Laravel application security audited</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Sun, 08 Jun 2025 17:48:35 +0000</pubDate>
      <link>https://dev.to/vincentbean/getting-my-laravel-application-security-audited-4obd</link>
      <guid>https://dev.to/vincentbean/getting-my-laravel-application-security-audited-4obd</guid>
      <description>&lt;p&gt;A while ago I saw a message in a Slack channel that I’m in about someone that is building a tool to do security / code quality checks on PHP projects. He wanted a codebase to test his tool so I offered Vigilant.&lt;br&gt;
I’ve received a detailed report on the code quality and security of Vigilant, in this post I’ll share the report.&lt;/p&gt;

&lt;p&gt;Vigilant is an open source web monitoring application written in PHP using the Laravel framework. It uses the latest PHP and Laravel version and minimizes dependencies. It has been a core design decision to minimize the amount of third party packages for Vigilant to prevent dependency hell. “This choice has also improved security-less external code means fewer potential abandoned dependencies and more control at the cost of some extra work. When I do add an external dependency I have a few requirements such as code quality, activeness and downloads.&lt;/p&gt;

&lt;p&gt;Let’s get into the security audit, each blockquote is a copy/paste from the report and not something I’ve typed myself.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary and Methodology
&lt;/h1&gt;

&lt;p&gt;The report starts with an executive summary which comes with an issue chart and conclusion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt; performed a source code review on the source code repository vigilant. The objective of the review was to identify code quality and security issues in the source code. A total of eight issues were identified during the review. Each issue was rated according to its severity: one was rated as low severity, and seven as informational.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Conclusions&lt;br&gt;
The code review found one low-severity security issue in the codebase. This issue should be addressed to ensure the security of the application.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The overall quality of the codebase is good, but we have identified a few opportunities to improve the quality and maintainability of the code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Overall, the codebase is in good shape.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next page contains recommendations in which they say that there is no guarantee that there aren’t any security vulnerabilities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Recommendations&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We recommend addressing the issues identified in this report as part of regular development activities. The severity of the issues should be taken into account when prioritizing the fixes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;While the review identified some issues, it is important to note that this assessment does not guarantee the absence of other security vulnerabilities in the application. It is recommended to perform regular security assessments to ensure the application remains secure.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After these two introductory pages the report starts by describing the application and how the audit was performed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The vigilant application is written in PHP (13.2 KLOC) and JavaScript (97 LOC) and uses the Laravel framework.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scope&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The source code review was performed against the code found in&lt;/em&gt; &lt;a href="https://github.com/govigilant/vigilant" rel="noopener noreferrer"&gt;&lt;em&gt;https://github.com/govigilant/vigilant&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We focused on the parts of the application written in PHP. Source code written in other languages was not reviewed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Methodology&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The source code review was performed by analyzing the vigilant source code repository using static code analysis tools. The findings were then manually reviewed to ensure their accuracy and relevance. The code review focused on the following areas:&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the most interesting parts I found was a list of tools they used, this means that I could integrate these tools in a pipeline to automate some of these checks.&lt;/p&gt;

&lt;p&gt;The following tools were used to perform the source code review:&lt;/p&gt;

&lt;h1&gt;
  
  
  What are the findings?
&lt;/h1&gt;

&lt;p&gt;There are a total of eight issues identified, one of them was low severity and seven were informational.&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker image runs as root
&lt;/h1&gt;

&lt;p&gt;The only low severity finding was that the docker image runs as root. I found it really nice that they provide the impact, recommendation and references to understand the issue.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Missing strict_types declaration
&lt;/h1&gt;

&lt;p&gt;The second finding is that all the PHP files are missing the strict_types declaration. This is not really an issue since I’m running PHPStan in Github actions to check for type errors.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Commented-out code
&lt;/h1&gt;

&lt;p&gt;Here you instantly notice that the tool isn’t made specifically for Laravel, it complains about comments in configuration files. It did find two places where commented code existed but then other 24 cases were configuration files.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Unresolved TODO comments
&lt;/h1&gt;

&lt;p&gt;Here is a valid point in my opinion, the codebase should not contain any TODO comments. They are only acceptable if there is an action planned to resolve them, such as a story on the backlog.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Error suppression with the @ operator
&lt;/h1&gt;

&lt;p&gt;Vigilant has a built-in crawler which reads HTML, sometimes HTML can be malformed which is why I’ve chosen to use the @ operator. This was a conscious decision and I’ve included a comment why it was added.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  High cyclomatic complexity
&lt;/h1&gt;

&lt;p&gt;It’s nice that the tool checks this but you do not always have control over how it must be implemented. For example, their first finding in the table needs to be implemented a certain way because that package requires it like that.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Short commit messages
&lt;/h1&gt;

&lt;p&gt;Vigilant is currently only developed by me which made me not really pay attention to the commit messages. I’ve since changed this by adding new code via pull requests with a clear description.&lt;/p&gt;

&lt;h1&gt;
  
  
  No healthcheck defined in Dockerfile
&lt;/h1&gt;

&lt;p&gt;I’ve added health checks in the docker compose, but not in the Dockerfile as the image is intended to only run via compose. I’m using the same image for the web container and the queue container so I’d need multi-stage builds to add separate health checks which are not my priority right now. It would be a good addition.&lt;/p&gt;

&lt;h1&gt;
  
  
  My feedback on the tool
&lt;/h1&gt;

&lt;p&gt;As this audit was done for free I was asked to provide feedback on the findings which I gladly did.&lt;/p&gt;

&lt;p&gt;First of all I agree with the strict types finding but I would like to have seen a note that the files are being checked by PHPStan. Also, the commented out code for Laravel projects in the config directory findings can be omitted.&lt;/p&gt;

&lt;p&gt;In the list of tools was Trivy, it would have been nice to have somewhere mentioned that the CVE’s have been scanned and none matched.&lt;/p&gt;

&lt;p&gt;Finally I’ve suggested that some checks on composer dependencies should be added, for example if there are low quality or abandoned external packages in the project I would like to know. The tool should also run its checks on these dependencies to get an idea of the quality.&lt;br&gt;
A package that has a few hundred downloads is much less reliable than a package that has thousands of downloads on Packagist as the chance of a high quality fork is higher when the original author decides to stop development.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;I’m happy that there aren’t serious issues in Vigilant’s source code, it was interesting to see what things are checked and how. This review confirmed the value of key design decisions like limiting dependencies.&lt;/p&gt;

&lt;p&gt;If you found this interesting, please consider starring the &lt;a href="https://github.com/govigilant/vigilant" rel="noopener noreferrer"&gt;repository&lt;/a&gt; 🙏&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/getting-my-laravel-application-security-audited" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on June 8, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>php</category>
      <category>security</category>
    </item>
    <item>
      <title>Architecture of my open source Laravel monitoring application</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Wed, 04 Jun 2025 18:50:31 +0000</pubDate>
      <link>https://dev.to/vincentbean/architecture-of-my-open-source-laravel-monitoring-application-51di</link>
      <guid>https://dev.to/vincentbean/architecture-of-my-open-source-laravel-monitoring-application-51di</guid>
      <description>&lt;p&gt;I’ve built this project using the Laravel Framework, I am familiar with the ecosystem and it contains everything I need to build a web monitoring application. Most importantly it has Horizon, a powerful queueing system.&lt;br&gt;
For the interface I’m using Livewire and Eloquent’s global scopes ensure that users do not see each other’s resources.&lt;br&gt;
It is also easy to dockerize for self-hosting utilizing Laravel Octane so that I don’t have to deal with Nginx and FPM.&lt;/p&gt;

&lt;p&gt;But a project with many different components also comes with a challenge, how to keep the codebase clean and organized? By default Laravel ships with an &lt;code&gt;app&lt;/code&gt; folder in which your models, controllers, and other logic go. This default structure works initially, but quickly becomes overwhelming as the application grows. Each component has models, jobs, and actions so I have a lot of different classes.&lt;br&gt;
We could create folders for each component but that will still be a lot in one place.&lt;br&gt;
The solution for me is to utilize the power of Composer.&lt;/p&gt;
&lt;h1&gt;
  
  
  The Power of Composer
&lt;/h1&gt;

&lt;p&gt;If you have worked on any modern PHP application you probably know &lt;a href="https://getcomposer.org/" rel="noopener noreferrer"&gt;Composer&lt;/a&gt;. It is the most popular package manager for PHP. Laravel also heavily utilizes Composer and there are a lot of Composer packages available for Laravel.&lt;/p&gt;

&lt;p&gt;One of the key architectural decisions in &lt;em&gt;Vigilant&lt;/em&gt; is treating each major feature as a standalone Composer package. To wire them all together, I use Composer’s &lt;strong&gt;path repository&lt;/strong&gt; feature. This is what that looks like in the composer json of the Laravel application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"repositories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;Then I add all the packages using Composer’s &lt;code&gt;@dev&lt;/code&gt; alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/certificates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/crawler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/cve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/dns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/frontend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/lighthouse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/notifications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/onboarding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/sites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/uptime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By structuring your code into independent packages, you naturally promote separation of concerns. Each feature becomes isolated. Models, migrations, jobs, and logic all live together, making your codebase easier maintain and you instantly know in what folder you need to look when you want to find a file.&lt;/p&gt;

&lt;p&gt;It also makes it easier to write isolated tests and reduce accidental coupling as the dependencies of the package’s composer json are required.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s inside a package?
&lt;/h1&gt;

&lt;p&gt;Each of the packages contains the following:&lt;/p&gt;

&lt;p&gt;The packages themselves are responsible for registering the things that they need such as migrations, routes, commands, and Livewire components. The main project does not force or expect any of these.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the &lt;code&gt;certificates&lt;/code&gt; package, this package contains all of the logic for Vigilant’s certificate monitoring. The composer json located in &lt;code&gt;packages/certificates/composer.json&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vigilant/certificates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vigilant Certificate Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AGPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"guzzlehttp/guzzle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"laravel/framework"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^12.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"livewire/livewire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/sites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/frontend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vigilant/notifications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dev"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"require-dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"laravel/pint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"larastan/larastan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"orchestra/testbench"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^10.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"phpstan/phpstan-mockery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"phpunit/phpunit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^11.0"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"autoload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"psr-4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Vigilant\\Certificates\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"autoload-dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"psr-4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Vigilant\\Certificates\\Tests\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tests"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extra"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"laravel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;"Vigilant&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Certificates&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;ServiceProvider"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phpunit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"analyse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phpstan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pint --test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;"@test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"@analyse"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"minimum-stability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prefer-stable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"repositories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see the package name is &lt;code&gt;vigilant/certificates&lt;/code&gt; which is how the main composer json requires it. Then in the &lt;code&gt;require&lt;/code&gt; list of this package are the other packages it depends on. For example, the certificate monitor needs to send notifications so it depends on &lt;code&gt;vigilant/notifications&lt;/code&gt; to do that.&lt;/p&gt;

&lt;p&gt;The service provider is set up just like any other Laravel Composer package, it registers routes, configuration, migrations, views, policies/gates, and Livewire components.&lt;/p&gt;

&lt;p&gt;But it does not only register the things Laravel needs, it also registers things Vigilant needs. For example, it registers the menu in the file &lt;code&gt;packages/certificates/resources/navigation.php&lt;/code&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Vigilant\Core\Facades\Navigation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Navigation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'certificates'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'Certificates'&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;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'phosphor-certificate'&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;gate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'use-certificates'&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;routeIs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'certificate*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it registers notifications with their conditions for the notification system:&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="nc"&gt;NotificationRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;registerNotification&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;    &lt;span class="nc"&gt;CertificateExpiresInDaysNotification&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="nc"&gt;CertificateExpiredNotification&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="nc"&gt;NotificationRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;registerCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CertificateExpiresInDaysNotification&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="nc"&gt;SiteCondition&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="nc"&gt;DaysCondition&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way each package is responsible for adding to the main application.&lt;/p&gt;

&lt;h1&gt;
  
  
  Quality checks
&lt;/h1&gt;

&lt;p&gt;I just quickly want to mention quality checks (tests, PHPStan, code style), these are done per package and if you have looked closely at the &lt;code&gt;composer.json&lt;/code&gt; of the certificates package you have seen that it contains a composer command called &lt;code&gt;quality&lt;/code&gt;.&lt;br&gt;
This way each package is responsible for which quality checks run.&lt;/p&gt;

&lt;p&gt;All these quality checks are run per package in a GitHub workflow using a matrix which makes them all run in parallel, this is a really cool feature that GitHub has. And the best thing is that it creates the matrix automatically using a &lt;code&gt;ls&lt;/code&gt; command in the packages folder. &lt;a href="https://github.com/govigilant/vigilant/blob/main/.github/workflows/package-quality.yml" rel="noopener noreferrer"&gt;Check out the GitHub action here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to handle common logic and views?
&lt;/h1&gt;

&lt;p&gt;Common logic is put in a special package called the core. The core contains things like validation rules, global scopes, and policies but also middleware and some utility classes.&lt;/p&gt;

&lt;p&gt;Frontend lives in two places, the layout is in the main project and the common components are in a package called &lt;code&gt;frontend&lt;/code&gt;. Each package that has frontend requires &lt;code&gt;vigilant/frontend&lt;/code&gt; and uses these components.&lt;/p&gt;

&lt;p&gt;The only exception is the scheduler, packages themselves do not register their scheduler. Instead this is handled in the main application so that we can quickly see what commands are being scheduled without having to jump through multiple service providers.&lt;/p&gt;

&lt;h1&gt;
  
  
  But Vigilant is also a SaaS
&lt;/h1&gt;

&lt;p&gt;Vigilant is an open-source project at heart but also has a SaaS component, this is the hosted version of the application. In this version I need things like billing, policies, and an admin panel.&lt;br&gt;
I have chosen to not publish these as open-source software to make it harder for people to create their own SaaS with my code. I think the benefits of open-source software outweigh the risks but I must keep this part closed source to mitigate some of that risk.&lt;/p&gt;

&lt;p&gt;These additional things I need for the SaaS are packaged in a separate SaaS repository. This repository is like the &lt;code&gt;packages&lt;/code&gt; folder in the main project, it contains multiple packages that all register themselves.&lt;/p&gt;

&lt;p&gt;During the build process of the SaaS version, it will do a composer require on the SaaS package which adds all the necessary packages.&lt;/p&gt;

&lt;p&gt;But as you can imagine the SaaS version has some different requirements, I’m using Laravel’s policies and gates to control resource limits but as you might know, if you check if someone may create a model and don’t have a policy for that model it will always return false. Vigilant has a setting in the configuration called &lt;code&gt;edition&lt;/code&gt;. This is &lt;code&gt;ce&lt;/code&gt; (Community Edition) by default. There is a global helper function to check if it is running in &lt;code&gt;ce&lt;/code&gt; mode, this is what the service providers use to add an allow all policy to the models. The SaaS version does not run in the &lt;code&gt;ce&lt;/code&gt; version and the SaaS package brings it’s own policies for all the models.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Building &lt;em&gt;Vigilant&lt;/em&gt; as a modular Laravel application has been a rewarding architectural decision. By leveraging Composer’s path repository feature, each part of the system, from certificates to the SaaS packages, is neatly encapsulated into its own package. This has made the codebase more organized, maintainable, and scalable as the project continues to grow.&lt;/p&gt;

&lt;p&gt;Laravel’s ecosystem, combined with tools like Livewire, Horizon, and Octane, provides everything needed to build a modern web application. But Laravel’s real power shines when you break away from the monolithic default structure and start thinking in terms of domains and components.&lt;/p&gt;

&lt;p&gt;Modularity doesn’t just help with code clarity, it also enables faster testing, clearer ownership of features, and an easier path to open-sourcing or scaling into SaaS. While managing multiple packages adds some overhead, the long-term benefits in code quality and team collaboration far outweigh the costs.&lt;/p&gt;

&lt;p&gt;If you found this helpful, please consider starring the &lt;a href="https://github.com/govigilant/vigilant" rel="noopener noreferrer"&gt;repository&lt;/a&gt; 🙏&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://govigilant.io/articles/architecture-of-my-open-source-laravel-monitoring-application" rel="noopener noreferrer"&gt;&lt;em&gt;https://govigilant.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on June 4, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My Laravel Horizon preferences after 5 years of using it</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Thu, 29 May 2025 20:04:24 +0000</pubDate>
      <link>https://dev.to/vincentbean/my-laravel-horizon-preferences-after-5-years-of-using-it-464k</link>
      <guid>https://dev.to/vincentbean/my-laravel-horizon-preferences-after-5-years-of-using-it-464k</guid>
      <description>&lt;p&gt;When working with Laravel Horizon, it’s easy to get started, run php artisan horizon, dispatch some jobs, monitor them in the dashboard, and you’re good to go. But as your application scales and the amount of jobs grow more complex, subtle issues begin to surface. The design of your jobs and configuration of Horizon and Redis must be correct in order to run Horizon without issues.&lt;/p&gt;

&lt;p&gt;I've been working with Laravel Horizon for the past five years on Laravel applications that do 1M+ jobs per day. In this article, I’ll share what I’ve learned from working with Laravel Horizon for five years. Including how I prefer to design jobs; how I isolate queues to prevent bottlenecks; and most importantly, the hidden pitfalls of Horizon's configuration and how to avoid them. If you’ve ever had a job mysteriously fail to dispatch or vanish without explanation, read this article.&lt;/p&gt;

&lt;p&gt;I've added a few code examples from my open source project, &lt;a href="https://govigilant.io/" rel="noopener noreferrer"&gt;Vigilant&lt;/a&gt;. An all-in-one website monitoring application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Designing Jobs: Slim, small and quick
&lt;/h3&gt;

&lt;p&gt;I prefer to keep my jobs slim, the job class should be responsible for how it runs on the queue and not running the actual logic of the action. I usually decouple the logic in seperate action class. Decoupling the logic from your job provides a few advantages, first of all your logic is not dependent on a job and can be called from anywhere. This is good practice as it makes your code more flexible. For example, if you later decide that you need the same code in a controller it is better to call a separate class with your logic than dispatching your job synchronously. Dispatching a job synchronously defeats the purpose of putting your code in a job anyway.&lt;/p&gt;

&lt;p&gt;Let's take a look at one of Vigilant's jobs, the &lt;em&gt;CheckUptimeJob&lt;/em&gt;. As you can guess this job is responsible for checking if a service is up.&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;Vigilant\Uptime\Jobs&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\Bus\Queueable&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\Contracts\Queue\ShouldBeUnique&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\Contracts\Queue\ShouldQueue&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\Foundation\Bus\Dispatchable&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\Queue\InteractsWithQueue&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\Queue\SerializesModels&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;Vigilant\Core\Services\TeamService&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;Vigilant\Uptime\Actions\CheckUptime&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;Vigilant\Uptime\Models\Monitor&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;CheckUptimeJob&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldBeUnique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&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;Dispatchable&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;InteractsWithQueue&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;Queueable&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;SerializesModels&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Monitor&lt;/span&gt; &lt;span class="nv"&gt;$monitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'uptime.queue'&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CheckUptime&lt;/span&gt; &lt;span class="nv"&gt;$uptime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;TeamService&lt;/span&gt; &lt;span class="nv"&gt;$teamService&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$teamService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setTeamById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$uptime&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;monitor&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;uniqueId&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&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;monitor&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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;As you can see, the handle method only sets the environment and calls the action. The constructor just accepts the properties it needs to run and it defines an unique id. All the jobs in Vigilant are written like this, they are just a way of running logic on the queue.&lt;/p&gt;

&lt;p&gt;I have also found that more jobs that have little runtime are better than a single job that does a lot and takes a while to complete. For example, when there are 100 sites that Vigilant needs to check the uptime for it will dispatch 100 jobs instead of one job that checks all the sites. This has a few benefits, first of all you can utilize Horizon's multiple processes per queue running the jobs in parallel.&lt;/p&gt;

&lt;p&gt;But the greatest benefit of running small jobs is error handling. When a job fails you know exactly with what arguments the job was ran (if you set the right tags!). That way it will be easier to find the cause of the failure. You can even implement the &lt;em&gt;failed&lt;/em&gt; method and add extra logging to your Eloquent model for example. That way you'll always know when something went wrong and what the exception was.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queue configuration: Isolate workloads
&lt;/h3&gt;

&lt;p&gt;Deciding on what queues to have, how many processes to allocate and how to configure them is heavily dependent on your application. If you do not have many different jobs a single queue is fine. As you scale you'll notice that you'll need to split them up so that unrelated processes don't block each other.&lt;/p&gt;

&lt;p&gt;For Vigilant I've chosen to separate the queues by the type of monitor. This way a web crawler cannot block an uptime monitor. These two types of monitor also have very different requirements. Uptime monitoring is strict and must run at a specified interval. That means that there always should be a worker available to pick up a job.&lt;br&gt;&lt;br&gt;
When crawling a website it is not a problem if a job is waiting in the queue for a few minutes, as long as it gets run.&lt;/p&gt;
&lt;h3&gt;
  
  
  Unique Jobs: Misconfiguration will cause them to never run
&lt;/h3&gt;

&lt;p&gt;Unique jobs are great for ensuring that a job is &lt;strong&gt;never&lt;/strong&gt; dispatched twice, but when misconfigured it can cause strange issues such as your job not dispatching. Most jobs in Vigilant are unique, for example, the uptime monitor dispatches a job at a specified interval but if for any reason the job hasn't executed within that interval I do not want another job on the queue.&lt;/p&gt;

&lt;p&gt;Unique jobs work by storing an unique lock key in your cache, as long as that key exist a new job will not be dispatched. Normally the key gets created when the job is dispatched and removed when a job finishes. It does not matter if your job completes successfully or fails, the lock will always be removed.&lt;/p&gt;

&lt;p&gt;I've written a whole article about this in the past, you can check it out &lt;a href="https://dev.toundefined"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Job timeouts
&lt;/h3&gt;

&lt;p&gt;When you've worked with Horizon you'll probably recognize the job timeout exceeded exception. I just want to highlight two configuration entires that you should configure based on your slowest job.&lt;br&gt;&lt;br&gt;
First of all the &lt;em&gt;timeout&lt;/em&gt; setting in your Horizon queue worker which can be found in &lt;em&gt;config/horizon.php&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Secondly Laravel's &lt;em&gt;config/queue.php&lt;/em&gt; contains the &lt;em&gt;retry_after&lt;/em&gt; setting which you should configure at the same value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Redis configuration for Horizon
&lt;/h3&gt;

&lt;p&gt;As Horizon is built on Redis you cannot skip this configuration. There are two settings that are important:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;maxmemory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;maxmemory-policy&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The maxmemory option should be obvious, Redis should have enough memory to store your jobs. But the more important option of the two is the policy. What should Redis do if the memory is full? These are the options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# volatile-lru -&amp;gt; Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -&amp;gt; Evict any key using approximated LRU.
# volatile-lfu -&amp;gt; Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -&amp;gt; Evict any key using approximated LFU.
# volatile-random -&amp;gt; Remove a random key having an expire set.
# allkeys-random -&amp;gt; Remove a random key, any key.
# volatile-ttl -&amp;gt; Remove the key with the nearest expire time (minor TTL)
# noeviction -&amp;gt; Don't evict anything, just return an error on write operations.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When working with jobs we never want to remove any. All jobs that are pushed onto the queue should be picked up by Horizon. With this knowledge the only way we can guarantee that all jobs will be picked up is by setting the policy to &lt;em&gt;noeviction&lt;/em&gt;. This means that the part of your application that dispatches the job will get an exception when Redis is full. This is preferable as the alternative is that a random job will be removed and with an exception you get alerted when Redis is full.&lt;/p&gt;

&lt;p&gt;While the no eviction policy is the best for queues, it's not the best for cache. This is why I always run two Redis instances, one for the queue and one for cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring Horizon beyond the basics
&lt;/h3&gt;

&lt;p&gt;Horizon's default dashboarding is lacking, it shows the basic information but not enough when you start running a large amount of jobs. It is possible to expand the monitoring of Horizon because Horizon and Laravel's queueing system provide a few events that you can hook into. You can also query the Redis database to gain specific insights.&lt;/p&gt;

&lt;p&gt;In the past I've written an &lt;a href="https://github.com/VincentBean/horizon-extended-dashboard" rel="noopener noreferrer"&gt;alternative dashboard&lt;/a&gt; for Horizon which I've since have abandoned in favour of Prometheus / Grafana. I've found that collecting metrics and publishing them to Prometheus is far superior than storing them in a MySQL database. I do not have any open source code for this way monitoring but here are a few ideas which are possible:&lt;br&gt;&lt;br&gt;
Export all pending, completed and failed jobs over time so that you get a historic overview of what runs when and how long large processes take.&lt;br&gt;&lt;br&gt;
Keep track of job runtimes to get an idea which jobs are the slowest and can be optimized.&lt;br&gt;&lt;br&gt;
Count the unique jobs and unique locks to check for differences.&lt;/p&gt;

&lt;p&gt;As Horizon is built on Redis it is also important to monitor Redis, especially the memory and maxmemory usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;After five years of working with Laravel Horizon, my biggest takeaway is this: Horizon is incredibly powerful—but only if you treat it with care and intention.&lt;/p&gt;

&lt;p&gt;Many of the issues developers face with Horizon aren’t bugs, it is usually incorrect configuration.&lt;br&gt;&lt;br&gt;
Whether it's isolating queues to prevent bottlenecks, understanding how unique job locks work under the hood, or fine-tuning Redis configuraiton to avoid deleted jobs.&lt;/p&gt;

&lt;p&gt;The strategies I’ve shared, from designing slim, single-responsibility jobs to building out advanced monitoring with Prometheus have helped me build reliable, observable, and maintainable queue systems. If you're just starting with Horizon or you're troubleshooting mysterious job behavior, I hope this post helps you avoid some of the sharp edges I’ve encountered along the way.&lt;/p&gt;

&lt;p&gt;Thanks for reading, feel free to dig into &lt;a href="https://github.com/govigilant/vigilant" rel="noopener noreferrer"&gt;Vigilant&lt;/a&gt; if you’re curious how these patterns look in a real world app.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>redis</category>
      <category>php</category>
    </item>
    <item>
      <title>How I plan on scaling my Laravel (PHP) application</title>
      <dc:creator>Vincent Boon</dc:creator>
      <pubDate>Wed, 22 Jan 2025 17:44:58 +0000</pubDate>
      <link>https://dev.to/vincentbean/how-i-plan-on-scaling-my-laravel-php-application-1n0n</link>
      <guid>https://dev.to/vincentbean/how-i-plan-on-scaling-my-laravel-php-application-1n0n</guid>
      <description>&lt;p&gt;I am building &lt;a href="https://10mpage.com/" rel="noopener noreferrer"&gt;10MPage.com&lt;/a&gt; which captures the state of the internet in 2025. Every internet user is allowed to upload a small image of 64x64 pixels and contribute to this archive. That means you too! ;)&lt;/p&gt;

&lt;p&gt;As a solo developer I want to spend as little money as possible in the early stages of this project which is why I host on a cheap VPS. &lt;br&gt;
But I do want to be prepared for what comes next, when this project grows I need to have a plan on scaling the hosting of this application.&lt;/p&gt;

&lt;h1&gt;
  
  
  The application and the services it requires
&lt;/h1&gt;

&lt;p&gt;The application is written in PHP using the Laravel framework and currently hosted on a single machine. The application heavily relies on background processes for handling the images, finding free places on the grid to place new images and sending e-mails.&lt;/p&gt;

&lt;p&gt;The background processes are run through Laravel Horizon which is a queueing system that uses a long lived PHP process. This is running via supervisor to ensure that it is automatically restarted when it stops. Each deployment of the application the process will stop and be restarted by supervisor.&lt;/p&gt;

&lt;p&gt;All jobs and caching are done with Redis and the data is stored in a MySQL database. Web requests are served through nginx using PHP-FPM.&lt;br&gt;
The current setup looks like this:&lt;/p&gt;

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

&lt;p&gt;I am aware that there are services like Laravel Forge but they require an extra subscription which is more that my single server costs. For this reason I am setting everything up myself on a simple VPS.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scaling up
&lt;/h1&gt;

&lt;p&gt;The first and most simple way to scale up is to upgrade the VPS to a bigger machine. This can be done within a few minutes and has some downtime. But it's a good way to buy some time if scaling is needed due to high load to setup other servers.&lt;/p&gt;

&lt;p&gt;To scale even more I need seperate servers. I'd like to do this with as little downtime as possible so here's my plan in (count) steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Move Redis to a seperate server&lt;/li&gt;
&lt;li&gt;Add a load balancer&lt;/li&gt;
&lt;li&gt;Create new worker server(s)&lt;/li&gt;
&lt;li&gt;Add webservers&lt;/li&gt;
&lt;li&gt;Old webserver becomes the new database server&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step one, Redis
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Downtime: None&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd start with moving Redis to another server. The web appliction can be configured to temporarily use the local file system for caching and the job queue can be temporarily shut down. I'd setup a new server and add it to a private network. I am not bothering with clustering Redis as Laravel Horizon does not support Redis clusters.&lt;br&gt;
After Redis is moved we can turn it off and uninstall it on our original server.&lt;/p&gt;

&lt;p&gt;At this point I'd have two servers, one Redis server and one web/database/worker server handling the rest.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step two, load balancer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Downtime: None&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In order to load balance across multiple web server we first need a load balancer. I've got experience with HAProxy so that is what I'll be using. It has a little more options than nginx for load balancing such as active health checks and more load algorithms. &lt;br&gt;
This server will also handle SSL termination.&lt;/p&gt;

&lt;p&gt;For this second step I will still be running the web server on our first server. After this load balancer is setup I will change the DNS to point to this server. &lt;/p&gt;

&lt;p&gt;At this point the setup has three servers, one Redis server, a load balancer and our first server still handling the web requests, workers and database.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step three, seperate worker servers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Downtime: None&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As I explained before, the background worker is essential for running this project. It needs to talk to Redis and the database to do it's work and can be turned of for a little while. The application is designed so that it will always start off where it was last left. This is done by using states, for example a pending tile can have the state 'place' so that the worker knows that it must place that tile. After placement the status will change.&lt;/p&gt;

&lt;p&gt;Laravel Horizon is also designed to run on multiple servers, that means that I do not have to shut down the current worker when adding a new one. The process of seperating this to another server is straightforward. We create a new server, deploy and configure the application and use supervisor to run the worker. &lt;/p&gt;

&lt;p&gt;After that we can shutdown the worker on the original server. Scaling workers is as easy as replicating a worker server.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step four, multiple webservers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Downtime: None&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We've already added a load balancer, to seperate the webservers we can follow the same steps as the worker server. But instead of running Horizon with supervisor the server will serve the application via HTTP using nginx and FPM. Also here, when we've got one server we can simply replicate it. The only configuration that has to be done is adding the webservers to the load balancer. &lt;/p&gt;

&lt;p&gt;At this point we have a single redis server, a single database server, a single loadbalancer, seperate worker servers and seperate web servers.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Set five, single database server
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Downtime: Few minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original server now has only one function, the database. In order to keep it clean I will remove any other software that is not required on this server. Nginx, supervisor, redis and PHP can all go away. As the database server is one of the most important parts of the application I need it to have enough capacity. Clustering is an option but also a lot of work. I think that one big DB server is fine for this project but that does come with a cost. Scaling up will require a server reboot during which the application does not work.&lt;/p&gt;

&lt;p&gt;And there we have it, all five steps of my scaling plan for 10MPage. Here are all te steps visualized:&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Application deployments
&lt;/h1&gt;

&lt;p&gt;Deployments have to be adjusted, initially there was only one place where the app had to go. But with multiple servers running the application (web and worker servers) it now must be deployed to all of them.&lt;/p&gt;

&lt;p&gt;I am using git based deployments, I have a small script included in the repository that deploys the application. For a single setup it does all steps but with seperate servers it must do a few checks to know what to deploy. For example, I can run &lt;code&gt;php artisan horizon:status&lt;/code&gt; to see if Horizon is running and only restrt it when it is. I can do the same for PHP-FPM.&lt;/p&gt;

&lt;p&gt;The only adjustment is that it needs to run on multiple servers. I will create a simple script that triggers this on all of the servers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Single point of failure
&lt;/h1&gt;

&lt;p&gt;I am aware that this setup has multiple single points of failure.&lt;br&gt;
The points of failure are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single load balancer&lt;/li&gt;
&lt;li&gt;Single database server&lt;/li&gt;
&lt;li&gt;Single Redis server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step in this setup would be to eliminate some single points of failure. The load balancer can be setup redundant on two servers with a floating IP, so when one fails the floating IP can be switched to the other node.&lt;/p&gt;

&lt;p&gt;The database and redis server are another story, if I need to scale those up I will write a follow up article on that. But I do not expect it happen.&lt;/p&gt;

&lt;h1&gt;
  
  
  A note on containers / clusters
&lt;/h1&gt;

&lt;p&gt;I'm a big fan of running applications in containers and the scalability of them is great. But for smaller projects like this I believe that it is overkill to containerize the application and deploy it on a cluster. I'm keeping the hosting setup simple to that I can set it up as quickly as possible in the initial phase of the project. I do not see it needing things like autoscaling. The only reason for using containers in this project would be the ease of setup when scaling to multiple servers. But since this project does not require much I decided that I'd rather make snapshots and clone machines when I need to scale &lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Scaling &lt;a href="https://10mpage.com/" rel="noopener noreferrer"&gt;10MPage.com&lt;/a&gt; is an exciting challenge that I approach with a pragmatic mindset, keeping the infrastructure simple yet effective. Starting with a minimal setup on a single VPS allows for cost efficiency in the early stages, while the outlined scaling plan ensures a smooth transition as the project grows. Each step, from moving Redis to a separate server to implementing a load balancer, adding workers, and deploying multiple web servers, is designed to minimize downtime and maintain the application’s functionality.&lt;/p&gt;

&lt;p&gt;By keeping the hosting straightforward and avoiding the complexities of containerization or clusters, I can focus on building the project and adapting as needs evolve. This deliberate approach balances preparation with simplicity, ensuring &lt;a href="https://10mpage.com/" rel="noopener noreferrer"&gt;10MPage.com&lt;/a&gt; remains robust, scalable, and aligned with its mission to capture the essence of the internet in 2025—one tile at a time.&lt;/p&gt;

&lt;p&gt;Thank you for reading this artile, I hope you've learned something.&lt;br&gt;
If you did, why not add your favorite programming language, crypto coin or your pet to the 10MPage? It is free!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>php</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
