<?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: Edgaras</title>
    <description>The latest articles on DEV Community by Edgaras (@edgaras).</description>
    <link>https://dev.to/edgaras</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1268816%2Fcda2beb0-3f38-429c-a71d-55dd562a7e54.png</url>
      <title>DEV Community: Edgaras</title>
      <link>https://dev.to/edgaras</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edgaras"/>
    <language>en</language>
    <item>
      <title>Deploying PaddleOCR PDF OCR service on Azure Container Apps</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Mon, 17 Aug 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/edgaras/deploying-paddleocr-pdf-ocr-service-on-azure-container-apps-2lp5</link>
      <guid>https://dev.to/edgaras/deploying-paddleocr-pdf-ocr-service-on-azure-container-apps-2lp5</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/Edgaras0x4E/paddleocr-pdf-api" rel="noopener noreferrer"&gt;paddleocr-pdf-api&lt;/a&gt; converts PDFs to markdown over an HTTP API. This post deploys it to Azure Container Apps as a single container app with an HTTP ingress.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/edgaras/using-a-self-hosted-pdf-ocr-api-with-paddleocr-3k52"&gt;Using a Self-Hosted PDF OCR API with PaddleOCR&lt;/a&gt; describes the API itself. Read that one first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engines and workload profiles
&lt;/h2&gt;

&lt;p&gt;Three OCR engines are available, one per image tag. Each image contains its model weights, and Container Apps has a workload profile for each one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Image tag&lt;/th&gt;
&lt;th&gt;Models&lt;/th&gt;
&lt;th&gt;Needs&lt;/th&gt;
&lt;th&gt;Workload profile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;latest-text-baked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PP-OCRv5 detection and recognition&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Consumption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;latest-vl-baked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PaddleOCR-VL-1.6 plus PP-DocLayoutV3&lt;/td&gt;
&lt;td&gt;~8.5 GB VRAM&lt;/td&gt;
&lt;td&gt;Consumption-GPU-NC8as-T4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;structure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;latest-structure-baked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PP-StructureV3&lt;/td&gt;
&lt;td&gt;~10.5 GB VRAM&lt;/td&gt;
&lt;td&gt;Consumption-GPU-NC8as-T4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Consumption profile allows 0.25 to 4 vCPU and 0.5 to 8 GiB per replica, in a fixed 1:2 ratio. The two GPU profiles are &lt;code&gt;Consumption-GPU-NC8as-T4&lt;/code&gt; and &lt;code&gt;Consumption-GPU-NC24-A100&lt;/code&gt;. A T4 has 16 GB of VRAM, which is enough for both GPU engines.&lt;/p&gt;

&lt;p&gt;This deployment uses the &lt;code&gt;text&lt;/code&gt; engine on the Consumption profile.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;latest&lt;/code&gt; tag contains no model weights. &lt;code&gt;OCR_ENGINE&lt;/code&gt; defaults to &lt;code&gt;vl&lt;/code&gt;, so it downloads PaddleOCR-VL-1.6 and PP-DocLayoutV3 on first run. That download repeats on every replica replacement, so the baked tags are the ones to deploy here.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rg-paddleocr
&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;swedencentral
&lt;span class="nv"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;env-paddleocr
&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;paddleocr-api

az group create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOC&lt;/span&gt;

az containerapp &lt;span class="nb"&gt;env &lt;/span&gt;create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$ENV&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOC&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-workload-profiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying the app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;

az containerapp create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$APP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment&lt;/span&gt; &lt;span class="nv"&gt;$ENV&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--workload-profile-name&lt;/span&gt; Consumption &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt; docker.io/edgaras0x4e/paddleocr-pdf-api:latest-text-baked &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-port&lt;/span&gt; 8000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ingress&lt;/span&gt; external &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpu&lt;/span&gt; 4.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 8.0Gi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min-replicas&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-replicas&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secrets&lt;/span&gt; api-key&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-vars&lt;/span&gt; &lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secretref:api-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deployment needs no other Azure resource. The models are in the image, and job state is written to the replica's own storage. Mounting Azure Files replaces that storage, and setting &lt;code&gt;DATABASE_URL&lt;/code&gt; moves the database to PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replica settings
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;POST /ocr&lt;/code&gt; writes the file, records the job as queued, and returns. A background thread does the OCR. No request stays open while a document is processed.&lt;/p&gt;

&lt;p&gt;Container Apps adds an HTTP scale rule when you do not define one. It scales on request count, and its minimum is zero replicas. OCR runs without requests, so nothing keeps a replica running.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minReplicas: 0&lt;/code&gt; scales the app to zero while a job is running. Pages already written survive if state persists, and the job resumes on the next request. On ephemeral storage the job and its results are lost.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minReplicas: 1&lt;/code&gt; keeps one replica running, and a job finishes without traffic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;maxReplicas: 1&lt;/code&gt; is not optional here. Jobs are stored in SQLite on the replica's own disk, so each replica has a separate database. A status poll can reach a replica that does not know the job and returns 404. Running more replicas needs PostgreSQL for the records and a shared mount for the uploads. With PostgreSQL every replica sees every queued job, so the replica that claims one may not be the replica that received the upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FQDN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az containerapp show &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$APP&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; properties.configuration.ingress.fqdn &lt;span class="nt"&gt;-o&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;JOB_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$FQDN&lt;/span&gt;&lt;span class="s2"&gt;/ocr"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@document.pdf"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .job_id&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$FQDN&lt;/span&gt;&lt;span class="s2"&gt;/ocr/&lt;/span&gt;&lt;span class="nv"&gt;$JOB_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The models load on the worker thread, so the API answers before OCR can start. A job submitted in that window stays &lt;code&gt;queued&lt;/code&gt; until the models are ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making job state survive a restart
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/data&lt;/code&gt; contains the SQLite database and the uploaded PDFs. Each replica gets ephemeral storage, so both are lost when the replica is replaced. Replacement happens on every revision change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Survives replica loss&lt;/th&gt;
&lt;th&gt;Requires&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQLite on ephemeral storage&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL through &lt;code&gt;DATABASE_URL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Results yes, running job no&lt;/td&gt;
&lt;td&gt;A database server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Files mount on &lt;code&gt;/data&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;A storage account and share&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PostgreSQL moves the records off the replica, and several replicas can then share them. The uploaded files stay local.&lt;/p&gt;

&lt;p&gt;An Azure Files mount keeps both the SQLite database and the uploaded files. Create a storage account and a file share, then register the share with the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;STORAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;stpaddleocr&lt;span class="nv"&gt;$RANDOM&lt;/span&gt;

az storage account create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOC&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sku&lt;/span&gt; Standard_LRS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--kind&lt;/span&gt; StorageV2

az storage share-rm create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; ocr-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-account&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--quota&lt;/span&gt; 100

&lt;span class="nv"&gt;STORAGE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az storage account keys list &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--account-name&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"[0].value"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;

az containerapp &lt;span class="nb"&gt;env &lt;/span&gt;storage &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$ENV&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-name&lt;/span&gt; ocrdata &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--azure-file-account-name&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--azure-file-account-key&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STORAGE_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--azure-file-share-name&lt;/span&gt; ocr-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-mode&lt;/span&gt; ReadWrite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;az containerapp update&lt;/code&gt; has no flag for Azure Files mounts, so the mount goes in YAML:&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="c1"&gt;# mount.yaml&lt;/span&gt;
&lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paddleocr-api&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;docker.io/edgaras0x4e/paddleocr-pdf-api:latest-text-baked&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4.0&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8.0Gi&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API_KEY&lt;/span&gt;
            &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-key&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;volumeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ocr-data&lt;/span&gt;
            &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ocr-data&lt;/span&gt;
        &lt;span class="na"&gt;storageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ocrdata&lt;/span&gt;
        &lt;span class="na"&gt;storageType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureFile&lt;/span&gt;
        &lt;span class="na"&gt;mountOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dir_mode=0777,file_mode=0777,uid=0,gid=0,mfsymlinks,nobrl"&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp update &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$APP&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="nt"&gt;--yaml&lt;/span&gt; mount.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nobrl&lt;/code&gt; is required. Azure Files is an SMB share, and SQLite locks the database with byte-range locks that the SMB client forwards to the server. Those locks fail on SMB, so without &lt;code&gt;nobrl&lt;/code&gt; the app cannot open its database and the container does not start.&lt;/p&gt;

&lt;p&gt;Locks handled locally are invisible to other clients of the share. That is one more reason &lt;code&gt;maxReplicas&lt;/code&gt; stays at 1: two replicas writing the same database file would not see each other's locks.&lt;/p&gt;

&lt;p&gt;A completed job survives a replica restart with this mount in place. A job that ran before the mount does not.&lt;/p&gt;

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

&lt;p&gt;Container Apps runs the image without changes. The deployment is a resource group, an environment, and one &lt;code&gt;az containerapp create&lt;/code&gt;. Job state that has to outlive a replica adds a storage account, a file share, and &lt;code&gt;nobrl&lt;/code&gt; in the mount options.&lt;/p&gt;

&lt;p&gt;OCR runs without open requests, so &lt;code&gt;minReplicas: 0&lt;/code&gt; can stop a job in progress, and &lt;code&gt;maxReplicas&lt;/code&gt; stays at 1 as long as the database is SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bicep template
&lt;/h2&gt;

&lt;p&gt;One file deploys the same resources: the storage account, the share, the environment, the share registration, and the app with the mount.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.bicep
param location string = resourceGroup().location
@secure()
param apiKey string
param storageAccountName string = 'stpaddleocr${uniqueString(resourceGroup().id)}'

resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageAccountName
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
}

resource share 'Microsoft.Storage/storageAccounts/fileServices/shares@2023-05-01' = {
  name: '${storage.name}/default/ocr-data'
  properties: { shareQuota: 100 }
}

resource env 'Microsoft.App/managedEnvironments@2024-03-01' = {
  name: 'env-paddleocr'
  location: location
  properties: {
    workloadProfiles: [
      { name: 'Consumption', workloadProfileType: 'Consumption' }
    ]
  }
}

resource envStorage 'Microsoft.App/managedEnvironments/storages@2024-03-01' = {
  parent: env
  name: 'ocrdata'
  properties: {
    azureFile: {
      accountName: storage.name
      accountKey: storage.listKeys().keys[0].value
      shareName: 'ocr-data'
      accessMode: 'ReadWrite'
    }
  }
  dependsOn: [ share ]
}

