<?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>Comparing Caddy, nginx and Apache Configuration</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Mon, 07 Sep 2026 10:30:00 +0000</pubDate>
      <link>https://dev.to/edgaras/comparing-caddy-nginx-and-apache-configuration-2bnb</link>
      <guid>https://dev.to/edgaras/comparing-caddy-nginx-and-apache-configuration-2bnb</guid>
      <description>&lt;p&gt;Caddy, nginx and Apache httpd each serve static files, terminate TLS and reverse-proxy to a backend. They differ most in the configuration language, and in how much of the configuration has to be written at all.&lt;/p&gt;

&lt;p&gt;The versions used are Caddy v2.11.4, nginx 1.31.3 mainline and Apache httpd 2.4.68. Every configuration below ran on the official &lt;code&gt;caddy:alpine&lt;/code&gt;, &lt;code&gt;nginx:alpine&lt;/code&gt; and &lt;code&gt;httpd:alpine&lt;/code&gt; images, with &lt;code&gt;localhost&lt;/code&gt; and non-privileged ports in place of &lt;code&gt;example.com&lt;/code&gt;, port 80 and port 443, and a self-signed certificate for nginx and Apache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three projects
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Caddy&lt;/th&gt;
&lt;th&gt;nginx&lt;/th&gt;
&lt;th&gt;Apache httpd&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;2-clause BSD&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published by&lt;/td&gt;
&lt;td&gt;ZeroSSL, an HID Global company&lt;/td&gt;
&lt;td&gt;F5, Inc.&lt;/td&gt;
&lt;td&gt;The Apache Software Foundation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current release&lt;/td&gt;
&lt;td&gt;v2.11.4, 2026-06-03&lt;/td&gt;
&lt;td&gt;mainline 1.31.3, stable 1.30.4&lt;/td&gt;
&lt;td&gt;2.4.68, 2026-06-08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release lines&lt;/td&gt;
&lt;td&gt;One line&lt;/td&gt;
&lt;td&gt;Mainline (odd middle number), updated every one to two months; stable (even), about once a year&lt;/td&gt;
&lt;td&gt;2.4.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official binaries&lt;/td&gt;
&lt;td&gt;Static binaries for Linux, macOS, Windows and FreeBSD, plus Debian and Fedora/RHEL packages&lt;/td&gt;
&lt;td&gt;Linux packages for RHEL, Debian, Ubuntu, SLES, Alpine and Amazon Linux, and a Windows zip&lt;/td&gt;
&lt;td&gt;Source only; Windows binaries come from third-party vendors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What the configuration is written in
&lt;/h2&gt;