resource app 'Microsoft.App/containerApps@2024-03-01' = {
  name: 'paddleocr-api'
  location: location
  properties: {
    managedEnvironmentId: env.id
    workloadProfileName: 'Consumption'
    configuration: {
      ingress: {
        external: true
        targetPort: 8000
      }
      secrets: [
        { name: 'api-key', value: apiKey }
      ]
    }
    template: {
      containers: [
        {
          name: 'paddleocr-api'
          image: 'docker.io/edgaras0x4e/paddleocr-pdf-api:latest-text-baked'
          resources: {
            cpu: json('4.0')
            memory: '8.0Gi'
          }
          env: [
            { name: 'API_KEY', secretRef: 'api-key' }
          ]
          volumeMounts: [
            { volumeName: 'ocr-data', mountPath: '/data' }
          ]
        }
      ]
      volumes: [
        {
          name: 'ocr-data'
          storageName: envStorage.name
          storageType: 'AzureFile'
          mountOptions: 'dir_mode=0777,file_mode=0777,uid=0,gid=0,mfsymlinks,nobrl'
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 1
      }
    }
  }
}

output fqdn string = app.properties.configuration.ingress.fqdn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOC&lt;/span&gt;

az deployment group create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; main.bicep &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameters&lt;/span&gt; &lt;span class="nv"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployment prints the app's hostname as the &lt;code&gt;fqdn&lt;/code&gt; output.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>docker</category>
      <category>ocr</category>
      <category>containers</category>
    </item>
    <item>
      <title>UML Component Diagrams</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Thu, 13 Aug 2026 19:05:51 +0000</pubDate>
      <link>https://dev.to/edgaras/uml-component-diagrams-3fa8</link>
      <guid>https://dev.to/edgaras/uml-component-diagrams-3fa8</guid>
      <description>&lt;p&gt;A UML component diagram shows the units a software system is assembled from, the interfaces each unit provides, the interfaces it requires, and how those interfaces are wired together. UML 2.5.1 defines a component as a modular part of a system that encapsulates its contents and whose manifestation is replaceable within its environment.&lt;/p&gt;

&lt;p&gt;A component diagram describes a system in larger pieces than a class diagram: each component groups several classes into one unit that can be replaced on its own, and hardware does not appear. Architects use it to define boundaries before implementation starts. Teams that own separate services use it to list the operations one service calls on another.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a UML Component Diagram?
&lt;/h2&gt;

&lt;p&gt;A component diagram is a structural diagram. It shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Components&lt;/strong&gt;: modular units whose internals are hidden and reachable only through their interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provided interfaces&lt;/strong&gt;: implemented by the component. Other components call them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required interfaces&lt;/strong&gt;: called by the component but implemented elsewhere. The component cannot run until something supplies them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ports&lt;/strong&gt;: named connection points on a component boundary that group provided and required interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connectors&lt;/strong&gt;: an assembly connector joins one component's required interface to another's provided interface. A delegation connector passes requests from the outer boundary to the part that handles them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realizing classifiers and artifacts&lt;/strong&gt;: the classes that implement the component's behavior, and the files it is packaged into. Both can be listed inside the box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A component can be as abstract as a billing unit in a design, or as concrete as a service you deploy and version on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Component Diagrams?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agree which operations two services provide to each other before either is written.&lt;/li&gt;
&lt;li&gt;Make dependency direction explicit, so a cycle between two components is easy to see.&lt;/li&gt;
&lt;li&gt;Decide which units can be versioned and released independently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core Components and Notation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Component
&lt;/h3&gt;

&lt;p&gt;A component is drawn as a classifier rectangle with the keyword &lt;code&gt;«component»&lt;/code&gt;. A component icon may be placed in the right hand corner. When the icon is shown, the keyword may be hidden.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1jlr1gfqh0szfkenlphs.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1jlr1gfqh0szfkenlphs.png" alt="UML Component Diagram Keyword Form and Icon Form" width="753" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Provided Interface
&lt;/h3&gt;

&lt;p&gt;A provided interface is a small circle on the end of a line from the component, labeled with the interface name. The symbol is called a lollipop or a ball. The component implements every operation that interface declares.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frylnhjtz7ivsaabaeqz1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frylnhjtz7ivsaabaeqz1.png" alt="UML Component Diagram Provided Interface Ball Notation" width="636" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Required Interface
&lt;/h3&gt;

&lt;p&gt;A required interface is a half circle on the end of a line from the component, labeled with the interface name. The symbol is called a socket. The component calls those operations but does not implement them, so something else has to supply them.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnkpqz9zdouci5trhcd98.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnkpqz9zdouci5trhcd98.png" alt="UML Component Diagram Required Interface Socket Notation" width="641" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Assembly Connector
&lt;/h3&gt;

&lt;p&gt;When one component requires an interface that another provides, draw the socket touching the ball. UML calls this an assembly connector.&lt;/p&gt;

&lt;p&gt;Ball-and-socket only wires ports that carry exactly one interface. If a port carries several, or the parts have no ports, draw a plain connector line instead.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83nyrqjn2hj7yatj7czu.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83nyrqjn2hj7yatj7czu.png" alt="UML Component Diagram Assembly Connector Ball and Socket" width="799" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Port
&lt;/h3&gt;

&lt;p&gt;A port is drawn as a small square on the component boundary, or inside it. The name is placed near the square, the type after a colon. Lollipops and sockets attach to the port rather than to the component body. A port can carry more than one interface. Instead of drawing a separate lollipop for each, draw one and list the names on it: &lt;code&gt;IPlayback, IDownload&lt;/code&gt;. A port with several interfaces still cannot be wired with ball-and-socket, so its connections use plain lines.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fityes3qqpdfg8ben62kt.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fityes3qqpdfg8ben62kt.png" alt="UML Component Diagram Port Notation" width="630" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Delegation Connector
&lt;/h3&gt;

&lt;p&gt;A component can show the parts it is built from. A delegation connector is a line from a port on the component's own boundary to a port on one of those parts. Requests arriving from outside are handled by that part.&lt;/p&gt;

&lt;p&gt;There is no special arrowhead or label. What makes the line a delegation is where it attaches: one end is on the component's own boundary. If both ends attach to inner parts, the same line is an assembly connector.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F96p05lmxfztoj6zrp0zd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F96p05lmxfztoj6zrp0zd.png" alt="UML Component Diagram Delegation Connector Internal Structure" width="799" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ball and socket symbols carry only the interface name. When operation signatures matter, draw the interface as a classifier rectangle with the keyword &lt;code&gt;«interface»&lt;/code&gt; and connect it with dependency arrows. A provided interface uses an InterfaceRealization: a dashed line with a hollow triangle arrowhead. A required interface uses a Usage dependency: a dashed line with an open arrowhead and the keyword &lt;code&gt;«use»&lt;/code&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5t6njb6898l7hcaqio5y.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5t6njb6898l7hcaqio5y.png" alt="UML Component Diagram Interface Realization and Usage Dependency" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Standard Stereotypes for Components
&lt;/h3&gt;

&lt;p&gt;From the UML 2.5.1 standard profile:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stereotype&lt;/th&gt;
&lt;th&gt;Applies to&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;«Subsystem»&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Component&lt;/td&gt;
&lt;td&gt;A unit of hierarchical decomposition for large systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;«Service»&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Component&lt;/td&gt;
&lt;td&gt;A stateless, functional component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;«Specification»&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classifier&lt;/td&gt;
&lt;td&gt;A component with interfaces only, and no realizing classifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;«Realization»&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classifier&lt;/td&gt;
&lt;td&gt;A component whose realizing classifiers implement a separate &lt;code&gt;«Specification»&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Practical Example: Video Streaming Platform
&lt;/h2&gt;

&lt;p&gt;The diagram below models the main components of a video streaming platform.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flislr53bxmhvr0c79xjs.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flislr53bxmhvr0c79xjs.png" alt="UML Component Diagram Video Streaming Platform Example" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The relationships it shows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;WebClient&lt;/code&gt; requires &lt;code&gt;IStreamCatalog&lt;/code&gt; and &lt;code&gt;IPlayback&lt;/code&gt;. It provides nothing, so it carries only sockets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CatalogService&lt;/code&gt; provides &lt;code&gt;IStreamCatalog&lt;/code&gt;, which returns title listings and search results.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CatalogService&lt;/code&gt; requires &lt;code&gt;IMediaMetadata&lt;/code&gt; from &lt;code&gt;MediaStore&lt;/code&gt; to read durations, resolutions, and cover art.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PlaybackService&lt;/code&gt; provides &lt;code&gt;IPlayback&lt;/code&gt;, and requires &lt;code&gt;ILicense&lt;/code&gt; from &lt;code&gt;LicenseService&lt;/code&gt; to check playback rights before a stream starts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PlaybackService&lt;/code&gt; also requires &lt;code&gt;IMediaStorage&lt;/code&gt; from &lt;code&gt;MediaStore&lt;/code&gt; to read video segments.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MediaStore&lt;/code&gt; provides two separate interfaces, so &lt;code&gt;CatalogService&lt;/code&gt; gets metadata access without the ability to read video segments.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;LicenseService&lt;/code&gt; and &lt;code&gt;MediaStore&lt;/code&gt; require nothing, so each can be tested alone. &lt;code&gt;PlaybackService&lt;/code&gt; needs two test doubles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name the interface, not the connection&lt;/strong&gt;: &lt;code&gt;ILicense&lt;/code&gt; states what crosses the boundary. A line labeled "calls license service" does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split interfaces by responsibility&lt;/strong&gt;: one interface per concern lets a consumer depend on only the part it uses, as &lt;code&gt;MediaStore&lt;/code&gt; does above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add ports when a component has many interfaces&lt;/strong&gt;: named groups keep the boundary readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep internal structure on its own diagram&lt;/strong&gt;: the external view answers what a component needs. Draw parts and delegation connectors separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match component boundaries to units you can release&lt;/strong&gt;: if two components always ship together, the boundary between them is not doing any work.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;A component diagram shows the pieces of a system and what each one needs from the others. Two teams can use it to settle an interface list before either writes code. It also shows which pieces are independent enough to release on their own.&lt;/p&gt;

&lt;p&gt;It records no timing. The diagram states that &lt;code&gt;PlaybackService&lt;/code&gt; requires &lt;code&gt;ILicense&lt;/code&gt;, but not whether the license check runs before the first byte of video or halfway through. Order belongs on a &lt;a href="https://dev.to/edgaras/uml-sequence-diagrams-18e4"&gt;sequence diagram&lt;/a&gt;. For where each component actually runs, use a &lt;a href="https://dev.to/edgaras/uml-deployment-diagrams-15c2"&gt;deployment diagram&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>uml</category>
      <category>architecture</category>
      <category>systems</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>A Self-Hosted Web Content Extraction API</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Wed, 20 May 2026 09:59:37 +0000</pubDate>
      <link>https://dev.to/edgaras/a-self-hosted-web-content-extraction-api-40n3</link>
      <guid>https://dev.to/edgaras/a-self-hosted-web-content-extraction-api-40n3</guid>
      <description>&lt;p&gt;Getting clean content out of a web page is harder than it looks, especially at scale. Every site is put together differently, so a scraper that works on one page falls apart on the next, and the part you actually care about is buried in menus, ads, cookie banners, and scripts. You can feed the whole page to an LLM and let it pull the content out, or pay for an extraction API, but both get expensive once you are handling more than a handful of pages. Many sites also render their content with JavaScript, so a plain HTTP fetch returns almost nothing to begin with.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Edgaras0x4E/web-loader-engine" rel="noopener noreferrer"&gt;Web Loader Engine&lt;/a&gt; is a web content extraction service built in Rust that handles this. It loads each page in a headless Chromium browser and runs it through Mozilla Readability, so you get the actual content without the clutter around it. It can return Markdown, HTML, plain text, or a screenshot, all through a HTTP API call. Useful for RAG pipelines, scraping, archiving pages, and screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;p&gt;Pull and run the prebuilt image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 14786:14786 &lt;span class="nt"&gt;--name&lt;/span&gt; web-loader &lt;span class="se"&gt;\&lt;/span&gt;
  edgaras0x4e/web-loader-engine:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that it is up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:14786/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole setup. No headless browser to install, no driver to wire up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first extraction
&lt;/h2&gt;

&lt;p&gt;Send a URL to &lt;code&gt;/load&lt;/code&gt; and get structured content back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:14786/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"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;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Domain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Title: Example Domain&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;URL Source: https://example.com&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;This domain is for use in documentation examples without needing permission. Avoid use in operations.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;[Learn more](https://iana.org/domains/example)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&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;"processing_time_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"cached"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;The &lt;code&gt;content&lt;/code&gt; field is ready to chunk and embed. The &lt;code&gt;metadata&lt;/code&gt; tells you how long it took and whether the response came from cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick your output format
&lt;/h2&gt;

&lt;p&gt;The same endpoint returns different formats based on the &lt;code&gt;x-respond-with&lt;/code&gt; header. Accepted values are &lt;code&gt;markdown&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;screenshot&lt;/code&gt;, and &lt;code&gt;pageshot&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Plain text, no markup&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:14786/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-respond-with: text"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Markdown is the default and is usually what you want for LLM input. Plain text is handy for keyword indexing, and HTML keeps structure when you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;For visual snapshots, ask for a &lt;code&gt;screenshot&lt;/code&gt; (viewport) or &lt;code&gt;pageshot&lt;/code&gt; (full page):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:14786/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-respond-with: screenshot"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine renders the page, saves a PNG to &lt;code&gt;SCREENSHOT_DIR&lt;/code&gt;, and returns a URL you can fetch from the same server:&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;"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;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"screenshot_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;"/screenshots/httpsexamplecom_6eb6a747-ba80-47bf-91c5-2767aae1c5d0.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&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;"processing_time_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1064&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cached"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;h2&gt;
  
  
  Precision extraction
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x-wait-for-selector&lt;/code&gt; waits for a CSS selector before extracting, so dynamic content has time to load.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-target-selector&lt;/code&gt; extracts only a specific region of the page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-remove-selector&lt;/code&gt; strips elements you do not want, such as ads or footers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-with-links-summary&lt;/code&gt; and &lt;code&gt;x-with-images-summary&lt;/code&gt; add a summary of links or images found on the page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-set-cookie&lt;/code&gt; and &lt;code&gt;x-user-agent&lt;/code&gt; let you control the request, with &lt;code&gt;rotate&lt;/code&gt; available for automatic user agent rotation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-no-cache: true&lt;/code&gt; forces a fresh fetch, bypassing the cache. Useful when you change selectors, since cached responses are returned as-is.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:14786/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-no-cache: true"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-wait-for-selector: h1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-target-selector: div"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-remove-selector: a"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Process URLs in batch
&lt;/h2&gt;

&lt;p&gt;For crawling or bulk indexing, send many URLs at once to &lt;code&gt;/load/batch&lt;/code&gt; and let the browser pool handle them concurrently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:14786/load/batch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"urls": ["https://example.com/1", "https://example.com/2"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"results"&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;"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;"https://example.com/1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&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;"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;"https://example.com/1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Page Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&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;"processing_time_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cached"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_processing_time_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URLs are fetched in parallel rather than one after another, so a batch is much faster than looping. Four pages that each take about two seconds came back in roughly 4.4 seconds together, against about 18 seconds when fetched one at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  User agent rotation
&lt;/h2&gt;

&lt;p&gt;Some sites get cranky when every request shows up with the same user agent. The engine can rotate through a pool of them for you. You set the strategy and the pool with environment variables, where &lt;code&gt;USER_AGENT_ROTATION&lt;/code&gt; is &lt;code&gt;off&lt;/code&gt;, &lt;code&gt;round_robin&lt;/code&gt;, or &lt;code&gt;random&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USER_AGENT_ROTATION=round_robin&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USER_AGENT_POOL=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36|Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_USER_AGENT=Mozilla/5.0 (compatible; web-loader)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;round_robin&lt;/code&gt; each request moves to the next entry in the pool. You can also decide per request with the &lt;code&gt;x-user-agent&lt;/code&gt; header: pass a string to use it as-is, &lt;code&gt;rotate&lt;/code&gt; to force rotation even when the strategy is &lt;code&gt;off&lt;/code&gt;, or &lt;code&gt;default&lt;/code&gt; to skip rotation for that one call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:14786/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-user-agent: rotate"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Browser pool resilience
&lt;/h2&gt;

&lt;p&gt;Rendering runs through a pool of headless Chromium pages, sized by &lt;code&gt;BROWSER_POOL_SIZE&lt;/code&gt; (default 10). That pool is what lets batches run in parallel, and it is also the part most likely to break, since browsers crash. The engine checks each browser and recreates any that die or drop their connection, and a request retries a few times instead of failing on the first hiccup. You can kill every browser in the container and the next request still comes back fine, with the pool rebuilt behind it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/health&lt;/code&gt; endpoint shows the current state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:14786/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.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;"browser_pool"&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;"available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"healthy"&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;"recreation_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;&lt;code&gt;recreation_count&lt;/code&gt; goes up each time a dead browser is replaced, so it is worth keeping an eye on for a long-running deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in features
&lt;/h2&gt;

&lt;p&gt;Beyond extraction, the engine ships with the parts you would otherwise build yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt; with a configurable TTL, so repeated URLs come back from cache instead of being fetched again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-domain rate limiting and circuit breakers&lt;/strong&gt;, so a slow or failing host does not take down your pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protection&lt;/strong&gt; that blocks internal IP ranges, important when you accept URLs from users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy support&lt;/strong&gt; that honors &lt;code&gt;HTTP_PROXY&lt;/code&gt;, &lt;code&gt;HTTPS_PROXY&lt;/code&gt;, and &lt;code&gt;NO_PROXY&lt;/code&gt;, including Chromium egress routing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Everything is driven by environment variables. The defaults are sensible, and you only override what you need:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;API_PORT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14786&lt;/td&gt;
&lt;td&gt;Server port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;Optional bearer-token auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BROWSER_POOL_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Concurrent browser instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REQUEST_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;Timeout in seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CACHE_TTL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3600&lt;/td&gt;
&lt;td&gt;Cache lifetime in seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SCREENSHOT_DIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;/app/screenshots&lt;/td&gt;
&lt;td&gt;Screenshot storage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A typical Compose setup:&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;web-loader&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;edgaras0x4e/web-loader-engine:latest&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;14786:14786"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BROWSER_POOL_SIZE=10&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CACHE_TTL=3600&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;screenshots:/app/screenshots&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&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;screenshots&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;The point is that one service takes you from a URL to clean content. Rendering, output formats, caching, rate limiting, security, batch fetching, user agent rotation, and a browser pool that recovers on its own are all part of it, so you are not stitching together a browser driver, a parser, and a cache by hand. It runs as a single container that you point at a URL to get back something you can actually use.&lt;/p&gt;

&lt;p&gt;Try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 14786:14786 edgaras0x4e/web-loader-engine:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Edgaras0x4E/web-loader-engine" rel="noopener noreferrer"&gt;https://github.com/Edgaras0x4E/web-loader-engine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Hub: &lt;a href="https://hub.docker.com/r/edgaras0x4e/web-loader-engine" rel="noopener noreferrer"&gt;https://hub.docker.com/r/edgaras0x4e/web-loader-engine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>scraping</category>
      <category>webscraping</category>
      <category>rag</category>
    </item>
    <item>
      <title>UML Deployment Diagrams</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Thu, 09 Apr 2026 12:52:02 +0000</pubDate>
      <link>https://dev.to/edgaras/uml-deployment-diagrams-15c2</link>
      <guid>https://dev.to/edgaras/uml-deployment-diagrams-15c2</guid>
      <description>&lt;p&gt;When designing complex software systems, understanding how your application components map to physical infrastructure is crucial. &lt;strong&gt;UML Deployment Diagrams&lt;/strong&gt; provide a clear visual representation of how software artifacts are distributed across hardware and execution environments, making them essential for system architects, DevOps engineers, and development teams planning production deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a UML Deployment Diagram?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Deployment Diagram&lt;/strong&gt; is a structural UML diagram that models the physical deployment of software artifacts onto hardware nodes and execution environments. It shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware topology&lt;/strong&gt; - The physical devices and servers that make up your infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Software placement&lt;/strong&gt; - Which applications, libraries, and files run on which hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network architecture&lt;/strong&gt; - How different nodes communicate with each other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution environments&lt;/strong&gt; - Runtime platforms like operating systems, containers, or virtual machines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment configurations&lt;/strong&gt; - How artifacts are configured and deployed in different environments&lt;/li&gt;
&lt;/ul&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%2F0nt5y75d0e7j1mdxjw2l.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%2F0nt5y75d0e7j1mdxjw2l.png" alt="Basic UML Web Application example" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Deployment Diagrams?
&lt;/h2&gt;

&lt;p&gt;Deployment diagrams are particularly valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure planning&lt;/strong&gt; - Design and document server topology before implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud architecture&lt;/strong&gt; - Model AWS, Azure, or GCP deployments with proper resource allocation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps workflows&lt;/strong&gt; - Communicate deployment strategies between development and operations teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System documentation&lt;/strong&gt; - Provide clear reference for how production systems are configured&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment comparison&lt;/strong&gt; - Visualize differences between development, staging, and production setups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity planning&lt;/strong&gt; - Identify hardware requirements and potential bottlenecks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Troubleshooting&lt;/strong&gt; - Understand system architecture when diagnosing deployment issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core Components and Notation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Nodes
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Node&lt;/strong&gt; is the fundamental building block of deployment diagrams, representing a computational resource where artifacts can be deployed. Nodes are drawn as &lt;strong&gt;3D boxes (cubes)&lt;/strong&gt; to distinguish them from other UML elements.&lt;/p&gt;

&lt;p&gt;There are two primary types of nodes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Device Nodes&lt;/strong&gt; - Physical hardware resources marked with the &lt;code&gt;«device»&lt;/code&gt; stereotype:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Servers, workstations, mobile devices, embedded systems&lt;/li&gt;
&lt;li&gt;Any physical computing resource that can execute software&lt;/li&gt;
&lt;li&gt;Examples: &lt;code&gt;«server»&lt;/code&gt; Web Server, &lt;code&gt;«mobile»&lt;/code&gt; iPhone, &lt;code&gt;«pc»&lt;/code&gt; Developer Workstation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Execution Environment Nodes&lt;/strong&gt; - Software platforms that provide runtime services, marked with the &lt;code&gt;«executionEnvironment»&lt;/code&gt; stereotype:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operating systems (Linux, Windows)&lt;/li&gt;
&lt;li&gt;Runtime platforms (JVM, .NET Runtime, Node.js)&lt;/li&gt;
&lt;li&gt;Containerization platforms (Docker, Kubernetes)&lt;/li&gt;
&lt;li&gt;Application servers (Tomcat, nginx)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Execution environments are typically shown &lt;strong&gt;nested inside device nodes&lt;/strong&gt; to represent the "runs on" relationship.&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%2Faxj16j6hd218v5lvirpt.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%2Faxj16j6hd218v5lvirpt.png" alt="Different UML Node Types with Stereotypes" width="761" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifacts
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;Artifact&lt;/strong&gt; represents a physical, deployable file or component in your system. Artifacts are drawn as &lt;strong&gt;rectangles&lt;/strong&gt; labeled with the &lt;code&gt;«artifact»&lt;/code&gt; stereotype. Optionally, a small document icon can be added in the upper-right corner for visual clarity.&lt;/p&gt;

&lt;p&gt;Common artifact types include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;«executable»&lt;/code&gt; - Runnable programs (app.jar, server.exe, main.py)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;«library»&lt;/code&gt; - Shared libraries (commons-lang.jar, libssl.so, react.dll)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;«file»&lt;/code&gt; - Configuration or data files (config.yaml, application.properties)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;«script»&lt;/code&gt; - Executable scripts (deploy.sh, Dockerfile, setup.py)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;«document»&lt;/code&gt; - Documentation files (api-spec.pdf, README.md)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;«source»&lt;/code&gt; - Source code files (Main.java, app.ts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Artifacts are deployed to nodes using either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Containment&lt;/strong&gt; - Drawing the artifact inside the node box (implicit deployment)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy dependency&lt;/strong&gt; - A dashed arrow with &lt;code&gt;«deploy»&lt;/code&gt; stereotype from artifact to node&lt;/li&gt;
&lt;/ol&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%2Foy55xatro7u36q4kdood.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%2Foy55xatro7u36q4kdood.png" alt="UML Artifact Types and Deployment Relationships" width="631" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Communication Paths
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Communication Paths&lt;/strong&gt; represent network connections between nodes, shown as &lt;strong&gt;solid lines&lt;/strong&gt; connecting node boxes. They indicate that nodes can exchange information.&lt;/p&gt;

&lt;p&gt;Communication paths can be labeled with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol information&lt;/strong&gt; - Using stereotypes like &lt;code&gt;«HTTP»&lt;/code&gt;, &lt;code&gt;«HTTPS»&lt;/code&gt;, &lt;code&gt;«TCP»&lt;/code&gt;, &lt;code&gt;«gRPC»&lt;/code&gt;, &lt;code&gt;«AMQP»&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port numbers&lt;/strong&gt; - Showing which ports are used for communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection names&lt;/strong&gt; - Descriptive labels for the communication channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiplicity&lt;/strong&gt; - Numbers indicating how many connections exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: A line labeled &lt;code&gt;«HTTPS»&lt;/code&gt; port 443 connecting a Load Balancer to a Web Server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node Instances
&lt;/h3&gt;

&lt;p&gt;While nodes represent types of computational resources, &lt;strong&gt;Node Instances&lt;/strong&gt; represent specific running instances. The entire name (both instance name and type) is underlined in the format &lt;u&gt;instanceName : NodeType&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;Example: &lt;u&gt;webServer01 : Ubuntu Server&lt;/u&gt; or &lt;u&gt;prod-db-primary : PostgreSQL&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;The underline distinguishes instances (actual running systems) from node types (abstract architectural components).&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Cloud Web Application
&lt;/h2&gt;

&lt;p&gt;Let's model a typical web application deployed on AWS cloud infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Components:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront CDN for content delivery&lt;/li&gt;
&lt;li&gt;Application Load Balancer for traffic distribution&lt;/li&gt;
&lt;li&gt;ECS (Elastic Container Service) for container orchestration&lt;/li&gt;
&lt;li&gt;Two Docker containers: one for the web app, one for the API service&lt;/li&gt;
&lt;li&gt;RDS PostgreSQL for relational data&lt;/li&gt;
&lt;li&gt;S3 bucket for static assets (images, documents)&lt;/li&gt;
&lt;/ul&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%2Fccl1dl33a9iit0yptfjd.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%2Fccl1dl33a9iit0yptfjd.png" alt="UML Cloud Web Application Architecture" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key deployment relationships shown:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;webapp.jar&lt;/code&gt; artifact deployed to &lt;code&gt;web-container&lt;/code&gt; execution environment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api.jar&lt;/code&gt; artifact deployed to &lt;code&gt;api-container&lt;/code&gt; execution environment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema.sql&lt;/code&gt; artifact deployed to PostgreSQL database&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;static-assets/&lt;/code&gt; deployed to S3 bucket&lt;/li&gt;
&lt;li&gt;Communication paths showing HTTPS, HTTP, gRPC, and TCP connections with appropriate port numbers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Enterprise On-Premises Deployment
&lt;/h2&gt;

&lt;p&gt;Traditional on-premises deployments can also be effectively modeled with deployment diagrams, showing physical hardware and network topology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Enterprise web application in a corporate data center&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Components:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardware load balancer appliance for traffic distribution&lt;/li&gt;
&lt;li&gt;Three web server machines running Apache&lt;/li&gt;
&lt;li&gt;Two application server machines running JBoss&lt;/li&gt;
&lt;li&gt;Primary and replica database servers&lt;/li&gt;
&lt;li&gt;File server for shared storage&lt;/li&gt;
&lt;li&gt;Network segmentation with DMZ and internal network&lt;/li&gt;
&lt;/ul&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%2Ftfgkwhgid4ytbtbk9wz7.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%2Ftfgkwhgid4ytbtbk9wz7.png" alt="UML Enterprise On-Premises Deployment" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node instances shown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;loadBalancer01 : F5 Load Balancer&lt;/code&gt; - Hardware load balancer in DMZ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webServer01, webServer02, webServer03 : Linux Server&lt;/code&gt; - Web tier servers in DMZ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appServer01, appServer02 : Linux Server&lt;/code&gt; - Application tier servers in internal network&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dbPrimary : Oracle Server&lt;/code&gt; - Primary database server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dbReplica : Oracle Server&lt;/code&gt; - Read replica for reporting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fileServer01 : Windows Server&lt;/code&gt; - Shared file storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This diagram clearly shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Physical server instances with specific names&lt;/li&gt;
&lt;li&gt;Execution environments (Linux, JBoss, Apache, Oracle) nested within hardware&lt;/li&gt;
&lt;li&gt;Network boundaries separating DMZ from internal network&lt;/li&gt;
&lt;li&gt;Multiple instances for high availability&lt;/li&gt;
&lt;li&gt;Communication paths with protocols and ports&lt;/li&gt;
&lt;li&gt;Artifacts deployed to each tier&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;p&gt;When creating deployment diagrams, follow these guidelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with the big picture&lt;/strong&gt; - Begin with major nodes and communication paths, then add detail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use appropriate stereotypes&lt;/strong&gt; - Leverage standard UML stereotypes (&lt;code&gt;«device»&lt;/code&gt;, &lt;code&gt;«executionEnvironment»&lt;/code&gt;) and create custom ones for cloud services when needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show execution environment hierarchies&lt;/strong&gt; - Nest execution environments inside devices to show the full stack (e.g., Docker inside Linux inside AWS EC2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Label communication paths clearly&lt;/strong&gt; - Always indicate protocols and ports for network connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep diagrams focused&lt;/strong&gt; - Create separate diagrams for different environments (dev, staging, production) or architectural layers rather than cramming everything into one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document artifact versions&lt;/strong&gt; - When relevant, include version numbers with artifacts (api-v2.3.jar)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indicate multiplicity&lt;/strong&gt; - Show how many instances of nodes exist (e.g., 3 web servers behind a load balancer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use consistent naming&lt;/strong&gt; - Follow naming conventions that match your actual infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show dependencies clearly&lt;/strong&gt; - Use deployment specifications to document configuration requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update regularly&lt;/strong&gt; - Keep deployment diagrams synchronized with actual infrastructure changes&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;UML Deployment Diagrams are indispensable for documenting and communicating how software systems are physically deployed across infrastructure. Whether you're planning a cloud migration, documenting microservices architecture, or troubleshooting production issues, deployment diagrams provide the clarity needed to understand the relationship between your software artifacts and the hardware they run on.&lt;/p&gt;

</description>
      <category>uml</category>
      <category>architecture</category>
      <category>systems</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Using a Self-Hosted PDF OCR API with PaddleOCR</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Sun, 22 Mar 2026 15:26:59 +0000</pubDate>
      <link>https://dev.to/edgaras/using-a-self-hosted-pdf-ocr-api-with-paddleocr-3k52</link>
      <guid>https://dev.to/edgaras/using-a-self-hosted-pdf-ocr-api-with-paddleocr-3k52</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;If you need to extract text from PDFs - especially large ones with 100+ pages - and don't want to pay for cloud OCR services or use LLM APIs on it, PaddleOCR can handle it locally on your own GPU.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Edgaras0x4E/paddleocr-pdf-api" rel="noopener noreferrer"&gt;paddleocr-pdf-api&lt;/a&gt; is an open-source Docker image that wraps PaddleOCR's vision-language model into a REST API. It runs on your GPU and lets you fetch results page-by-page as they're processed, without waiting for the entire document to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this is useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Processing large volumes of PDFs&lt;/strong&gt; - submit documents via API and process them one by one through a job queue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive documents that can't leave your network&lt;/strong&gt; - everything runs locally, no external API calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large documents (100+ pages)&lt;/strong&gt; - results stream page-by-page, so you can start consuming output before the full document is done&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrating OCR into a pipeline&lt;/strong&gt; - simple REST API that any language/tool can call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less common languages&lt;/strong&gt; - handles languages that many OCR tools struggle with&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's under the hood
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt;: PaddleOCR-VL-1.5 (0.9B parameters) &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU VRAM&lt;/strong&gt;: ~8.5 GB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: Markdown (headings, tables, paragraphs) via JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: SQLite + filesystem, persisted via Docker volume&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt;: FastAPI, PaddlePaddle GPU, pypdfium2 - single Python file&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Requirements: Docker with &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html" rel="noopener noreferrer"&gt;NVIDIA Container Toolkit&lt;/a&gt; and a GPU with ~8.5 GB VRAM.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;paddleocr&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;edgaras0x4e/paddleocr-pdf-api:latest&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;8099: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;ocr-data:/data&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;reservations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;devices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nvidia&lt;/span&gt;
              &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
              &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;gpu&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&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;ocr-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Submit a PDF
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8099/ocr &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@document.pdf"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"994e7b398bb44d8ab5eade4d2ef57a15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"queued"&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;h3&gt;
  
  
  Poll progress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8099/ocr/994e7b398bb44d8ab5eade4d2ef57a15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"994e7b398bb44d8ab5eade4d2ef57a15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;185&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processed_pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&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;h3&gt;
  
  
  Fetch a single page (as soon as it's ready)
&lt;/h3&gt;

&lt;p&gt;No need to wait for the entire document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8099/ocr/994e7b398bb44d8ab5eade4d2ef57a15/pages/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"page_num"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"markdown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"## Chapter 1&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Lorem ipsum dolor sit amet..."&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;h3&gt;
  
  
  Fetch all pages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8099/ocr/994e7b398bb44d8ab5eade4d2ef57a15/result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns the full document with a &lt;code&gt;pages&lt;/code&gt; array containing each page's markdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  List jobs, cancel, delete
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all jobs&lt;/span&gt;
curl http://localhost:8099/jobs

&lt;span class="c"&gt;# Cancel a running job&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8099/ocr/&lt;span class="o"&gt;{&lt;/span&gt;job_id&lt;span class="o"&gt;}&lt;/span&gt;/cancel

&lt;span class="c"&gt;# Delete a job and its data&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://localhost:8099/ocr/&lt;span class="o"&gt;{&lt;/span&gt;job_id&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full API reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ocr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upload a PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ocr/{job_id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Job status and progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ocr/{job_id}/pages/{page_num}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single page result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ocr/{job_id}/result&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ocr/{job_id}/cancel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cancel a job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ocr/{job_id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete job and its data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  API key authentication
&lt;/h2&gt;

&lt;p&gt;To restrict access, set the &lt;code&gt;API_KEY&lt;/code&gt; environment variable:&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;API_KEY=your-secret-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All requests then require the header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-secret-key"&lt;/span&gt; http://localhost:8099/jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Optional authentication key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OCR_DPI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DPI for PDF page rendering (higher = better quality, slower)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DB_PATH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/data/ocr.db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQLite database path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UPLOAD_DIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/data/uploads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upload storage path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Example: integrating into a Python script
&lt;/h2&gt;

&lt;p&gt;A minimal example that submits a PDF and waits for results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8099&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Submit
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/ocr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scan.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Poll until done
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/ocr/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;processed_pages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_pages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; pages done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Get results
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/ocr/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Page &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_num&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;PDF is uploaded and saved to disk&lt;/li&gt;
&lt;li&gt;A background worker picks up queued jobs sequentially&lt;/li&gt;
&lt;li&gt;Each page is rendered to an image using &lt;code&gt;pypdfium2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PaddleOCR-VL extracts text and converts it to markdown&lt;/li&gt;
&lt;li&gt;HTML artifacts and image placeholders are cleaned from the output&lt;/li&gt;
&lt;li&gt;Results are stored in SQLite and available per-page as they complete&lt;/li&gt;
&lt;li&gt;Jobs interrupted by a container restart are automatically re-queued&lt;/li&gt;
&lt;/ol&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/Edgaras0x4E/paddleocr-pdf-api" rel="noopener noreferrer"&gt;github.com/Edgaras0x4E/paddleocr-pdf-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker image&lt;/strong&gt;: &lt;a href="https://hub.docker.com/r/edgaras0x4e/paddleocr-pdf-api" rel="noopener noreferrer"&gt;hub.docker.com/r/edgaras0x4e/paddleocr-pdf-api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ocr</category>
      <category>api</category>
      <category>python</category>
      <category>fastapi</category>
    </item>
    <item>
      <title>A Developer's Guide to Useful Apache Modules</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Tue, 04 Nov 2025 04:45:16 +0000</pubDate>
      <link>https://dev.to/edgaras/a-developers-guide-to-useful-apache-modules-2kb7</link>
      <guid>https://dev.to/edgaras/a-developers-guide-to-useful-apache-modules-2kb7</guid>
      <description>&lt;p&gt;Apache is one of the most widely used web servers, and its real power comes from its modular design.&lt;br&gt;&lt;br&gt;
Below is a practical, example-based guide to Apache modules that are useful in real-world web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Enable/Disable Modules
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable a module&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod module_name

&lt;span class="c"&gt;# Disable a module&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2dismod module_name

&lt;span class="c"&gt;# Restart Apache&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart apache2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;mod_rewrite&lt;/code&gt; - URL Routing &amp;amp; Clean URLs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName example.com

    RewriteEngine On
    RewriteRule ^user/([0-9]+)/?$ /profile.php?id=$1 [L,QSA]
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Used for clean URLs, routing logic, SEO-friendly paths, and framework rewrites.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_ssl&lt;/code&gt; - HTTPS Support
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:443&amp;gt;
    ServerName example.com

    SSLEngine On
    SSLCertificateFile /etc/ssl/certs/example.crt
    SSLCertificateKeyFile /etc/ssl/private/example.key
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enables TLS/SSL so the site can run securely over HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_headers&lt;/code&gt; - Security &amp;amp; Cache Headers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod_headers.c&amp;gt;
    Header always set X-Frame-Options "DENY"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "no-referrer"
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allows setting and modifying HTTP response headers for security and performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_expires&lt;/code&gt; - Browser Cache Control
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod_expires.c&amp;gt;
    ExpiresActive On
    ExpiresByType text/css "access plus 7 days"
    ExpiresByType image/png "access plus 1 year"
    ExpiresDefault "access plus 1 hour"
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adds automatic expiration headers so browsers can cache static files.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_proxy&lt;/code&gt; and &lt;code&gt;mod_proxy_http&lt;/code&gt; - Reverse Proxy to Apps
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName api.example.com

    ProxyPass / http://127.0.0.1:4000/
    ProxyPassReverse / http://127.0.0.1:4000/
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forwards requests to backend services like Node, Python, Docker, or another server.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_security&lt;/code&gt; - Web Application Firewall
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SecRuleEngine On
Include /usr/share/modsecurity-crs/*.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blocks common attacks such as SQL injection, XSS, and RCE using rule sets.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_status&lt;/code&gt; - Live Server Status Page
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Location "/server-status"&amp;gt;
    SetHandler server-status
    Require ip 127.0.0.1
&amp;lt;/Location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Displays live Apache metrics: active connections, workers, load, uptime.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_auth_basic&lt;/code&gt; and &lt;code&gt;mod_authn_file&lt;/code&gt; - Simple Password Protection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Location "/admin"&amp;gt;
    AuthType Basic
    AuthName "Restricted"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
&amp;lt;/Location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adds quick HTTP basic authentication without touching application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mod_evasive&lt;/code&gt; - Auto-Blocking for DDoS / Brute Force
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod_evasive20.c&amp;gt;
    DOSHashTableSize 2048
    DOSPageCount 5
    DOSPageInterval 1
    DOSBlockingPeriod 30
    DOSEmailNotify admin@example.com
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detects repeated requests and temporarily blocks abusive traffic automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Useful Modules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mod_deflate&lt;/code&gt; or &lt;code&gt;mod_brotli&lt;/code&gt; - Response Compression
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod_deflate.c&amp;gt;
    AddOutputFilterByType DEFLATE text/html text/css application/json
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod_brotli.c&amp;gt;
    BrotliCompressionQuality 6
    AddOutputFilterByType BROTLI_COMPRESS text/html text/css application/javascript
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compresses responses to reduce bandwidth and speed up page loads.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mod_pagespeed&lt;/code&gt; - Auto Performance Optimizer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModPagespeed on
ModPagespeedEnableFilters rewrite_css,sprite_images,collapse_whitespace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically optimizes assets: minifies, inlines, rewrites, lazyloads, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mod_geoip&lt;/code&gt; / &lt;code&gt;mod_maxminddb&lt;/code&gt; - Geo-Based Rules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod_maxminddb.c&amp;gt;
    MaxMindDBFile COUNTRY_DB /usr/share/GeoIP/GeoLite2-Country.mmdb
    MaxMindDBEnv GEOIP_COUNTRY_CODE COUNTRY_DB/country/iso_code
&amp;lt;/IfModule&amp;gt;

&amp;lt;Location /&amp;gt;
    Require all granted
    Require not env GEOIP_COUNTRY_CODE=CN
&amp;lt;/Location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allows country-based routing, blocking, redirects, or localization.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Purpose / Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_rewrite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL routing, clean URLs, SEO-friendly rewrites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_ssl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables HTTPS/TLS encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_headers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets custom security and cache headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_expires&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls browser caching for static files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_proxy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reverse proxy to backend apps/services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_security&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Web Application Firewall (WAF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Live server metrics/status page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_auth_basic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simple HTTP basic authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_evasive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-blocking for DDoS / brute force requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mod_deflate&lt;/code&gt; / &lt;code&gt;brotli&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Response compression for faster load times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mod_pagespeed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatic asset optimization (minify, inline etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mod_geoip&lt;/code&gt; / &lt;code&gt;maxminddb&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Geo-based routing, blocking, or localization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>apache</category>
      <category>webdev</category>
      <category>performance</category>
      <category>security</category>
    </item>
    <item>
      <title>Ethereum Integration in PHP for Wallets and Transfers</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Mon, 06 Oct 2025 18:38:04 +0000</pubDate>
      <link>https://dev.to/edgaras/ethereum-integration-in-php-for-wallets-and-transfers-11d2</link>
      <guid>https://dev.to/edgaras/ethereum-integration-in-php-for-wallets-and-transfers-11d2</guid>
      <description>&lt;p&gt;If you build backends in PHP and want to talk to Ethereum without pulling in a full Node stack, this library gives you a clean methods for the main functionality: wallet management, ETH transfers (legacy and EIP‑1559), balances, transaction queries, and utilities.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Edgaras0x4E/Ethereum" rel="noopener noreferrer"&gt;&lt;code&gt;Edgaras0x4E/Ethereum&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What you can do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wallets&lt;/strong&gt;: Create/import wallets, derive address/public key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transfers&lt;/strong&gt;: Send ETH using legacy (Type‑0) and EIP‑1559 (Type‑2) transactions; wait for confirmations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Balances &amp;amp; network&lt;/strong&gt;: &lt;code&gt;getBalance&lt;/code&gt;, &lt;code&gt;getBalanceInEther&lt;/code&gt;, &lt;code&gt;getBlockNumber&lt;/code&gt;, &lt;code&gt;getNetworkInfo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactions&lt;/strong&gt;: &lt;code&gt;getTransaction&lt;/code&gt;, &lt;code&gt;getTransactionReceipt&lt;/code&gt;, &lt;code&gt;getTransactionStatus&lt;/code&gt;, &lt;code&gt;getTransactionDetails&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Address activity&lt;/strong&gt;: Scan recent blocks with &lt;code&gt;getTransactionsForAddress&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocks&lt;/strong&gt;: Fetch transactions with &lt;code&gt;getTransactionsByBlockNumber&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilities&lt;/strong&gt;: Unit conversion, checksum addresses, hex helpers, hashing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.3+&lt;/li&gt;
&lt;li&gt;Extensions: &lt;code&gt;gmp&lt;/code&gt;, &lt;code&gt;bcmath&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;An Ethereum JSON‑RPC endpoint (Infura)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require edgaras/ethereum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick start: create a wallet and read balance
&lt;/h3&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;Edgaras\Ethereum\Ethereum&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;Edgaras\Ethereum\Wallet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$rpcUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://sepolia.infura.io/v3/YOUR_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$chainId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11155111&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Sepolia&lt;/span&gt;

&lt;span class="nv"&gt;$eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ethereum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chainId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new wallet (or import with new Wallet('0xPRIVATE_KEY_HEX'))&lt;/span&gt;
&lt;span class="nv"&gt;$wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createWallet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setWallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$wallet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Address: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$wallet&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Balance: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBalanceInEther&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ETH&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send ETH (legacy Type‑0)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\Ethereum\Ethereum&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;Edgaras\Ethereum\Wallet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ethereum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chainId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setWallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0xYOUR_PRIVATE_KEY_HEX'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nv"&gt;$txHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0xRecipientAddress...'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0.01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"tx: &lt;/span&gt;&lt;span class="nv"&gt;$txHash&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Optional: wait for confirmation&lt;/span&gt;
&lt;span class="nv"&gt;$receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;waitForConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$txHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delaySeconds&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"included in block: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$receipt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'blockNumber'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send ETH with EIP‑1559 (Type‑2)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\Ethereum\Ethereum&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;Edgaras\Ethereum\Wallet&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;Edgaras\Ethereum\Utils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ethereum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chainId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setWallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0xYOUR_PRIVATE_KEY_HEX'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Set EIP‑1559 gas parameters (in gwei)&lt;/span&gt;
&lt;span class="nv"&gt;$maxPriorityFeePerGasGwei&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 2 gwei tip&lt;/span&gt;
&lt;span class="nv"&gt;$maxFeePerGasGwei&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'20'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// 20 gwei max fee&lt;/span&gt;

&lt;span class="c1"&gt;// Convert to wei hex&lt;/span&gt;
&lt;span class="nv"&gt;$maxPriorityFeePerGas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;gweiToWei&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$maxPriorityFeePerGasGwei&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nv"&gt;$maxFeePerGas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;gweiToWei&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$maxFeePerGasGwei&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nv"&gt;$txHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendEtherEIP1559&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'0xRecipientAddress...'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'0.01'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$maxFeePerGas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$maxPriorityFeePerGas&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"EIP‑1559 tx: &lt;/span&gt;&lt;span class="nv"&gt;$txHash&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Practical examples
&lt;/h3&gt;

&lt;p&gt;Minimal wallet overview (balance + recent txs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\Ethereum\Ethereum&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;Edgaras\Ethereum\Utils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ethereum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chainId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0xRecipientAddress...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$balanceEth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBalanceInEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Address: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toChecksumAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Balance: &lt;/span&gt;&lt;span class="nv"&gt;$balanceEth&lt;/span&gt;&lt;span class="s2"&gt; ETH&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$txs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTransactionsForAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxBlocksToScan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxTxToShow&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$txs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[%s] %s | %s ETH | peer: %s | block: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'direction'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'hash'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'valueEth'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'peer'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'blockNumber'&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;List transactions in a block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\Ethereum\Ethereum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ethereum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chainId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$blockNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9354804&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// example&lt;/span&gt;
&lt;span class="nv"&gt;$txs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTransactionsByBlockNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$blockNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$txs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="nv"&gt;$idx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  hash:  "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'hash'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  from:  "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'from'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  to:    "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'to'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  value: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'0x0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;" (wei hex)&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&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;h3&gt;
  
  
  Utilities you’ll actually use
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\Ethereum\Utils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Unit conversion&lt;/span&gt;
&lt;span class="nv"&gt;$wei&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;etherToWei&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1.5'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;weiToEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0x204fce5e3e250261100000000'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// hex or decimals&lt;/span&gt;
&lt;span class="nv"&gt;$gwei&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;weiToGwei&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0x4a817c800'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Address helpers&lt;/span&gt;
&lt;span class="nv"&gt;$isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;isValidAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0x242d35Cc6634C0532925a3b8D4C9db96C4b4d8b6'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$checksum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toChecksumAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0x242d35cc6634c0532925a3b8d4c9db96c4b4d8b6'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Hex helpers&lt;/span&gt;
&lt;span class="nv"&gt;$hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'255'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0xff&lt;/span&gt;
&lt;span class="nv"&gt;$dec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0xff'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 255&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  EIP‑1559 in practice (why it matters)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Better fee predictability&lt;/strong&gt; and reduced overpayment when the network is quiet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic fee market&lt;/strong&gt;: you set a max fee and a priority tip; the base fee adjusts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed transactions&lt;/strong&gt;: EIP‑2718 with proper hashing (&lt;code&gt;0x02 || RLP(list)&lt;/code&gt;) for sender recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick EIP‑1559 for modern fee markets; fall back to legacy for maximum compatibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keep your private keys out of source control; inject via env/config vaults&lt;/li&gt;
&lt;li&gt;Monitor base fee and adjust &lt;code&gt;maxFeePerGas&lt;/code&gt;/&lt;code&gt;maxPriorityFeePerGas&lt;/code&gt; dynamically&lt;/li&gt;
&lt;li&gt;Wrap RPC errors and rate limits; the library exposes clear exceptions&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;waitForConfirmation&lt;/code&gt; with sensible timeouts and backoff in worker jobs&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>ethereum</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Laravel Project Backup to Azure Blob with Bash Script</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Mon, 25 Aug 2025 22:13:48 +0000</pubDate>
      <link>https://dev.to/edgaras/laravel-project-backup-to-azure-blob-with-bash-script-3ke</link>
      <guid>https://dev.to/edgaras/laravel-project-backup-to-azure-blob-with-bash-script-3ke</guid>
      <description>&lt;p&gt;If you’re running a Laravel app and want an easy way to back it up to Azure Blob Storage without Docker images or third-party SaaS tools, here’s a self-contained Bash script that does exactly that.&lt;/p&gt;

&lt;p&gt;It backs up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your Laravel project files (with exclusions like &lt;code&gt;vendor/&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Your MySQL database (via &lt;code&gt;mysqldump&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Packages everything into a &lt;code&gt;.tar.gz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Uploads to Azure Blob Storage using &lt;code&gt;az CLI&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cleans up local backups older than X days&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;, &lt;code&gt;gzip&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mysqldump&lt;/code&gt; (MySQL/MariaDB client) &lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?view=azure-cli-latest&amp;amp;pivots=apt" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-Eeuo&lt;/span&gt; pipefail


&lt;span class="c"&gt;# ---- Paths ----&lt;/span&gt;
&lt;span class="nv"&gt;LARAVEL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/laravel/project"&lt;/span&gt; &lt;span class="c"&gt;# Path to your Laravel project root&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/local/backup/directory"&lt;/span&gt; &lt;span class="c"&gt;# Local backup directory  &lt;/span&gt;

&lt;span class="c"&gt;# ---- Azure Storage (account + key) ----&lt;/span&gt;
&lt;span class="nv"&gt;AZURE_STORAGE_ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;ACCOUNT_NAME&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;AZURE_STORAGE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;ACCOUNT-KEY&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;AZURE_CONTAINER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;CONTAINER-NAME&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# ---- Retention (local only) ----&lt;/span&gt;
&lt;span class="nv"&gt;RETAIN_LOCAL_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7

&lt;span class="c"&gt;# ---- Backup content exclusion ----&lt;/span&gt;
&lt;span class="nv"&gt;EXCLUDES&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  &lt;span class="s2"&gt;"vendor"&lt;/span&gt;
  &lt;span class="s2"&gt;"node_modules"&lt;/span&gt;
  &lt;span class="s2"&gt;"storage/framework/cache"&lt;/span&gt;
  &lt;span class="s2"&gt;"storage/logs"&lt;/span&gt;
  &lt;span class="s2"&gt;".git"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;#############################################&lt;/span&gt;

timestamp_utc&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y%m%dT%H%M%SZ"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

require_cmd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: Missing required command: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

read_env_var&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&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;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LARAVEL_DIR&lt;/span&gt;&lt;span class="s2"&gt;/.env"&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="nv"&gt;val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LARAVEL_DIR&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"s/^&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=(.*)&lt;/span&gt;&lt;span class="nv"&gt;$/&lt;/span&gt;&lt;span class="se"&gt;\1&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\r'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
  if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;:0:1&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;:&lt;span class="p"&gt; -1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&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="nv"&gt;val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;:1:-1&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;:0:1&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"'"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;:&lt;span class="p"&gt; -1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&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="nv"&gt;val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;:1:-1&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

dump_mysql&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;db_host db_port db_name db_user db_pass db_socket dump_path
  &lt;span class="nv"&gt;db_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_env_var &lt;span class="s2"&gt;"DB_HOST"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_env_var &lt;span class="s2"&gt;"DB_PORT"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_env_var &lt;span class="s2"&gt;"DB_DATABASE"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_env_var &lt;span class="s2"&gt;"DB_USERNAME"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_env_var &lt;span class="s2"&gt;"DB_PASSWORD"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_socket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_env_var &lt;span class="s2"&gt;"DB_SOCKET"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;dump_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  require_cmd mysqldump

  &lt;span class="nv"&gt;db_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;db_host&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;127&lt;/span&gt;&lt;span class="p"&gt;.0.0.1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;db_port&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;3306&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--single-transaction&lt;/span&gt; &lt;span class="nt"&gt;--quick&lt;/span&gt; &lt;span class="nt"&gt;--routines&lt;/span&gt; &lt;span class="nt"&gt;--events&lt;/span&gt; &lt;span class="nt"&gt;--triggers&lt;/span&gt; &lt;span class="o"&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;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_socket&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;args+&lt;span class="o"&gt;=(&lt;/span&gt; &lt;span class="nt"&gt;--socket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_socket&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;args+&lt;span class="o"&gt;=(&lt;/span&gt; &lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_port&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nb"&gt;umask &lt;/span&gt;077
  &lt;span class="nv"&gt;MYSQL_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_pass&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; mysqldump &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dump_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

make_archive&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;src_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;db_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;out_tar_gz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;local &lt;/span&gt;stage
  &lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'rm -rf "$stage"'&lt;/span&gt; RETURN

  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;/site"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;/db"&lt;/span&gt;

  &lt;span class="nb"&gt;pushd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;tar_excludes&lt;/span&gt;&lt;span class="o"&gt;=()&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;e &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXCLUDES&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;tar_excludes+&lt;span class="o"&gt;=(&lt;/span&gt; &lt;span class="s2"&gt;"--exclude=&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-cf&lt;/span&gt; - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;tar_excludes&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;/site"&lt;/span&gt; &lt;span class="nt"&gt;-xf&lt;/span&gt; -
  &lt;span class="nb"&gt;popd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null

  &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_sql&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;/db/database.sql"&lt;/span&gt;

  &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-czf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out_tar_gz&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; site db
&lt;span class="o"&gt;}&lt;/span&gt;

upload_with_azcli&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;AZURE_STORAGE_ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AZURE_STORAGE_ACCOUNT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;AZURE_STORAGE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AZURE_STORAGE_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    az storage blob upload &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--container-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AZURE_CONTAINER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--overwrite&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;span class="o"&gt;}&lt;/span&gt;

main&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LARAVEL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: LARAVEL_DIR not found: &lt;/span&gt;&lt;span class="nv"&gt;$LARAVEL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;local &lt;/span&gt;ts app_name base_name db_dump archive
  &lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp_utc&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;app_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LARAVEL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;base_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;app_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;db_dump&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql"&lt;/span&gt;
  &lt;span class="nv"&gt;archive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[1/4] Dumping MySQL → &lt;/span&gt;&lt;span class="nv"&gt;$db_dump&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  dump_mysql &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_dump&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[2/4] Creating archive → &lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  make_archive &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LARAVEL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db_dump&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[3/4] Uploading to Azure via az CLI"&lt;/span&gt;
  upload_with_azcli &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Upload complete: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[4/4] Pruning local backups older than &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RETAIN_LOCAL_DAYS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;d"&lt;/span&gt;
  find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;app_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-*.tar.gz"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RETAIN_LOCAL_DAYS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt; &lt;span class="nt"&gt;-delete&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
  &lt;/span&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;app_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-*.sql"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RETAIN_LOCAL_DAYS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt; &lt;span class="nt"&gt;-delete&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

  echo&lt;/span&gt; &lt;span class="s2"&gt;"Done."&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

main &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Database Dump&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads &lt;code&gt;DB_HOST&lt;/code&gt;, &lt;code&gt;DB_DATABASE&lt;/code&gt;, &lt;code&gt;DB_USERNAME&lt;/code&gt;, &lt;code&gt;DB_PASSWORD&lt;/code&gt; from &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;mysqldump&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File Packaging&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copies project files while excluding unnecessary directories&lt;/li&gt;
&lt;li&gt;Adds &lt;code&gt;database.sql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Compresses into &lt;code&gt;tar.gz&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Azure Upload&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;az storage blob upload&lt;/code&gt; with &lt;code&gt;AZURE_STORAGE_ACCOUNT&lt;/code&gt; + &lt;code&gt;AZURE_STORAGE_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Retention Cleanup&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deletes &lt;code&gt;.sql&lt;/code&gt; and &lt;code&gt;.tar.gz&lt;/code&gt; files older than &lt;code&gt;RETAIN_LOCAL_DAYS&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  Save the script:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano laravel-backup.sh
chmod +x laravel-backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure variables:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LARAVEL_DIR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BACKUP_DIR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZURE_STORAGE_ACCOUNT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZURE_STORAGE_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZURE_CONTAINER&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Run manually:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./laravel-backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Or schedule with cron:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /path/to/laravel-backup.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/laravel-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>laravel</category>
      <category>php</category>
      <category>bash</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Efficiently Measure String Similarity in PHP Applications</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Sun, 25 May 2025 15:26:36 +0000</pubDate>
      <link>https://dev.to/edgaras/efficiently-measure-string-similarity-in-php-applications-l31</link>
      <guid>https://dev.to/edgaras/efficiently-measure-string-similarity-in-php-applications-l31</guid>
      <description>&lt;p&gt;Measuring string similarity accurately is important for tasks like detecting typos in user inputs, matching similar database entries, improving chatbot responses, and identifying duplicate content. PHP developers can simplify these tasks using specialized algorithms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Include the library via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require edgaras/strsim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, use the Composer autoloader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;require __DIR__ &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing Powerful String Similarity Algorithms
&lt;/h2&gt;

&lt;p&gt;By using specialized classes, developers can easily integrate complex string similarity metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Levenshtein&lt;/strong&gt;: Ideal for detecting user input spelling mistakes in form validation.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\Levenshtein&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$userInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"recieve"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$correctWord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"receive"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Levenshtein&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$correctWord&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Did you mean '&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$correctWord&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Damerau-Levenshtein&lt;/strong&gt;: Useful for correcting common keyboard typing errors in search features.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\DamerauLevenshtein&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DamerauLevenshtein&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"adress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Searching for 'address' instead."&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hamming&lt;/strong&gt;: Critical for error checking in transmitted binary data packets.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\Hamming&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hamming&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"11001101"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10001111"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$errors&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;trigger_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Data packet corrupted."&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jaro&lt;/strong&gt;: Ideal for short string comparisons and record matching.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\Jaro&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Jaro&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"crate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Highly similar words detected."&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jaro-Winkler&lt;/strong&gt;: Excellent for deduplicating customer records by comparing similar names.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\JaroWinkler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JaroWinkler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Jonathan Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Jonathon Smyth"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Possible duplicate found."&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Longest Common Subsequence (LCS)&lt;/strong&gt;: Useful in text diff applications to detect common content.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Edgaras\StrSim\LCS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LCS&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"contentversion1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"contentversion2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Common subsequence length: &lt;/span&gt;&lt;span class="nv"&gt;$length&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smith-Waterman&lt;/strong&gt;: Good for local alignment of DNA sequences in bioinformatics research.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\SmithWaterman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$alignmentScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SmithWaterman&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ACGTAG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ACGACG"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$alignmentScore&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"High genetic similarity detected."&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Needleman-Wunsch&lt;/strong&gt;: Valuable for global alignment of DNA or protein sequences.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\NeedlemanWunsch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NeedlemanWunsch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GATTACA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"GCATGCU"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Alignment score: &lt;/span&gt;&lt;span class="nv"&gt;$score&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cosine Similarity&lt;/strong&gt;: Useful for comparing frequency patterns in short texts.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\Cosine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cosine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"night"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"nacht"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Text similarity: &lt;/span&gt;&lt;span class="nv"&gt;$similarity&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jaccard Index&lt;/strong&gt;: Effective for comparing the overlap between sets of tokens.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\Jaccard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Jaccard&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"token1 token2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"token2 token3"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Token overlap: &lt;/span&gt;&lt;span class="nv"&gt;$index&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monge-Elkan&lt;/strong&gt;: Great for fuzzy matching of multi-word strings.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Edgaras\StrSim\MongeElkan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MongeElkan&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"john smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"jon smythe"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$similarity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Likely match identified."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Applications
&lt;/h2&gt;

&lt;p&gt;Natural Language Processing: Improve chatbot accuracy by handling slight input variations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy Matching&lt;/strong&gt;: Optimize database searches and user input validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spell Checking&lt;/strong&gt;: Implement typo tolerance in text entry fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bioinformatics&lt;/strong&gt;: Conduct detailed genetic sequence analyses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By selecting the appropriate algorithm, developers can significantly enhance the reliability and responsiveness of their PHP applications dealing with textual or genetic data.&lt;/p&gt;

</description>
      <category>php</category>
      <category>nlp</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Reliably Mount a CIFS Share Using systemd on Boot</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Fri, 25 Apr 2025 07:40:21 +0000</pubDate>
      <link>https://dev.to/edgaras/how-to-reliably-mount-a-cifs-share-using-systemd-on-boot-2l8i</link>
      <guid>https://dev.to/edgaras/how-to-reliably-mount-a-cifs-share-using-systemd-on-boot-2l8i</guid>
      <description>&lt;p&gt;Mounting a CIFS share at boot via &lt;code&gt;/etc/fstab&lt;/code&gt; can often lead to issues if the network isn't ready. A more reliable solution is to create a &lt;strong&gt;systemd mount unit&lt;/strong&gt; that ensures the network is online before attempting to mount.&lt;/p&gt;

&lt;p&gt;Here's how you do it:&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a credentials file
&lt;/h2&gt;

&lt;p&gt;Create a file to store your SMB credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/smb-credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username=your_username
password=your_password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secure it so only root can read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo chmod 600 /etc/smb-credentials
sudo chown root:root /etc/smb-credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create the mount point
&lt;/h2&gt;

&lt;p&gt;Make the local directory where the CIFS share will be mounted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/nas_share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create a systemd mount unit
&lt;/h3&gt;

&lt;p&gt;Run the following command to create or edit the mount unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl edit &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--full&lt;/span&gt; mnt-nas_share.mount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the following content:&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;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Mount SMB Share&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;
&lt;span class="py"&gt;Wants&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Mount]&lt;/span&gt;
&lt;span class="py"&gt;What&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;//nas.local/share&lt;/span&gt;
&lt;span class="py"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/mnt/nas_share&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cifs&lt;/span&gt;
&lt;span class="py"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;credentials=/etc/smb-credentials,iocharset=utf8,nounix,noserverino,nofail,_netdev,soft,vers=3.0&lt;/span&gt;
&lt;span class="py"&gt;TimeoutSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;p&gt;[Unit]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Description=Mount NAS HDD Share&lt;/code&gt;: Human-readable description.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;After=network-online.target&lt;/code&gt;: Ensures the mount waits for the network to be up.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Wants=network-online.target&lt;/code&gt;: Pulls in the network-online target if not already present.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[Mount]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;What=//nas.local/share&lt;/code&gt;: UNC path to the CIFS share.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Where=/mnt/nas_share&lt;/code&gt;: Local mount point.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Type=cifs&lt;/code&gt;: Filesystem type.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Options=...&lt;/code&gt;: Mount options:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;credentials=...&lt;/code&gt;: Path to the credentials file (should be root-only readable).
iocharset=utf8: Character encoding.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nounix&lt;/code&gt;, &lt;code&gt;noserverino&lt;/code&gt;: Workarounds for specific CIFS behaviors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nofail&lt;/code&gt;: Don’t block boot if the mount fails.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_netdev&lt;/code&gt;: Marks this mount as requiring network.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;soft&lt;/code&gt;: Causes the mount to time out if the server is unresponsive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vers=3.0&lt;/code&gt;: Explicitly uses SMB version 3.0.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TimeoutSec=30&lt;/code&gt;: Maximum wait time before systemd considers the mount failed.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;[Install]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WantedBy=multi-user.target&lt;/code&gt;: Starts the mount at boot in multi-user mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Enable the mount
&lt;/h2&gt;

&lt;p&gt;After saving the unit file, reload and enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reexec
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;mnt-nas_share.mount
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start mnt-nas_share.mount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;daemon-reexec&lt;/code&gt;: Ensures systemd itself is up-to-date.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;daemon-reload&lt;/code&gt;: Reloads unit files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enable&lt;/code&gt;: Enables the mount to persist across reboots.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt;: Immediately mounts the share.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Verify the mount
&lt;/h2&gt;

&lt;p&gt;Check the status with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status mnt-nas_share.mount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the share is mounted without errors.&lt;/p&gt;

&lt;p&gt;Using this method ensures your CIFS mounts only attempt to initialize when the network is available, solving the common &lt;code&gt;Permission denied opening credential file&lt;/code&gt; problem caused by early mount attempts during boot.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>systemd</category>
      <category>networking</category>
    </item>
    <item>
      <title>Setting Up MySQL and MongoDB Together in Laravel 12</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Sun, 13 Apr 2025 21:05:00 +0000</pubDate>
      <link>https://dev.to/edgaras/setting-up-mysql-and-mongodb-together-in-laravel-12-3171</link>
      <guid>https://dev.to/edgaras/setting-up-mysql-and-mongodb-together-in-laravel-12-3171</guid>
      <description>&lt;p&gt;Sometimes you need relational data for parts of your app (MySQL) and flexible document storage for others (MongoDB). Here's how to set up both in Laravel 12.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install MongoDB PHP Extension
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ext-mongodb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Required Composer Packages
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo composer require mongodb/mongodb
sudo composer require mongodb/laravel-mongodb:^5.2 --with-all-dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;.env&lt;/code&gt; example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=&amp;lt;CHANGE-TO-YOUR-MYSQL-DATABASE&amp;gt;
DB_USERNAME=&amp;lt;CHANGE-TO-YOUR-MYSQL-USERNAME&amp;gt;
DB_PASSWORD=&amp;lt;CHANGE-TO-YOUR-MYSQL-PASSWORD&amp;gt;

MONGODB_USERNAME=&amp;lt;CHANGE-TO-YOUR-MONGODB-USERNAME&amp;gt;
MONGODB_PASSWORD=&amp;lt;CHANGE-TO-YOUR-MONGODB-PASSWORD&amp;gt;
MONGODB_SERVER=127.0.0.1
MONGODB_PORT=27017
MONGODB_DATABASE=&amp;lt;CHANGE-TO-YOUR-MONGODB-COLLECTION&amp;gt;
MONGODB_URI="mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@${MONGODB_SERVER}:${MONGODB_PORT}/${MONGODB_DATABASE}?directConnection=true&amp;amp;serverSelectionTimeoutMS=2000&amp;amp;authSource=admin&amp;amp;appName=mongosh+2.2.2?tls=false&amp;amp;authSource=admin"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;config/database.php&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_CONNECTION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="s1"&gt;'connections'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;


&lt;span class="s1"&gt;'mysql'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_URL'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'port'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_PORT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3306'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_DATABASE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'laravel'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'username'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'root'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'unix_socket'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_SOCKET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'charset'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_CHARSET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'utf8mb4'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'collation'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_COLLATION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'utf8mb4_unicode_ci'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'prefix'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'prefix_indexes'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'strict'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'engine'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;extension_loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pdo_mysql'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="no"&gt;PDO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MYSQL_ATTR_SSL_CA&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MYSQL_ATTR_SSL_CA'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="s1"&gt;'mongodb'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mongodb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'dsn'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_URI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mongodb://'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_SERVER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_PORT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_DATABASE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'?directConnection=true&amp;amp;serverSelectionTimeoutMS=2000&amp;amp;authSource=admin&amp;amp;appName=mongosh+2.2.2?tls=false&amp;amp;authSource=admin'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MONGODB_DATABASE'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Model examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MySQL Model (&lt;code&gt;User&lt;/code&gt;)
&lt;/h3&gt;



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

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&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;h3&gt;
  
  
  MongoDB Model (&lt;code&gt;Post&lt;/code&gt;)
&lt;/h3&gt;



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

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MongoDB\Laravel\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'mongodb'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



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



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

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&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\Http\Request&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;App\Models\User&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;App\Models\Post&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;ExampleController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'MongoDB Example'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'body'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'This is stored in MongoDB.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'slug'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mongodb-example'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'post'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That's it. You now have MySQL and MongoDB both fully working in a Laravel 12 app.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>mysql</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>UML Use Case Diagrams</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Sun, 06 Apr 2025 21:05:00 +0000</pubDate>
      <link>https://dev.to/edgaras/uml-use-case-diagrams-2pk4</link>
      <guid>https://dev.to/edgaras/uml-use-case-diagrams-2pk4</guid>
      <description>&lt;p&gt;UML (Unified Modeling Language) is a tool in software engineering for visualizing, specifying, constructing, and documenting software system artifacts. One of the most widely used UML diagrams is the &lt;strong&gt;Use Case Diagram&lt;/strong&gt;, which helps in modeling system functionality from the user's perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a UML Use Case Diagram?
&lt;/h2&gt;

&lt;p&gt;A Use Case Diagram is a high-level visual representation of how users (actors) interact with a system. It belongs to the behavioral category of UML diagrams, as it focuses on depicting system functionality and user interactions rather than its structure. It identifies the primary functions and the relationships between actors and use cases, helping to understand system behavior and user interactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Components of a Use Case Diagram
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Actors&lt;/strong&gt; – Represent users or external systems that interact with the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cases&lt;/strong&gt; – Depict functionalities or actions performed by the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationships&lt;/strong&gt; – Define connections between elements, including:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Associations&lt;/strong&gt; – Lines that connect actors to use cases, showing interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generalization&lt;/strong&gt; – Represents inheritance between actors or use cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include&lt;/strong&gt; – Represents mandatory dependency where one use case always invokes another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend&lt;/strong&gt; – Represents optional behavior that extends another use case.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Boundary&lt;/strong&gt; – Defines the scope of the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extension Points&lt;/strong&gt; – Specific locations within a use case where additional behavior can be added via &lt;code&gt;&amp;lt;&amp;lt;extend&amp;gt;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  UML Use Case Diagram Notation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Actor
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;actor&lt;/strong&gt; represents an external entity (user or system) that interacts with the system. It is depicted as a stick figure:&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%2Fv6ri66r2g1vc1ixkfgh5.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%2Fv6ri66r2g1vc1ixkfgh5.png" alt="UML Use Case Diagram Actor" width="31" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;use case&lt;/strong&gt; represents a specific function or process performed by the system. It is depicted as an oval:&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%2Fuj4e5jdx9qt2sdzcg988.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%2Fuj4e5jdx9qt2sdzcg988.png" alt="UML Use Case Diagram Use Case" width="651" height="111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Association
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;association&lt;/strong&gt; is a line connecting actors to use cases, indicating interaction:&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%2Ffvc4d5npmbgc52uqjgws.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%2Ffvc4d5npmbgc52uqjgws.png" alt="UML Use Case Diagram Association" width="356" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generalization
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;generalization&lt;/strong&gt; relationship represents inheritance, where a specialized use case or actor inherits behavior from a more general one. It is depicted using a solid line with an unfilled arrowhead:&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%2Ff3nllljxc2b2ci9agaat.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%2Ff3nllljxc2b2ci9agaat.png" alt="UML Use Case Diagram Generalization" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Include Relationship
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;include&lt;/strong&gt; relationship signifies that one use case always invokes another. It represents mandatory behavior where the base use case is incomplete without the included functionality. All included use cases must be shown in the diagram as they represent essential components of the system functionality. It is represented by a dashed arrow labeled &lt;code&gt;&amp;lt;&amp;lt;include&amp;gt;&amp;gt;&lt;/code&gt; pointing from the base use case to the included use case.&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%2Fsgcr9060th8qb11lx7pf.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%2Fsgcr9060th8qb11lx7pf.png" alt="UML Use Case Diagram Include Relationship" width="539" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extend Relationship
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;extend&lt;/strong&gt; relationship shows optional behavior that may be added to a base use case under specific conditions. The base use case is complete on its own, and the extending use case only executes when certain conditions are met at specified extension points. Not all possible extensions need to be shown in the diagram since they represent optional enhancements rather than mandatory behavior. It is represented by a dashed arrow labeled &lt;code&gt;&amp;lt;&amp;lt;extend&amp;gt;&amp;gt;&lt;/code&gt; pointing from the extending use case to the base use case.&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%2F2zfnoa9cs99f2vi5seir.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%2F2zfnoa9cs99f2vi5seir.png" alt="UML Use Case Diagram Extend Relationship" width="484" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  System Boundary
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;system boundary&lt;/strong&gt; is a rectangle enclosing all use cases, defining the scope of the system.&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%2Fwhwele68ga5zd5rdtl8p.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%2Fwhwele68ga5zd5rdtl8p.png" alt="UML Use Case Diagram System Boundary" width="552" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extension Points
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Extension Points&lt;/strong&gt; define specific locations within a use case where an &lt;code&gt;&amp;lt;&amp;lt;extend&amp;gt;&amp;gt;&lt;/code&gt; relationship can introduce additional behavior. They indicate where an extending use case may insert its logic without modifying the base use case.  &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%2Fgfw9w1lwbhqxibzlak67.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%2Fgfw9w1lwbhqxibzlak67.png" alt="UML Use Case Diagram Extension Points" width="552" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use a Use Case Diagram?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When defining system requirements.&lt;/li&gt;
&lt;li&gt;To communicate system behavior with stakeholders.&lt;/li&gt;
&lt;li&gt;During initial design discussions.&lt;/li&gt;
&lt;li&gt;As a reference for development and testing teams.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;UML Use Case Diagrams offer a clear and structured way to visualize system interactions and functionalities from the user's perspective. Their value comes from simplifying complex systems into understandable components that represent real-world usage scenarios. By mapping actors, use cases, and their relationships, these diagrams help teams define accurate requirements, communicate system behavior effectively, and maintain focus on user needs throughout the development process. From initial design discussions to testing and implementation, Use Case Diagrams serve as a valuable reference that ensures all stakeholders share a common understanding of what the system should accomplish.&lt;/p&gt;

</description>
      <category>uml</category>
      <category>architecture</category>
      <category>systems</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