&lt;p&gt;Caddy's native configuration format is JSON. Any other format is converted to JSON by a config adapter, and the Caddyfile is the adapter in the standard build. &lt;code&gt;caddy adapt&lt;/code&gt; prints the conversion, so this Caddyfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com {
    root /var/www/html
    file_server
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;caddy adapt &lt;span class="nt"&gt;--config&lt;/span&gt; Caddyfile &lt;span class="nt"&gt;--pretty&lt;/span&gt;
&lt;span class="go"&gt;{
    "apps": {
        "http": {
            "servers": {
                "srv0": {
                    "listen": [
                        ":443"
                    ],
                    "routes": [
                        {
                            "match": [
                                {
                                    "host": [
                                        "example.com"
                                    ]
                                }
                            ],
                            "handle": [
                                {
                                    "handler": "subroute",
                                    "routes": [
                                        {
                                            "handle": [
                                                {
                                                    "handler": "vars",
                                                    "root": "/var/www/html"
                                                },
                                                {
                                                    "handler": "file_server",
                                                    "hide": [
                                                        "./Caddyfile"
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ],
                            "terminal": true
                        }
                    ]
                }
            }
        }
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON is what the server runs. The Caddyfile is one way to produce it.&lt;/p&gt;

&lt;p&gt;nginx configuration is a directive file. Simple directives end in a semicolon, and block directives use braces. Blocks that can contain other directives are called contexts: &lt;code&gt;events&lt;/code&gt;, &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;server&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;. Everything outside a block is in the &lt;code&gt;main&lt;/code&gt; context. A directive is inherited from the enclosing level only when the same directive is absent at the current level.&lt;/p&gt;

&lt;p&gt;Apache configuration is also a directive file, scoped by container sections: &lt;code&gt;&amp;lt;VirtualHost&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Directory&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;DirectoryMatch&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Files&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;FilesMatch&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Location&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;LocationMatch&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Proxy&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;If&amp;gt;&lt;/code&gt;. The sections merge in a fixed group order: &lt;code&gt;&amp;lt;Directory&amp;gt;&lt;/code&gt; together with &lt;code&gt;.htaccess&lt;/code&gt;, then &lt;code&gt;&amp;lt;DirectoryMatch&amp;gt;&lt;/code&gt;, then &lt;code&gt;&amp;lt;Files&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;FilesMatch&amp;gt;&lt;/code&gt;, then &lt;code&gt;&amp;lt;Location&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;LocationMatch&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;If&amp;gt;&lt;/code&gt; last.&lt;/p&gt;

&lt;p&gt;Position in the file matters differently in each of the three.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt;: file order is mostly ignored. The adapter sorts HTTP handler directives into a fixed built-in order, and directives of the same name by how specific their matchers are. A &lt;code&gt;route&lt;/code&gt; block keeps the written order for everything inside it. Plugin directives are not in the built-in order, so each needs a place set by the &lt;code&gt;order&lt;/code&gt; global option or a &lt;code&gt;route&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nginx&lt;/strong&gt;: file order decides which regular expression location is tried first. nginx checks the prefix locations first and keeps the longest match. It then tries the regular expression locations in the order they appear and takes the first that matches. If none match, the kept prefix is used. &lt;code&gt;^~&lt;/code&gt; on the longest prefix skips the regular expression step. &lt;code&gt;=&lt;/code&gt; is an exact match and stops the search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache&lt;/strong&gt;: file order decides within a group, not across groups. A &lt;code&gt;&amp;lt;Location&amp;gt;&lt;/code&gt; block always applies after a &lt;code&gt;&amp;lt;Directory&amp;gt;&lt;/code&gt; block, because of the group order above. &lt;code&gt;&amp;lt;Directory&amp;gt;&lt;/code&gt; is the exception inside its own group: shortest path first, so &lt;code&gt;&amp;lt;Directory "/var/web/dir"&amp;gt;&lt;/code&gt; before &lt;code&gt;&amp;lt;Directory "/var/web/dir/subdir"&amp;gt;&lt;/code&gt;, with file order deciding only between sections naming the same directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When more than one block could match a request, each server picks exactly one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt;: the site block with the most specific matching address handles the request. No other site block applies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nginx&lt;/strong&gt;: &lt;code&gt;server_name&lt;/code&gt; is matched in a fixed order. An exact name first, then the longest wildcard beginning with an asterisk, then the longest wildcard ending with one, then the first matching regular expression. A request matching none of these goes to the default server for that address and port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache&lt;/strong&gt;: exactly one &lt;code&gt;&amp;lt;VirtualHost&amp;gt;&lt;/code&gt; is selected. Directives from the other matching virtual hosts are never merged in.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The same site, three times
&lt;/h2&gt;

&lt;p&gt;The site serves static files from a directory, reverse-proxies &lt;code&gt;/api/&lt;/code&gt; to &lt;code&gt;127.0.0.1:8080&lt;/code&gt; with the client's Host preserved and &lt;code&gt;X-Forwarded-For&lt;/code&gt; and &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; set, terminates HTTPS, redirects HTTP to HTTPS, compresses text responses and writes an access log.&lt;/p&gt;

&lt;p&gt;Caddy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com {
    root /var/www/html
    encode
    reverse_proxy /api/* 127.0.0.1:8080
    file_server
    log {
        output file /var/log/caddy/access.log
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;worker_processes&lt;/span&gt;  &lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;worker_connections&lt;/span&gt;  &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;include&lt;/span&gt;       &lt;span class="s"&gt;mime.types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;default_type&lt;/span&gt;  &lt;span class="nc"&gt;application/octet-stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;log_format&lt;/span&gt;  &lt;span class="s"&gt;main&lt;/span&gt;  &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt; &lt;span class="s"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$time_local&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;
                      &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt; &lt;span class="nv"&gt;$body_bytes_sent&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$http_referer&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$http_user_agent&lt;/span&gt;&lt;span class="s"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;access_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt;  &lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;gzip&lt;/span&gt;             &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_min_length&lt;/span&gt;  &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_vary&lt;/span&gt;        &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_types&lt;/span&gt;       &lt;span class="nc"&gt;text/plain&lt;/span&gt; &lt;span class="nc"&gt;text/css&lt;/span&gt; &lt;span class="nc"&gt;text/xml&lt;/span&gt; &lt;span class="nc"&gt;application/json&lt;/span&gt;
                     &lt;span class="nc"&gt;application/javascript&lt;/span&gt; &lt;span class="nc"&gt;application/xml&lt;/span&gt; &lt;span class="nc"&gt;image/svg&lt;/span&gt;&lt;span class="s"&gt;+xml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt;       &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt;  &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt;       &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt;       &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;http2&lt;/span&gt;        &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt;  &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;      &lt;span class="n"&gt;/etc/nginx/ssl/example.com.crt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt;  &lt;span class="n"&gt;/etc/nginx/ssl/example.com.key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt;              &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt;         &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt;   &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&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;Apache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="nc"&gt;ServerRoot&lt;/span&gt; "/usr/local/apache2"

&lt;span class="nc"&gt;Listen&lt;/span&gt; 80
&lt;span class="nc"&gt;Listen&lt;/span&gt; 443

&lt;span class="nc"&gt;LoadModule&lt;/span&gt; mpm_event_module   modules/mod_mpm_event.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; unixd_module       modules/mod_unixd.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; authz_core_module  modules/mod_authz_core.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; mime_module        modules/mod_mime.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; dir_module         modules/mod_dir.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; alias_module       modules/mod_alias.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; log_config_module  modules/mod_log_config.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; filter_module      modules/mod_filter.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; deflate_module     modules/mod_deflate.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; headers_module     modules/mod_headers.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; ssl_module         modules/mod_ssl.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; proxy_module       modules/mod_proxy.so
&lt;span class="nc"&gt;LoadModule&lt;/span&gt; proxy_http_module  modules/mod_proxy_http.so

&lt;span class="nc"&gt;User&lt;/span&gt;  daemon
&lt;span class="nc"&gt;Group&lt;/span&gt; daemon

&lt;span class="nc"&gt;ServerName&lt;/span&gt; example.com
&lt;span class="nc"&gt;TypesConfig&lt;/span&gt; conf/mime.types
&lt;span class="nc"&gt;DirectoryIndex&lt;/span&gt; index.html

&lt;span class="nc"&gt;ErrorLog&lt;/span&gt; logs/error_log
&lt;span class="nc"&gt;LogFormat&lt;/span&gt; "%h %l %u %t \"%r\" %&amp;gt;s %b \"%{Referer}i\" \"%{User-agent}i\"" combined
&lt;span class="nc"&gt;CustomLog&lt;/span&gt; logs/access_log combined

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;Directory&lt;/span&gt;&lt;span class="sr"&gt; "/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;Require&lt;/span&gt; &lt;span class="ss"&gt;all&lt;/span&gt; denied
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; *:80&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;ServerName&lt;/span&gt; example.com
    &lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="ss"&gt;permanent&lt;/span&gt; "/" "https://example.com/"
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; *:443&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;ServerName&lt;/span&gt;    example.com
    &lt;span class="nc"&gt;DocumentRoot&lt;/span&gt;  "/var/www/html"

    &lt;span class="nc"&gt;SSLEngine&lt;/span&gt; &lt;span class="ss"&gt;on&lt;/span&gt;
    &lt;span class="nc"&gt;SSLCertificateFile&lt;/span&gt;    "conf/example.com.crt"
    &lt;span class="nc"&gt;SSLCertificateKeyFile&lt;/span&gt; "conf/example.com.key"

    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;Directory&lt;/span&gt;&lt;span class="sr"&gt; "/var/www/html"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="nc"&gt;Require&lt;/span&gt; &lt;span class="ss"&gt;all&lt;/span&gt; granted
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class="nc"&gt;AddOutputFilterByType&lt;/span&gt; DEFLATE text/html text/plain text/css text/javascript \
                          application/javascript application/json application/xml image/svg+xml

    &lt;span class="nc"&gt;ProxyRequests&lt;/span&gt;     &lt;span class="ss"&gt;Off&lt;/span&gt;
    &lt;span class="nc"&gt;ProxyPreserveHost&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;
    &lt;span class="nc"&gt;RequestHeader&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Forwarded-Proto "https"
    &lt;span class="nc"&gt;ProxyPass&lt;/span&gt;        "/api/" "http://127.0.0.1:8080/api/"
    &lt;span class="nc"&gt;ProxyPassReverse&lt;/span&gt; "/api/" "http://127.0.0.1:8080/api/"
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CustomLog&lt;/code&gt; is at server level rather than inside the &lt;code&gt;:443&lt;/code&gt; virtual host. Inside the virtual host it logs HTTPS requests only, and the port-80 redirects are not logged at all. The Caddy and nginx configurations log both.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Caddy&lt;/th&gt;
&lt;th&gt;nginx&lt;/th&gt;
&lt;th&gt;Apache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Non-blank lines&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytes&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;td&gt;1303&lt;/td&gt;
&lt;td&gt;1839&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LoadModule&lt;/code&gt; lines&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ordering is the same for the smallest job, serving one static directory over plain HTTP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Caddy&lt;/th&gt;
&lt;th&gt;nginx&lt;/th&gt;
&lt;th&gt;Apache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Non-blank lines&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytes&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;477&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LoadModule&lt;/code&gt; lines&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why the Caddyfile is shorter
&lt;/h2&gt;

&lt;p&gt;The difference is mostly defaults, not a more compact syntax. The Caddyfile names the site address, and Caddy derives the rest from it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS&lt;/strong&gt;: a site address with a hostname turns on automatic HTTPS, so the Caddyfile needs no certificate directive. nginx needs &lt;code&gt;ssl_certificate&lt;/code&gt; and &lt;code&gt;ssl_certificate_key&lt;/code&gt;. Apache needs &lt;code&gt;SSLEngine on&lt;/code&gt;, &lt;code&gt;SSLCertificateFile&lt;/code&gt; and &lt;code&gt;SSLCertificateKeyFile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The HTTP to HTTPS redirect&lt;/strong&gt;: Caddy adds it and answers with 308. nginx and Apache each need a second server block or virtual host, which return 301 above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directive order&lt;/strong&gt;: the adapter sorts &lt;code&gt;encode&lt;/code&gt;, &lt;code&gt;reverse_proxy&lt;/code&gt; and &lt;code&gt;file_server&lt;/code&gt; into the built-in order, so no &lt;code&gt;route&lt;/code&gt; block is needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/2&lt;/strong&gt;: Caddy serves it on the HTTPS listener with no directive. nginx needs &lt;code&gt;http2 on;&lt;/code&gt;, added in 1.25.1 and off by default. Apache needs &lt;code&gt;mod_http2&lt;/code&gt; and a &lt;code&gt;Protocols&lt;/code&gt; line, and the configuration above has neither.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more defaults differ, both between nginx and the other two.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nginx writes an access log with no configuration at all, to &lt;code&gt;logs/access.log&lt;/code&gt; in its predefined &lt;code&gt;combined&lt;/code&gt; format. Caddy and Apache write none until configured.&lt;/li&gt;
&lt;li&gt;Caddy and Apache both send &lt;code&gt;Vary: Accept-Encoding&lt;/code&gt; on compressed responses with no extra configuration. nginx needs &lt;code&gt;gzip_vary on&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Headers sent to the proxied backend
&lt;/h2&gt;

&lt;p&gt;Each server was configured to proxy everything to &lt;code&gt;127.0.0.1:8080&lt;/code&gt;, with no header directives of any kind, and the backend printed the headers it received. The request carried &lt;code&gt;Host: site.test&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header at the backend&lt;/th&gt;
&lt;th&gt;Caddy 2.11.4&lt;/th&gt;
&lt;th&gt;nginx 1.31.3&lt;/th&gt;
&lt;th&gt;httpd 2.4.68&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Host&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;site.test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1:8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1:8080&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Forwarded-Proto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Forwarded-Host&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;site.test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;site.test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Forwarded-Server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localhost&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Via&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.1 Caddy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Caddy passes the client's Host through unchanged and sets the three &lt;code&gt;X-Forwarded-*&lt;/code&gt; headers itself. It ignores any incoming values of those three unless &lt;code&gt;trusted_proxies&lt;/code&gt; is configured.&lt;/p&gt;

&lt;p&gt;nginx replaces Host with &lt;code&gt;$proxy_host&lt;/code&gt; and adds nothing else, which is why the configuration above carries four &lt;code&gt;proxy_set_header&lt;/code&gt; lines. &lt;code&gt;$host&lt;/code&gt; holds the host without the port, so a request to &lt;code&gt;example.com:8443&lt;/code&gt; reaches the backend as &lt;code&gt;Host: example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Apache replaces Host with the &lt;code&gt;ProxyPass&lt;/code&gt; target unless &lt;code&gt;ProxyPreserveHost On&lt;/code&gt; is set. &lt;code&gt;mod_proxy_http&lt;/code&gt; adds &lt;code&gt;X-Forwarded-For&lt;/code&gt;, &lt;code&gt;X-Forwarded-Host&lt;/code&gt; and &lt;code&gt;X-Forwarded-Server&lt;/code&gt; by default, under &lt;code&gt;ProxyAddHeaders&lt;/code&gt;, which has defaulted to on since 2.3.10. &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; is not in that set, so the Apache configuration adds it with &lt;code&gt;RequestHeader&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking a configuration
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Caddy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;caddy validate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deserializes, loads and provisions every module without starting the config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caddy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;caddy adapt --validate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adapts a Caddyfile to JSON and then validates it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caddy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;caddy fmt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Formats a Caddyfile; exits 1 when the output differs from the input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nginx&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx -t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tests the syntax and tries to open the files the configuration references&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nginx&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx -T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The same, and dumps the whole configuration to stdout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apachectl configtest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parses the configuration files and reports &lt;code&gt;Syntax OK&lt;/code&gt; or the error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;httpd -S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows the parsed virtual host settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither nginx nor Apache documents an equivalent of &lt;code&gt;caddy fmt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reloading
&lt;/h2&gt;

&lt;p&gt;All three can replace the running configuration without dropping connections, by different mechanisms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt;: &lt;code&gt;caddy reload&lt;/code&gt; posts the configuration to the admin API's &lt;code&gt;/load&lt;/code&gt; endpoint. The new configuration is provisioned and started before the old one is stopped, so both run briefly. The command fails if the admin endpoint is disabled. The grace period for old connections is eternal by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nginx&lt;/strong&gt;: &lt;code&gt;nginx -s reload&lt;/code&gt; signals the master process. The master checks the syntax, opens log files and new listen sockets, starts workers with the new configuration and tells the old workers to shut down. Old workers close their listen sockets, finish serving connected clients and exit. &lt;code&gt;worker_shutdown_timeout&lt;/code&gt; caps that wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache&lt;/strong&gt;: &lt;code&gt;apachectl graceful&lt;/code&gt; sends &lt;code&gt;USR1&lt;/code&gt;. Children finish their current request, the parent re-reads the configuration files and re-opens the log files, and each child is replaced from the new generation. &lt;code&gt;apachectl restart&lt;/code&gt; sends &lt;code&gt;HUP&lt;/code&gt; instead, which kills children immediately and zeroes the &lt;code&gt;mod_status&lt;/code&gt; counters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A configuration with an error is rejected by all three, and the running server keeps the previous configuration. Each was given a valid configuration, then a broken one, then asked to reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;caddy reload &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/caddy/Caddyfile
&lt;span class="go"&gt;{"level":"info","ts":1786909867.4621506,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
Error: adapting config using caddyfile: /etc/caddy/Caddyfile:6: unrecognized directive: this_is_not_a_directive

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload
&lt;span class="gp"&gt;2026/08/16 19:51:10 [emerg] 31#&lt;/span&gt;31: unknown directive &lt;span class="s2"&gt;"not_a_directive"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; /etc/nginx/nginx.conf:6
&lt;span class="go"&gt;nginx: [emerg] unknown directive "not_a_directive" in /etc/nginx/nginx.conf:6

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apachectl &lt;span class="nt"&gt;-k&lt;/span&gt; graceful
&lt;span class="go"&gt;AH00526: Syntax error on line 15 of /usr/local/apache2/conf/httpd.conf:
Invalid command 'NotARealDirective', perhaps misspelled or defined by a module not included in the server configuration
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In each case the server continued to answer with the configuration it had before the attempt, and a later reload of a corrected file applied the change.&lt;/p&gt;

&lt;p&gt;Apache keeps its ports bound across a restart. A &lt;code&gt;Listen&lt;/code&gt; change that conflicts with the existing binding therefore fails and terminates the server, and applying it needs a full stop and start.&lt;/p&gt;

&lt;p&gt;nginx can replace its own executable without stopping. Sending &lt;code&gt;USR2&lt;/code&gt; starts a second master from the new binary, &lt;code&gt;WINCH&lt;/code&gt; shuts down the old workers, and &lt;code&gt;QUIT&lt;/code&gt; retires the old master. The old master keeps its listen sockets throughout, so it can restart its workers with &lt;code&gt;HUP&lt;/code&gt; if the new binary misbehaves. Caddy's &lt;code&gt;caddy upgrade&lt;/code&gt; replaces the binary on disk only and leaves the running process on the old one.&lt;/p&gt;

&lt;p&gt;Caddy is the only one of the three whose open-source build exposes an HTTP API for the whole running configuration: the admin endpoint on &lt;code&gt;localhost:2019&lt;/code&gt;, with &lt;code&gt;POST /load&lt;/code&gt;, &lt;code&gt;POST /stop&lt;/code&gt;, &lt;code&gt;POST /adapt&lt;/code&gt; and the &lt;code&gt;/config/&lt;/code&gt; path supporting &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt;, plus &lt;code&gt;Etag&lt;/code&gt; and &lt;code&gt;If-Match&lt;/code&gt; for optimistic concurrency. nginx documents &lt;code&gt;ngx_http_api_module&lt;/code&gt; and the &lt;code&gt;-l&lt;/code&gt; control REST API added in 1.29.8 as commercial-subscription features. Apache's documentation states that changes to the main configuration files are recognized only on start or restart.&lt;/p&gt;

&lt;p&gt;Apache has one mechanism the others do not: &lt;code&gt;.htaccess&lt;/code&gt;, read on every request, so changes take effect without a restart. &lt;code&gt;AllowOverride&lt;/code&gt; defaults to &lt;code&gt;None&lt;/code&gt;. Where it is enabled, httpd looks for an &lt;code&gt;.htaccess&lt;/code&gt; file in the requested directory and in every higher-level directory that also has it enabled, on every request, whether or not the files exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which modules have to be present
&lt;/h2&gt;

&lt;p&gt;The configurations above do not fail in the same way. The Caddy one runs on any standard Caddy binary. The nginx one fails on a binary built without SSL or HTTP/2 support. The Apache one fails unless each of its 13 modules is loaded, and the error names the directive that stopped working, not the module that provides it. &lt;code&gt;Invalid command 'SSLEngine'&lt;/code&gt; does not say that the &lt;code&gt;LoadModule ssl_module&lt;/code&gt; line is missing. Removing each &lt;code&gt;LoadModule&lt;/code&gt; line in turn and running &lt;code&gt;httpd -t&lt;/code&gt; on 2.4.68 matches each error to its module:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module removed&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;httpd -t&lt;/code&gt; reports&lt;/th&gt;
&lt;th&gt;Provides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mpm_event_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AH00534: httpd: Configuration error: No MPM loaded.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The process model. Exactly one MPM must be loaded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unixd_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'User'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Group&lt;/code&gt;, the account the server runs as&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;authz_core_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'Require'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Require&lt;/code&gt;, the access rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mime_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'TypesConfig'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Content-Type from the file extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dir_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'DirectoryIndex'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serving &lt;code&gt;index.html&lt;/code&gt; for directory requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alias_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'Redirect'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Redirect&lt;/code&gt; in the port-80 virtual host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;log_config_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'LogFormat'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LogFormat&lt;/code&gt; and &lt;code&gt;CustomLog&lt;/code&gt;, the access log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;filter_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'AddOutputFilterByType'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attaching the compression filter by content type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deflate_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Unknown filter provider DEFLATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The DEFLATE filter itself, gzip compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;headers_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'RequestHeader'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RequestHeader&lt;/code&gt;, the &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ssl_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid command 'SSLEngine'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTPS, &lt;code&gt;SSLEngine&lt;/code&gt; and the certificate directives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proxy_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Cannot load modules/mod_proxy_http.so into server: ... ap_proxy_pre_http_request: symbol not found&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The proxy core and &lt;code&gt;ProxyPass&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proxy_http_module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Syntax OK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP support for &lt;code&gt;ProxyPass&lt;/code&gt;, the &lt;code&gt;/api/&lt;/code&gt; proxying&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the exception. Without &lt;code&gt;mod_proxy_http&lt;/code&gt; the configuration passes the syntax check and the server starts, static files are served normally, and only requests to &lt;code&gt;/api/&lt;/code&gt; fail. Those return status 500 and log &lt;code&gt;AH01144: No protocol handler was valid for the URL /api/hello (scheme 'http')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;None of this means recompiling. The official &lt;code&gt;httpd:alpine&lt;/code&gt; image ships 130 module files, including all 13 the configuration needs, and its stock &lt;code&gt;httpd.conf&lt;/code&gt; loads 25 of them. Adding the missing &lt;code&gt;LoadModule&lt;/code&gt; lines is the whole job.&lt;/p&gt;

&lt;p&gt;nginx has the equivalent constraint at build time rather than at config time. HTTPS and HTTP/2 support are compile-time options, &lt;code&gt;--with-http_ssl_module&lt;/code&gt; and &lt;code&gt;--with-http_v2_module&lt;/code&gt;, so whether the nginx configuration above works depends on how the binary was built, not on a directive. Prebuilt packages include them: &lt;code&gt;nginx -V&lt;/code&gt; on the official &lt;code&gt;nginx:alpine&lt;/code&gt; image reports both, plus &lt;code&gt;--with-http_v3_module&lt;/code&gt; and &lt;code&gt;--with-stream&lt;/code&gt;. A dynamic module has to match the running nginx exactly, because nginx refuses to load a module whose compiled-in version or module signature differs.&lt;/p&gt;

&lt;p&gt;Caddy has no load step at all. &lt;code&gt;caddy list-modules&lt;/code&gt; on v2.11.4 reports 132 standard modules in the binary, and every directive in the Caddyfile above comes from that set. Adding anything outside it means a new binary, built with &lt;code&gt;xcaddy&lt;/code&gt;, downloaded from the project's download page, or fetched by &lt;code&gt;caddy add-package&lt;/code&gt;, which the command-line reference marks experimental. That command replaces the binary on disk and leaves the running process on the old one until it restarts.&lt;/p&gt;

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

&lt;p&gt;The three configuration languages are nothing alike. The length differences come from defaults rather than syntax. Much of what the nginx and Apache configurations write out, Caddy does by default. Enabling a feature also happens in a different place in each server. Apache does it in the config file, nginx at build time, Caddy in the choice of binary. None of this tells you how the three behave under load. That depends on each server's process and connection handling, and the configuration file shows none of it.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>apache</category>
      <category>caddy</category>
      <category>webserver</category>
    </item>
    <item>
      <title>UML Activity Diagrams</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Tue, 01 Sep 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/edgaras/uml-activity-diagrams-4cmj</link>
      <guid>https://dev.to/edgaras/uml-activity-diagrams-4cmj</guid>
      <description>&lt;p&gt;A UML activity diagram models a process as a set of actions connected by flows. The process does not have to be software. An expense approval that crosses three departments fits the notation as well as a CI pipeline does. Among the UML behavior diagrams, this is the one about steps and their order, not about objects and the messages between them.&lt;/p&gt;

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

&lt;p&gt;An activity diagram describes the behavior of a system or an organization as steps in the order they run. You read it from the initial node, along the arrows, to a final node. It shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actions&lt;/strong&gt;: the individual steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control flows&lt;/strong&gt;: arrows giving the order between steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions and merges&lt;/strong&gt;: where the process takes one of several paths, and where those paths come back together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forks and joins&lt;/strong&gt;: where the process splits into parallel paths, and where it waits for all of them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object flows&lt;/strong&gt;: data or documents moving between steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partitions&lt;/strong&gt;: lanes naming who performs which steps&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Working out a process before automating it&lt;/strong&gt;: draw the manual process as it runs today, then mark the actions the new system takes over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filling in a use case&lt;/strong&gt;: a &lt;a href="https://dev.to/edgaras/uml-use-case-diagrams-2pk4"&gt;use case diagram&lt;/a&gt; names the goal, Place Order, and stops there. The steps go in an activity diagram.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finding parallelism&lt;/strong&gt;: some steps can start together and some cannot. The diagram states which, and where the process has to wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recording responsibility&lt;/strong&gt;: when a process crosses a team or service boundary, lanes show where work passes from one side to the other.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Action and Control Flow
&lt;/h3&gt;

&lt;p&gt;An action is a single step in the process. It is drawn as a rounded rectangle with the step's name inside. A control flow is the solid arrow between two actions, and it means the first action must finish before the second starts. In a hiring process, Screen application and Schedule interview are actions joined by a control flow. A condition in square brackets on an arrow is a guard; the flow is taken only when the condition is true.&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%2Ffvqtzflektj24jktyora.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%2Ffvqtzflektj24jktyora.png" alt="UML Activity Diagram actions connected by a control flow with a guard" width="623" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial and Final Nodes
&lt;/h3&gt;

&lt;p&gt;An initial node is a solid black circle. It marks where the process starts. There are two ending nodes. The activity final node is a solid circle inside a hollow ring; it ends the whole process, including parallel paths that are still running. The flow final node is a circle with an X in it; it ends only the path that reaches it, and the rest of the process continues. The receipt email path of an order process ends at a flow final while the packing path continues.&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%2Flk4udlgknqr24g1z96ft.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%2Flk4udlgknqr24g1z96ft.png" alt="UML Activity Diagram initial node, activity final node and flow final node" width="478" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision and Merge Nodes
&lt;/h3&gt;

&lt;p&gt;A decision node is a diamond with one arrow in and several out. Each outgoing arrow has a guard, and the process follows the arrow whose guard is true. One arrow may use the predefined guard &lt;code&gt;[else]&lt;/code&gt;, taken when no other arrow accepts the flow. A loan application takes the &lt;code&gt;[approved]&lt;/code&gt; arrow or the &lt;code&gt;[rejected]&lt;/code&gt; arrow, never both. A merge node is the same diamond with several arrows in and one out. It does not wait; each arriving flow continues straight out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa3gssarr5o4pns63nw6m.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%2Fa3gssarr5o4pns63nw6m.png" alt="UML Activity Diagram decision node with guarded edges and merge node" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fork and Join Nodes
&lt;/h3&gt;

&lt;p&gt;A fork is a thick bar with one arrow in and several out. When the flow reaches it, every outgoing path starts at once; where a decision picks one path, a fork starts them all. A join is a thick bar with several arrows in and one out. It waits for every incoming path to arrive, then the process continues as a single flow. An airline check-in forks into Check passport and Weigh baggage, and boarding waits at the join for both.&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%2Fijl12095eudmkzhb9xl2.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%2Fijl12095eudmkzhb9xl2.png" alt="UML Activity Diagram fork and join with two parallel actions" width="489" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Object Nodes and Data Stores
&lt;/h3&gt;

&lt;p&gt;An object node is a rectangle between two actions naming the data that moves from one to the other, an Invoice for example. Arrows entering or leaving it are object flows; they carry data, not just order. The same information can be drawn as pins, small squares on the edge of an action, one per input or output. An object node with the keyword «datastore» is a data store: it holds its contents for the whole run of the process, and an action reading from it gets a copy while the original stays in place. A customer register read by both the billing step and the shipping step is a data store.&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%2Fc353msk8zdgc89py1l8h.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%2Fc353msk8zdgc89py1l8h.png" alt="UML Activity Diagram object node between two actions and a datastore node" width="714" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Partitions
&lt;/h3&gt;

&lt;p&gt;A partition, usually called a swimlane, divides the diagram into rows or columns, one per performer. The performer's name goes in a box at one end of the lane, at the top when lanes run vertically, and every action inside the lane belongs to that performer. An insurance claim diagram gets one lane for the customer, one for the claims handler, and one for accounting. When lanes would make the diagram too wide, write the partition name in parentheses above the action name 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%2Fnd01b9jc6hymsfhvdqqb.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%2Fnd01b9jc6hymsfhvdqqb.png" alt="UML Activity Diagram partitions with a flow crossing lane borders" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Send Signal and Accept Event Actions
&lt;/h3&gt;

&lt;p&gt;Send signal and accept event actions communicate with something outside the process. A send signal action is a box with one pointed end. The process sends the named signal and continues without waiting for a reply. An accept event action is a box with one end notched inward. The flow stops there until the named event arrives. In an order process, Request payment is a send signal. Payment confirmed is the event the process waits for before shipping. An accept event action with no incoming arrow waits the whole time the process runs. Its outgoing flow starts each time the event occurs. A wait for a specific time such as the end of the month is drawn as an hourglass.&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%2Fgttlblq4x4klrkm2jf0e.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%2Fgttlblq4x4klrkm2jf0e.png" alt="UML Activity Diagram send signal action, accept event action and time event hourglass" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Pull Request CI Pipeline
&lt;/h2&gt;

&lt;p&gt;The diagram models what happens after a developer pushes a branch. Two lanes split the work between the developer and the CI server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwbfidij7gw45evf75yx.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%2Fjwbfidij7gw45evf75yx.png" alt="UML Activity Diagram of a pull request CI pipeline with Developer and CI Server partitions" width="799" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The initial node is in the Developer lane. Push branch is the only action the developer performs.&lt;/li&gt;
&lt;li&gt;The flow crosses into the CI Server lane and reaches a fork. Run linter and Run unit tests start at the same time.&lt;/li&gt;
&lt;li&gt;The join under them waits for both to finish.&lt;/li&gt;
&lt;li&gt;The decision routes on the result. &lt;code&gt;[all checks passed]&lt;/code&gt; continues to Build container image. &lt;code&gt;[else]&lt;/code&gt; goes to Post failure comment.&lt;/li&gt;
&lt;li&gt;A Container image object node is between Build container image and Deploy to staging. The image itself is the input of the deploy step.&lt;/li&gt;
&lt;li&gt;The two branches meet at a merge node, and the flow ends with the send signal action Notify developer and an activity final node. The pipeline stops as soon as the notification is sent.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a flow final to end one path, an activity final to end the whole process&lt;/strong&gt;: the first flow to reach an activity final also cancels every other path still running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write guards that cannot both be true&lt;/strong&gt;: &lt;code&gt;[total &amp;gt;= 100]&lt;/code&gt; beside &lt;code&gt;[total &amp;lt;= 100]&lt;/code&gt; can both be true at 100, and the tool then picks the edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not guard the edges out of a fork when a join collects them&lt;/strong&gt;: a flow stopped by a failed guard never reaches the join, and the process stops there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omit pins when they make the diagram hard to read&lt;/strong&gt;: an action name like Build container image already names its output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break an arrow that would cross the whole diagram with a pair of connectors&lt;/strong&gt;: the arrow ends at a small labeled circle and continues from the circle with the same label elsewhere on the page.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;An activity diagram records how one process runs from start to finish, including any parallel paths. It does not record which objects carry out a step or what messages pass between them. That is what &lt;a href="https://dev.to/edgaras/uml-sequence-diagrams-18e4"&gt;sequence diagrams&lt;/a&gt; show. A state machine diagram covers the states one object passes through over its lifetime.&lt;/p&gt;

</description>
      <category>uml</category>
      <category>architecture</category>
      <category>systems</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>jina-embeddings-v4 as an OpenAI-Compatible Embeddings Server</title>
      <dc:creator>Edgaras</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:30:00 +0000</pubDate>
      <link>https://dev.to/edgaras/jina-embeddings-v4-as-an-openai-compatible-embeddings-server-35j8</link>
      <guid>https://dev.to/edgaras/jina-embeddings-v4-as-an-openai-compatible-embeddings-server-35j8</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/Edgaras0x4E/jina-embeddings-v4" rel="noopener noreferrer"&gt;jina-embeddings-v4&lt;/a&gt; is a self-hosted server for the jina-embeddings-v4 embedding model with an OpenAI-compatible &lt;code&gt;/v1/embeddings&lt;/code&gt; endpoint. It runs on a single NVIDIA GPU. An application that calls OpenAI for embeddings can call this server instead. The request and response bodies are the same, so setting the client's base URL is the only change needed.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text that cannot leave your network&lt;/strong&gt;: the model runs on your infrastructure, and no external calls are made for embeddings after the weights are downloaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding a large dataset&lt;/strong&gt;: there is no per-request charge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing OpenAI client code&lt;/strong&gt;: the endpoint accepts the OpenAI request format and returns the OpenAI response format, so the calls in the application need no rewrite. The vectors are 2048-dimensional and unrelated to OpenAI's, so text already embedded with an OpenAI model has to be embedded again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multilingual and code retrieval&lt;/strong&gt;: one model embeds text in many languages, and source code when &lt;code&gt;task&lt;/code&gt; is &lt;code&gt;code&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is in the image
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt;: jina-embeddings-v4, 3.8B parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: 2048-dimensional &lt;code&gt;float32&lt;/code&gt; vectors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU memory&lt;/strong&gt;: 8.1 GB once the model is loaded, and over 10 GB while embedding a text of several thousand tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt;: FastAPI and sentence-transformers&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;An NVIDIA GPU with at least 10 GB of VRAM.&lt;/li&gt;
&lt;li&gt;An NVIDIA driver compatible with CUDA 12.4.&lt;/li&gt;
&lt;li&gt;Docker, Docker Compose, and the &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;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&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;jina&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/jina-embeddings-v4: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;8081:80"&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;jina-cache:/root/.cache/huggingface&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HF_HOME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/root/.cache/huggingface&lt;/span&gt;
      &lt;span class="na"&gt;API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-api-key-here&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;jina-cache&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;p&gt;The image download is about 7.8 GB, and 14.7 GB unpacked on disk. The prebuilt image needs no build step. Building from source (&lt;code&gt;git clone&lt;/code&gt; the repo, then &lt;code&gt;docker compose up -d --build&lt;/code&gt;) compiles &lt;code&gt;flash-attn&lt;/code&gt; against PyTorch 2.5.1 and CUDA 12.4, which takes 10 to 20 minutes.&lt;/p&gt;

&lt;p&gt;The weights are not in the image: on first start the server downloads about 7 GB from Hugging Face and loads them into GPU memory. On later starts the server reads the weights from the volume instead of downloading them again.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;/health&lt;/code&gt; returns &lt;code&gt;ok&lt;/code&gt;, the server is ready to embed:&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:8081/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="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Embedding text
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8081/v1/embeddings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer your-api-key-here"&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;"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;'{"input": ["The train leaves at eight in the morning"]}'&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;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&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;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"embedding"&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="mf"&gt;-0.008472833782434464&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.024404730647802353&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.007389210630208254&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jinaai/jina-embeddings-v4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&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 array holds 2048 values. Three are shown.&lt;/p&gt;

&lt;p&gt;The response uses the OpenAI list format. &lt;code&gt;data&lt;/code&gt; holds one object per input text, each with an &lt;code&gt;embedding&lt;/code&gt; array and its &lt;code&gt;index&lt;/code&gt;. &lt;code&gt;usage.prompt_tokens&lt;/code&gt; counts the input tokens. &lt;code&gt;total_tokens&lt;/code&gt; equals it, since embedding produces no output tokens.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;input&lt;/code&gt; also accepts a list of strings, one request for the whole batch.&lt;/p&gt;

&lt;p&gt;The full request body:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&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;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;One string or a list of strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Echoed back in the response. The server always serves the model set by &lt;code&gt;MODEL_ID&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;text-matching&lt;/code&gt; (default), &lt;code&gt;retrieval&lt;/code&gt;, or &lt;code&gt;code&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;query&lt;/code&gt; or &lt;code&gt;passage&lt;/code&gt;. Used only when &lt;code&gt;task&lt;/code&gt; is &lt;code&gt;retrieval&lt;/code&gt;, defaults to &lt;code&gt;passage&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encoding_format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Accepted for OpenAI compatibility and ignored. Vectors are always &lt;code&gt;float32&lt;/code&gt; arrays.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Task adapters
&lt;/h2&gt;

&lt;p&gt;The model produces a different embedding for the same text depending on the value of &lt;code&gt;task&lt;/code&gt;. The default, &lt;code&gt;text-matching&lt;/code&gt;, is for comparing two texts of the same kind, such as two support tickets or two product descriptions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;retrieval&lt;/code&gt; is for search, where a short query is matched against longer documents. Embed the documents with &lt;code&gt;prompt_name&lt;/code&gt; set to &lt;code&gt;passage&lt;/code&gt; and the query with &lt;code&gt;prompt_name&lt;/code&gt; set to &lt;code&gt;query&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;curl http://localhost:8081/v1/embeddings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer your-api-key-here"&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;"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;'{
        "input": ["when does the train leave"],
        "task": "retrieval",
        "prompt_name": "query"
      }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;code&lt;/code&gt; is for source code and code search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the OpenAI Python client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-key-here&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&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:8081/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jinaai/jina-embeddings-v4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;input&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;how long does the journey take&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2048
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;task&lt;/code&gt; and &lt;code&gt;prompt_name&lt;/code&gt; fields are not OpenAI parameters, so the SDK passes them through &lt;code&gt;extra_body&lt;/code&gt;:&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="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jinaai/jina-embeddings-v4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;input&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;SELECT id, name FROM users WHERE active = 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;extra_body&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;task&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;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the server runs without &lt;code&gt;API_KEY&lt;/code&gt;, the OpenAI SDK still rejects an empty &lt;code&gt;api_key&lt;/code&gt; string. Pass any non-empty placeholder.&lt;/p&gt;

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

&lt;p&gt;Environment variables on the &lt;code&gt;jina&lt;/code&gt; service:&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;MODEL_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jinaai/jina-embeddings-v4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hugging Face model id. Override only for a fork or finetune with the same architecture.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HF_HOME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/root/.cache/huggingface&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cache path inside the container. The compose file mounts the &lt;code&gt;jina-cache&lt;/code&gt; volume there, so a new container reuses the downloaded weights.&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;unset (optional)&lt;/td&gt;
&lt;td&gt;Bearer token for &lt;code&gt;/v1/embeddings&lt;/code&gt;. If unset, the endpoint accepts requests without a token.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The compose file maps host port 8081 to port 80 in the container. If another service already listens on 8081, change the first number in &lt;code&gt;8081:80&lt;/code&gt;.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/Edgaras0x4E/jina-embeddings-v4" rel="noopener noreferrer"&gt;github.com/Edgaras0x4E/jina-embeddings-v4&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/jina-embeddings-v4" rel="noopener noreferrer"&gt;hub.docker.com/r/edgaras0x4e/jina-embeddings-v4&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>openai</category>
      <category>machinelearning</category>
      <category>vectordatabase</category>
      <category>embeddings</category>
    </item>
    <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>
  </channel>
</rss>
