<?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: Mihai Farcas</title>
    <description>The latest articles on DEV Community by Mihai Farcas (@mihailtd).</description>
    <link>https://dev.to/mihailtd</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%2F142282%2F3789fb4b-f94d-4d22-9546-ffc5576a73ee.png</url>
      <title>DEV Community: Mihai Farcas</title>
      <link>https://dev.to/mihailtd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mihailtd"/>
    <language>en</language>
    <item>
      <title>Do You Need Rancher Desktop (or Docker Desktop) on Linux? My CachyOS Setup</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:58:35 +0000</pubDate>
      <link>https://dev.to/mihailtd/do-you-need-rancher-desktop-or-docker-desktop-on-linux-my-cachyos-setup-298a</link>
      <guid>https://dev.to/mihailtd/do-you-need-rancher-desktop-or-docker-desktop-on-linux-my-cachyos-setup-298a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mihai.ltd/blog/rancher-desktop-docker-desktop-on-linux" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Like everyone, I switched to CachyOS from Windows. Now I can say "I use Arch, by the way." But does native Linux still need a desktop container manager?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Like everyone else in the homelab and systems engineering community, I switched to CachyOS from Windows. I wiped the workstation, configured my NVMe drives on Btrfs, and can now say "I use Arch, by the way."&lt;/p&gt;

&lt;p&gt;Moving to an Arch-based distribution with optimized x86-64-v3 packages, zram, and the BORE (Burst-Oriented Response Enhancer) CPU scheduler changes your baseline expectations. Everything executes with zero latency.&lt;/p&gt;

&lt;p&gt;That speed brings up a fundamental question for daily container workflows: &lt;strong&gt;Do you actually need Rancher Desktop—or Docker Desktop—on Linux?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On Windows and macOS, the answer is straightforward. Neither operating system can run Linux containers natively. They both require a virtualization layer: WSL2 on Windows or the Apple Virtualization framework on macOS. If you work on Windows, a dedicated manager is mandatory, as detailed in my companion guide on &lt;a href="https://mihai.ltd/blog/rancher-desktop-windows-guide" rel="noopener noreferrer"&gt;Rancher Desktop on Windows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On Linux, your operating system &lt;em&gt;is&lt;/em&gt; the kernel. Containers are standard Linux processes isolated by kernel cgroups, namespaces, and seccomp profiles.&lt;/p&gt;

&lt;p&gt;Running a desktop container manager on Linux creates trade-offs every engineer should weigh before setting up a workstation.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Peculiar Architecture of Docker Desktop on Linux
&lt;/h2&gt;

&lt;p&gt;To understand why running desktop container suites on Linux feels counterintuitive, examine how Docker Desktop operates on the platform.&lt;/p&gt;

&lt;p&gt;When you install Docker Desktop on Linux, it does not manage your host kernel's container runtime directly. Instead, Docker Desktop spins up a dedicated virtual machine inside QEMU.&lt;/p&gt;

&lt;p&gt;Running a virtual machine on a native Linux host introduces distinct penalties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nested Virtualization Overhead:&lt;/strong&gt; You run Linux on bare metal, yet your container workloads execute inside a second, virtualized Linux kernel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artificial Resource Caps:&lt;/strong&gt; You must carve out CPU cores and RAM for the VM upfront, recreating the exact memory contention that Linux developers avoid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage and File I/O Latency:&lt;/strong&gt; Host volume mounts cross virtualized filesystem bridges instead of using direct VFS page cache bind-mounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commercial Licensing Exposure:&lt;/strong&gt; If your organization employs more than 250 people or generates over $10 million in annual revenue, Docker Desktop requires paid per-seat subscriptions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In our &lt;a href="https://mihai.ltd/blog/docker-desktop" rel="noopener noreferrer"&gt;Tech Radar evaluation of Docker Desktop&lt;/a&gt;, we formally rejected Docker Desktop because of update instability and commercial licensing overhead. On Linux, adding a QEMU virtualization layer on top of a native Linux kernel makes the proposition even harder to justify.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Where Rancher Desktop on Linux Makes Technical Sense
&lt;/h2&gt;

&lt;p&gt;Rancher Desktop takes a different path: fully open-source under the Apache 2.0 license, backed by SUSE, and listed under &lt;strong&gt;Adopt&lt;/strong&gt; on our &lt;a href="https://mihai.ltd/blog/rancher-desktop" rel="noopener noreferrer"&gt;Tech Radar&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On Linux, Rancher Desktop runs through QEMU or container isolation. While a bare-metal daemon delivers lower latency for standard web development, Rancher Desktop serves four distinct engineering use cases on Linux workstations:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Disposable, Multi-Version Kubernetes Sandboxes
&lt;/h3&gt;

&lt;p&gt;Testing Helm charts, operators, and Kubernetes manifests across diverse cluster versions is cumbersome when managing bare-metal k3s systemd services.&lt;/p&gt;

&lt;p&gt;Rancher Desktop lets you select exact Kubernetes releases (from v1.26 to v1.32) through a dropdown. When an experiment leaves behind broken CRDs or orphaned namespaces, clicking &lt;strong&gt;Reset Kubernetes&lt;/strong&gt; returns the cluster to a clean state in under 20 seconds without touching your host system packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fleet Parity Across Mixed-OS Engineering Teams
&lt;/h3&gt;

&lt;p&gt;In enterprise engineering organizations, developers rarely run identical operating systems. Half the team runs macOS, others run Windows with WSL2, and some run Linux.&lt;/p&gt;

&lt;p&gt;Standardizing on Rancher Desktop allows platform teams to distribute identical configuration profiles (&lt;code&gt;rancher-desktop.json&lt;/code&gt;). Every engineer gets the same container engine defaults (&lt;code&gt;dockerd&lt;/code&gt; or &lt;code&gt;containerd&lt;/code&gt;), the same bundled utilities (Helm 4.2, Trivy 0.72, Docker CLI 29.6), and identical Traefik ingress routing, regardless of host OS.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Isolated Sandboxing for Untrusted Containers
&lt;/h3&gt;

&lt;p&gt;If you audit external container images or test third-party tools, running them directly against your host Docker socket exposes your root system to container escapes.&lt;/p&gt;

&lt;p&gt;Rancher Desktop contains workloads inside an isolated runtime boundary, shielding your primary Linux host from disk exhaustion, network sniffing, and permission exploits.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Headless 2.0 Architecture
&lt;/h3&gt;

&lt;p&gt;As highlighted in the recent Rancher Desktop 2.0 Alpha 1 release notes, the project is decoupling from Electron. Version 2.0 introduces a headless mode that allows engineers to run container management and K3s clusters as a background service. On Linux, this brings declarative, scriptable container orchestration to headless development servers and CI runners without desktop UI overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. My Daily Driver Container Setup on CachyOS
&lt;/h2&gt;

&lt;p&gt;When developing services locally on CachyOS, I target execution speed, direct disk throughput, and minimal background resource drain.&lt;/p&gt;

&lt;p&gt;Here is the exact container stack running on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ CachyOS Bare-Metal Workstation ]
  │
  ├── Kernel: Linux CachyOS (BORE Scheduler + x86-64-v3 packages)
  ├── Filesystem: Btrfs with zstd:1 compression (Direct NVMe I/O)
  │
  ├── Native Container Runtime:
  │     ├── Engine: docker-ce (systemd service)
  │     ├── Socket: /var/run/docker.sock (User Group: docker)
  │     └── CLI Tools: docker, docker compose, nerdctl
  │
  ├── Terminal UI:
  │     └── lazydocker (Instant container, log, and volume monitoring)
  │
  └── Local Kubernetes:
        ├── Fast Labs: k3d / kind (Ephemeral clusters on Docker)
        └── Production Parity: Rancher Desktop (Isolated K3s with Traefik)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Installing Native Docker Engine on Arch / CachyOS
&lt;/h3&gt;

&lt;p&gt;Instead of wrapping the engine in an Electron application, install the native Docker packages from the official repositories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; docker docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the systemd service and socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; docker.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your user to the &lt;code&gt;docker&lt;/code&gt; group to drop &lt;code&gt;sudo&lt;/code&gt; requirements for daily commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log out and back in, or run &lt;code&gt;newgrp docker&lt;/code&gt; to apply the group membership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Filesystem Integration with Btrfs
&lt;/h3&gt;

&lt;p&gt;CachyOS defaults to Btrfs, which integrates cleanly with Docker's &lt;code&gt;btrfs&lt;/code&gt; storage driver. Docker leverages native subvolumes and copy-on-write (CoW) snapshots for container layers, making image pulls and build cache lookups immediate while conserving NVMe write cycles.&lt;/p&gt;

&lt;p&gt;Check your active storage driver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker info | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Storage Driver"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it shows &lt;code&gt;btrfs&lt;/code&gt; or &lt;code&gt;overlay2&lt;/code&gt;, your setup uses native Linux filesystem snapshots directly on disk without virtual disk translation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Installing Native K3s and Running at Startup
&lt;/h3&gt;

&lt;p&gt;For local Kubernetes, you do not need to wrap clusters inside an Electron app. Install native K3s directly via the official script:&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;-sfL&lt;/span&gt; https://get.k3s.io | sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer configures &lt;code&gt;/usr/local/bin/k3s&lt;/code&gt; and registers a systemd unit at &lt;code&gt;/etc/systemd/system/k3s.service&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To ensure the cluster starts automatically with your workstation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;k3s.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To configure &lt;code&gt;kubectl&lt;/code&gt; access for your current user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.kube
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/rancher/k3s/k3s.yaml ~/.kube/config
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; ~/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Real-World Resource Consumption: Why Idle K3s Stays On
&lt;/h3&gt;

&lt;p&gt;A common concern with running Kubernetes locally is memory drain. Here are the measured numbers from my active CachyOS workstation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline Idle Footprint:&lt;/strong&gt; Running bare K3s (control plane, CoreDNS, metrics-server, Traefik ingress, and local-path-provisioner) consumes &lt;strong&gt;500 MB to 700 MB of RAM&lt;/strong&gt; and negligible CPU (&amp;lt;1%).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Platform Lab Footprint:&lt;/strong&gt; On my machine, K3s runs over 60 active pods: Argo CD, Argo Workflows, CloudNativePG, Gitea, Grafana Alloy/Loki/Mimir, KEDA, n8n in queue mode, SeaweedFS, and Zitadel. Even under this workload, the entire &lt;code&gt;k3s.service&lt;/code&gt; cgroup sits at &lt;strong&gt;3.3 GB of RAM&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a 60 GB RAM workstation paired with CachyOS's BORE scheduler, 3.3 GB represents roughly 5.5% of total memory. Because memory pressure remains near zero, stopping and starting the cluster between coding sessions offers no tangible performance gain. Leaving it idling in the background is the most practical choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Bringing Down and Bringing Back Up K3s Without Losing Volumes
&lt;/h3&gt;

&lt;p&gt;When you do need to shut down the cluster—such as before major kernel upgrades or offline backups—you must ensure stateful databases (PostgreSQL, SeaweedFS, Gitea) do not suffer corrupted data or broken volume attachments.&lt;/p&gt;

&lt;h4&gt;
  
  
  Where Data Actually Lives
&lt;/h4&gt;

&lt;p&gt;K3s uses the embedded Rancher &lt;strong&gt;local-path-provisioner&lt;/strong&gt;. When a pod requests a PersistentVolumeClaim (PVC), the provisioner creates a host directory at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/rancher/k3s/storage/pvc-&amp;lt;volume-uuid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stopping K3s &lt;strong&gt;never deletes these directories&lt;/strong&gt;. The storage stays intact on disk until you explicitly run &lt;code&gt;kubectl delete pvc &amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Safe Shutdown Procedure
&lt;/h4&gt;

&lt;p&gt;To shut down cleanly without leaving orphaned network interfaces or corrupting database write-ahead logs (WAL):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Graceful Service Stop:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop k3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Systemd issues a &lt;code&gt;SIGTERM&lt;/code&gt; signal, allowing K3s and containerd shims time to flush file writes and stop containers cleanly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Clean Reset Fallback (If Processes or CNI Hang):&lt;/strong&gt;
If a container hangs or CNI network interfaces (&lt;code&gt;cni0&lt;/code&gt;, &lt;code&gt;flannel.1&lt;/code&gt;) fail to detach during an abrupt restart, run K3s's built-in cleanup script:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/local/bin/k3s-killall.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script kills leftover containerd shims and deletes stale CNI bridge interfaces, but &lt;strong&gt;does not touch &lt;code&gt;/var/lib/rancher/k3s/storage&lt;/code&gt;&lt;/strong&gt;. Your databases and persistent data remain safe.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bringing the Cluster Back Up
&lt;/h4&gt;

&lt;p&gt;To restore the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start k3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that all workloads and volumes recover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k3s kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
k3s kubectl get pvc &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kubelet restarts, inspects existing volume definitions, re-binds the local storage paths, and brings every application back online without data loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Terminal-First Observability with &lt;code&gt;lazydocker&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For standalone containers running outside K3s, I avoid heavy Electron wrappers and inspect running containers, stream logs, and prune stale images through &lt;code&gt;lazydocker&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="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; lazydocker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launching &lt;code&gt;lazydocker&lt;/code&gt; inside a terminal window consumes under 30 MB of RAM while providing real-time CPU and memory graphs, top processes, and interactive shell attach capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Comparing the Architectural Options on Linux
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Native Linux Docker / containerd&lt;/th&gt;
&lt;th&gt;Rancher Desktop on Linux&lt;/th&gt;
&lt;th&gt;Docker Desktop on Linux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Virtualization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None (Direct host kernel)&lt;/td&gt;
&lt;td&gt;Isolated VM / sandbox&lt;/td&gt;
&lt;td&gt;QEMU virtual machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File I/O Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native NVMe disk speed&lt;/td&gt;
&lt;td&gt;Virtualized bridge latency&lt;/td&gt;
&lt;td&gt;9P bridge latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM Footprint (Idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~60 MB&lt;/td&gt;
&lt;td&gt;~1.5 GB (with K3s)&lt;/td&gt;
&lt;td&gt;~2.5 GB (with VM &amp;amp; UI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kubernetes Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires k3d, kind, or bare k3s&lt;/td&gt;
&lt;td&gt;Built-in K3s with version selector&lt;/td&gt;
&lt;td&gt;Built-in single-node Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Licensing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% Free &amp;amp; Open Source&lt;/td&gt;
&lt;td&gt;100% Free &amp;amp; Open Source (Apache 2.0)&lt;/td&gt;
&lt;td&gt;Paid subscription required for large orgs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;docker compose&lt;/code&gt;, &lt;code&gt;nerdctl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rdctl&lt;/code&gt;, &lt;code&gt;nerdctl&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;docker compose&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Daily development, peak performance&lt;/td&gt;
&lt;td&gt;Multi-version K8s testing, mixed-OS teams&lt;/td&gt;
&lt;td&gt;Legacy enterprise compliance only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Which Path Should You Choose?
&lt;/h2&gt;

&lt;p&gt;Your choice depends on team structure and daily workflow requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose Native Linux Docker / containerd&lt;/strong&gt; if your priority is development velocity, battery life, and raw performance on an Arch, CachyOS, Fedora, or Ubuntu desktop. Your builds run directly against the Linux page cache, and &lt;code&gt;docker compose up&lt;/code&gt; starts in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose Rancher Desktop&lt;/strong&gt; if you maintain complex Kubernetes workloads, frequently test upgrades across Kubernetes versions, or need identical operational workflows across a distributed team running Windows, macOS, and Linux.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Docker Desktop on Linux&lt;/strong&gt; unless your organization enforces it through mandatory enterprise device policies. Running a QEMU virtual machine on top of a Linux kernel reintroduces performance bottlenecks that the operating system natively eliminates.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Related Guides &amp;amp; Architectural Reviews
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows Setup Guide:&lt;/strong&gt; &lt;a href="https://mihai.ltd/blog/rancher-desktop-windows-guide" rel="noopener noreferrer"&gt;Rancher Desktop on Windows: The Complete Zero-License Replacement for Docker Desktop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Radar Adoption Report:&lt;/strong&gt; &lt;a href="https://mihai.ltd/blog/rancher-desktop" rel="noopener noreferrer"&gt;Rancher Desktop (Adopt)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Radar Reject Report:&lt;/strong&gt; &lt;a href="https://mihai.ltd/blog/docker-desktop" rel="noopener noreferrer"&gt;Docker Desktop (Reject)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes Storage Deep Dive:&lt;/strong&gt; &lt;a href="https://mihai.ltd/blog/mount-local-storage-to-kubernetes-pods-k3s" rel="noopener noreferrer"&gt;Mount Local Storage to Kubernetes Pods with K3s&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article first appeared on &lt;a href="https://mihai.ltd/blog/rancher-desktop-docker-desktop-on-linux" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;. Read the original for the full version.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software architect with over a decade of experience building and scaling production systems. I write about Kubernetes, n8n workflow automation, AI agents and local LLM infrastructure, and I share the good results and the face-palm moments alike.&lt;/p&gt;

&lt;p&gt;On my YouTube channel, &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;Let's Talk Dev&lt;/a&gt;, I break down complex engineering concepts and the experiments behind these articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website and blog: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;@letstalkdev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;mihailtd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;Mihai Farcas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>cachyos</category>
      <category>archlinux</category>
      <category>docker</category>
    </item>
    <item>
      <title>n8n + Argo = ❤️: Offloading CPU-Heavy Steps to Argo Workflows</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:28:54 +0000</pubDate>
      <link>https://dev.to/mihailtd/n8n-argo-offloading-cpu-heavy-steps-to-argo-workflows-2h5b</link>
      <guid>https://dev.to/mihailtd/n8n-argo-offloading-cpu-heavy-steps-to-argo-workflows-2h5b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mihai.ltd/blog/n8n-argo-workflows-offload" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Hand heavy steps from n8n to Argo Workflows, release the worker slot, and let a pod do the compute.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://n8n.partnerlinks.io/ltd" rel="noopener noreferrer"&gt;n8n&lt;/a&gt; and Argo Workflows each do a different job well, and together they cover each other's gaps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;n8n&lt;/strong&gt; handles event-driven orchestration: it connects hundreds of SaaS APIs, listens to webhooks, transforms JSON payloads, and manages human-in-the-loop approvals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Argo Workflows&lt;/strong&gt; runs heavy, isolated container jobs on Kubernetes: batch jobs, video transcoding, and ML pipelines, each in its own pod.&lt;/p&gt;

&lt;p&gt;A CPU-heavy step does not belong in an n8n worker's Node.js event loop. Let n8n hand the job to Argo, release its worker slot with a Wait node, and resume when the Argo pod calls back.&lt;/p&gt;

&lt;p&gt;In my k3s lab, ten concurrent 30-CPU-second jobs finished with a median execution time of 81 seconds. The n8n workers peaked at 0.07 CPU cores, and a light neighbor workflow kept its baseline queue wait. This article covers the failure that motivates the pattern, the pattern itself, and the lab data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the lab run?
&lt;/h2&gt;

&lt;p&gt;The cluster is the one from the KEDA article: single-node k3s, n8n 2.40.5 in queue mode (main, two webhook processors, KEDA-scaled workers with a concurrency of 10 and a CPU limit of 1 core each), PostgreSQL on CloudNativePG, Valkey as the queue, all deployed by Argo CD. I added &lt;a href="https://argo-workflows.readthedocs.io/" rel="noopener noreferrer"&gt;Argo Workflows&lt;/a&gt; 4.1.4 in a single namespace.&lt;/p&gt;

&lt;p&gt;The test job is a synchronous JavaScript loop: xorshift over a 256 MiB typed array, calibrated so one unit costs about one CPU-second on this machine. Both variants run the identical function, so the only difference is where it runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inline.&lt;/strong&gt; A webhook triggers a workflow with one Code node that calls &lt;code&gt;heavy(units)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offloaded.&lt;/strong&gt; A webhook triggers a workflow that submits an Argo Workflow running the same function in a &lt;code&gt;node:26-alpine&lt;/code&gt; pod with 1 CPU and 512 MiB, then waits for the pod to call back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://k6.io/" rel="noopener noreferrer"&gt;k6&lt;/a&gt; fires 10 jobs at once. At the same time it runs a light neighbor workflow at one execution per second: a 6-second HTTP call, the shape of a typical SaaS sync. That neighbor measures what the heavy jobs do to unrelated work. Timings come from n8n's &lt;code&gt;execution_entity&lt;/code&gt; table (queue wait is &lt;code&gt;startedAt&lt;/code&gt; minus &lt;code&gt;createdAt&lt;/code&gt;) and worker CPU from the metrics API.&lt;/p&gt;

&lt;p&gt;Each scenario ran once with 10 jobs, so read the numbers as shapes, not benchmarks. The machine is a 12-core, 24-thread workstation that also runs the rest of the lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks when a Code node does heavy work?
&lt;/h2&gt;

&lt;p&gt;At 30 CPU-seconds per job, 9 of the 10 executions failed with &lt;code&gt;Task execution aborted because runner became unresponsive&lt;/code&gt;. The one that succeeded finished in 29 seconds. At 10 CPU-seconds per job the result was the same: 1 of 10. At 5 CPU-seconds per job all 10 passed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Inline, default runner settings&lt;/th&gt;
&lt;th&gt;Executions ok&lt;/th&gt;
&lt;th&gt;Median execution time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5 CPU-seconds per job&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;44 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 CPU-seconds per job&lt;/td&gt;
&lt;td&gt;1/10&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 CPU-seconds per job&lt;/td&gt;
&lt;td&gt;1/10&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The cause sits in how n8n runs Code nodes. With the default &lt;code&gt;N8N_RUNNERS_MODE=internal&lt;/code&gt;, the worker launches a task runner as a child process, and &lt;code&gt;ps&lt;/code&gt; in the worker pod shows it as a second Node process next to &lt;code&gt;n8n worker&lt;/code&gt;. The runner has one event loop. When the task broker offers the runner a task, the runner has to acknowledge it within &lt;code&gt;N8N_RUNNERS_TASK_ACCEPT_TIMEOUT&lt;/code&gt;, which defaults to 2 seconds in n8n 2.40.5. A runner stuck in a synchronous loop cannot acknowledge anything. After three consecutive missed acknowledgements the broker declares the runner unresponsive and restarts it, and every task running on it aborts. The worker log shows the sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runner (4w6aiSeMoixrVB-hWUW7J) took too long to acknowledge acceptance of task (hC9eTMmw)
Task runner failed heartbeat check, restarting...
Task execution aborted because runner became unresponsive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default &lt;code&gt;N8N_RUNNERS_MAX_CONCURRENCY&lt;/code&gt; is 5 tasks, but one thread cannot run a synchronous loop for a second task until the first one returns, so concurrency inside the runner is nominal for CPU-bound code. At 5 CPU-seconds per job the runner survived and the ten jobs finished in sequence, in about 50 seconds. I did not bisect the failure threshold between 5 and 10 CPU-seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you tune your way out?
&lt;/h2&gt;

&lt;p&gt;Partly. I raised the four runner limits on the workers (&lt;code&gt;N8N_RUNNERS_TASK_ACCEPT_TIMEOUT&lt;/code&gt;, &lt;code&gt;N8N_RUNNERS_HEARTBEAT_INTERVAL&lt;/code&gt;, &lt;code&gt;N8N_RUNNERS_TASK_TIMEOUT&lt;/code&gt; and &lt;code&gt;N8N_RUNNERS_TASK_REQUEST_TIMEOUT&lt;/code&gt;, all to 900 seconds) and set &lt;code&gt;N8N_RUNNERS_MAX_CONCURRENCY=10&lt;/code&gt;. All ten 30-second jobs then succeeded, with a median execution time of 305 seconds.&lt;/p&gt;

&lt;p&gt;That number is the problem. The runner processes one synchronous task at a time, so ten 30-second jobs take about 300 seconds on a worker, and the others sit inside the runner while the worker shows free slots. KEDA saw almost no backlog. Its Redis trigger watches the waiting list, and the tasks queued inside the runner never appear there, so the Deployment peaked at 2 workers. Adding CPU to the pod does not help either, because a single-threaded runner cannot use more than one core.&lt;/p&gt;

&lt;p&gt;Tuning turns failures into a serial queue. It also removes the protection the limits provide: a task that hangs now holds a runner for 15 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the offload work?
&lt;/h2&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%2Fivi46kxzo6po0co0v6di.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%2Fivi46kxzo6po0co0v6di.png" alt="Architecture: n8n submits a job to Argo Workflows and resumes when the pod calls back" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The offloaded workflow has three nodes. The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Submit.&lt;/strong&gt; An HTTP Request node posts to the Argo server's submit endpoint, naming a &lt;code&gt;WorkflowTemplate&lt;/code&gt; and passing the job parameters plus the execution's resume URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait.&lt;/strong&gt; A Wait node set to resume on a webhook call saves the execution and releases the worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Callback.&lt;/strong&gt; When the job ends, an exit handler in the Argo Workflow posts the status to the resume URL, and n8n continues the execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The submit call is a plain REST request. The body names the template and passes &lt;code&gt;$execution.resumeUrl&lt;/code&gt;, the unique URL n8n generates for the Wait node of that execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST http://argo-workflows-server.argo-workflows.svc:2746/api/v1/workflows/argo-workflows/submit

={{ JSON.stringify({
  resourceKind: "WorkflowTemplate",
  resourceName: "heavy-job",
  submitOptions: { parameters: [
    "job-id=" + $execution.id,
    "units=" + ($("Webhook").first().json.body.units || 30),
    "callback-url=" + $execution.resumeUrl
  ] }
}) }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template (trimmed here) runs the job in its own pod with its own CPU and memory, and calls back from an &lt;code&gt;onExit&lt;/code&gt; handler so a failed job resumes n8n as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WorkflowTemplate&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;heavy-job&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo-workflow&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;heavy&lt;/span&gt;
  &lt;span class="na"&gt;onExit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;callback&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;parameters&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;job-id&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;units&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30"&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;callback-url&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;templates&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;heavy&lt;/span&gt;
      &lt;span class="na"&gt;script&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;node:26-alpine&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;// the same heavy(units) function as the Code node&lt;/span&gt;
          &lt;span class="s"&gt;console.log(heavy({{workflow.parameters.units}}));&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;384Mi&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;512Mi&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;callback&lt;/span&gt;
      &lt;span class="na"&gt;container&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;node:26-alpine&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;-e&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;fetch(process.argv[1], {&lt;/span&gt;
              &lt;span class="s"&gt;method: 'POST',&lt;/span&gt;
              &lt;span class="s"&gt;headers: {'Content-Type': 'application/json'},&lt;/span&gt;
              &lt;span class="s"&gt;body: JSON.stringify({jobId: process.argv[2], status: process.argv[3]}),&lt;/span&gt;
            &lt;span class="s"&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;{{workflow.parameters.callback-url}}"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{workflow.parameters.job-id}}"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{workflow.status}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Wait node releases the worker immediately. In the lab, the offloaded execution's &lt;code&gt;stoppedAt&lt;/code&gt; was 0.4 seconds after it started, and its status in PostgreSQL was &lt;code&gt;waiting&lt;/code&gt; until the callback arrived. The n8n documentation describes the 65-second persistence threshold for time-based waits; I observed that a webhook-resumed Wait persists the execution at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does n8n authenticate to Argo?
&lt;/h3&gt;

&lt;p&gt;The lab gives n8n its own ServiceAccount, &lt;code&gt;n8n-submitter&lt;/code&gt;, with a long-lived token and a Role that allows only what the submit call needs:&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;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&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;argoproj.io"&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="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflowtemplates"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&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;get"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&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;argoproj.io"&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="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflows"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&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;create"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Argo server runs with &lt;code&gt;--auth-mode=client&lt;/code&gt; and &lt;code&gt;--auth-mode=server&lt;/code&gt;. A request that carries a bearer token acts as that ServiceAccount, so n8n gets exactly the Role above, and a request with an invalid token gets a 401. In n8n the token lives in a Header Auth credential (&lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;), which the bootstrap job creates from the Secret.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;server&lt;/code&gt; mode is the catch. A request with no token acts as the Argo server's own ServiceAccount, which is how the UI works behind my SSO proxy. I checked from a pod in the cluster: a call without a token returned 200 and the workflow list. Client mode alone does not close that, because it only constrains callers that identify themselves. The lab restricts who can reach the server instead, with a NetworkPolicy that admits Traefik (the UI path) and the n8n namespace and nothing else. A pod in &lt;code&gt;default&lt;/code&gt; and a pod in the Argo namespace both fail to connect, and n8n still submits. Anything in the n8n namespace can still call without a token, so the policy narrows the exposure and does not remove it. In production, drop the &lt;code&gt;server&lt;/code&gt; mode and put the UI behind Argo's SSO mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did offloading change?
&lt;/h2&gt;

&lt;p&gt;All ten jobs succeeded at every size. Offloading kept the workers nearly idle and left the neighbor workflow's queue wait at baseline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Job (CPU-s)&lt;/th&gt;
&lt;th&gt;Executions ok&lt;/th&gt;
&lt;th&gt;Median execution time&lt;/th&gt;
&lt;th&gt;Neighbor queue wait p95 / max&lt;/th&gt;
&lt;th&gt;Peak workers&lt;/th&gt;
&lt;th&gt;Peak worker CPU (cores)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline (neighbor only)&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;0.01 s / 0.10 s&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;44 s&lt;/td&gt;
&lt;td&gt;4.18 s / 13.8 s&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1/10&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;2.63 s / 9.9 s&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;1/10&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;0.03 s / 18.3 s&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1.58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline, runner limits raised&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;305 s&lt;/td&gt;
&lt;td&gt;0.08 s / 9.2 s&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offloaded&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;25 s&lt;/td&gt;
&lt;td&gt;0.01 s / 0.1 s&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offloaded&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;35 s&lt;/td&gt;
&lt;td&gt;0.02 s / 0.04 s&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offloaded&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;81 s&lt;/td&gt;
&lt;td&gt;0.01 s / 0.1 s&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F2fa03w6msf1my0dmlkzq.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%2F2fa03w6msf1my0dmlkzq.png" alt="Median n8n execution time for ten jobs, inline versus offloaded, at three job sizes" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three effects stand out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workers stay idle.&lt;/strong&gt; While ten 30-second jobs ran, the offloaded workers peaked at 0.07 cores in total, against 0.86 to 1.58 inline. n8n's own &lt;code&gt;n8n_scaling_mode_queue_jobs_active&lt;/code&gt; gauge peaked at 6 during the offloaded run, which matches the neighbor workflow's six in-flight executions. The ten heavy jobs held no slots. During the inline runs the same gauge reached 15 to 30.&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%2Fmctpp48o653dbqrf3x2h.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%2Fmctpp48o653dbqrf3x2h.png" alt="Worker CPU while ten 30-second jobs run, inline versus offloaded, with the Argo job pods running" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The neighbor keeps its latency.&lt;/strong&gt; The light workflow waited a median of 0.01 seconds in the queue at baseline. With 5-second inline jobs its 95th-percentile wait rose to 4.18 seconds, and with 30-second inline jobs the worst wait reached 18.3 seconds. With offloaded jobs it stayed at the baseline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KEDA has nothing to react to.&lt;/strong&gt; The offloaded runs never scaled the workers past 1. The workers do not need more replicas, because the work no longer runs in them. The cluster scheduler absorbs it instead, which is the pod-per-step model I contrasted with n8n's queue workers in the KEDA article.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does offloading cost?
&lt;/h2&gt;

&lt;p&gt;A fixed delay on every job, and capacity that you must supply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fixed delay.&lt;/strong&gt; I ran single jobs one at a time with no other load. A 1-CPU-second job took 1.0 seconds end to end inline and 11.7 seconds offloaded. A 5-CPU-second job took 4.9 seconds inline and 11.7 seconds offloaded. The Argo pod's main container ran for 2 to 6 seconds of that; I did not trace where the rest goes, so I report the total as measured. For a short job, offloading is slower.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The capacity.&lt;/strong&gt; Argo gives you the cluster scheduler, not extra cores. I submitted N identical 30-second jobs directly to Argo, with no n8n involved, and measured the main container's run time per job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parallel jobs&lt;/th&gt;
&lt;th&gt;Median run time per job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;27 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;43 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;60 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;112 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The lab node has 12 physical cores and the job walks a 256 MiB array, so I expect shared cores and memory bandwidth to slow concurrent jobs well before the 24 threads run out. I did not isolate the cause. On a multi-node cluster with a node autoscaler, those jobs spread out. On one node, offloading moves the contention from a worker's single core to the node's cores, which is still an improvement because the worker's capacity no longer limits it, but the parallelism is not free.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should a step leave n8n?
&lt;/h2&gt;

&lt;p&gt;This is my reading of the numbers from one lab, not a general rule.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under about 2 CPU-seconds, stay inline.&lt;/strong&gt; The offload adds about 12 seconds of fixed time, which dwarfs the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A few seconds to about 10, decide by concurrency.&lt;/strong&gt; A single 5-second job is faster inline. Ten of them at once finished in 25 seconds offloaded against 44 inline, and left the neighbor workflow untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At 10 CPU-seconds or more, offload.&lt;/strong&gt; In this lab, the Code node on default settings did not survive ten of them at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offload anything that needs more CPU, memory, or tooling than a worker pod has.&lt;/strong&gt; The pod gets its own resource limits and any image you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave I/O-bound steps in n8n.&lt;/strong&gt; Waiting on an API costs the worker a slot but almost no CPU, and KEDA handles it, as the first article shows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things this lab did not test: cancelling an n8n execution while its Argo job runs (the job keeps running unless something deletes it), retries through Argo's &lt;code&gt;retryStrategy&lt;/code&gt;, the Wait node's time limit when a callback never arrives (the workflow sets 30 minutes), &lt;code&gt;workflowRestrictions&lt;/code&gt; to stop a token holder from submitting arbitrary templates, and a multi-node cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Code node runs in one single-threaded task runner per worker. A CPU-bound loop longer than a few seconds makes the runner miss task acknowledgements, and the broker restarts it and aborts every task on it.&lt;/li&gt;
&lt;li&gt;Raising the runner limits avoids the failures and serializes the work: ten 30-second jobs took 305 seconds on one worker.&lt;/li&gt;
&lt;li&gt;Offload with three pieces: an HTTP Request node that submits an Argo &lt;code&gt;WorkflowTemplate&lt;/code&gt;, a Wait node resumed by webhook, and an &lt;code&gt;onExit&lt;/code&gt; handler that posts the status to &lt;code&gt;$execution.resumeUrl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Give n8n its own Argo ServiceAccount with a Role limited to submitting from templates. Client auth mode only constrains callers that send a token, so close the &lt;code&gt;server&lt;/code&gt; mode fallback with a NetworkPolicy or SSO.&lt;/li&gt;
&lt;li&gt;Offloading costs about 12 seconds per job in this lab, so keep short jobs inline, and budget cluster capacity for the jobs you hand off.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I write on my blog independently. I'm also an official technical writer for n8n and a member of the n8n Creators program. I ran every experiment and benchmark in this article on my own single-node k3s lab cluster. If you want to try &lt;a href="https://n8n.partnerlinks.io/ltd" rel="noopener noreferrer"&gt;n8n Cloud&lt;/a&gt;, my referral link supports my personal research and lab benchmarks at no extra cost to you.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article first appeared on &lt;a href="https://mihai.ltd/blog/n8n-argo-workflows-offload" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;. Read the original for the full version.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software architect with over a decade of experience building and scaling production systems. I write about Kubernetes, n8n workflow automation, AI agents and local LLM infrastructure, and I share the good results and the face-palm moments alike.&lt;/p&gt;

&lt;p&gt;On my YouTube channel, &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;Let's Talk Dev&lt;/a&gt;, I break down complex engineering concepts and the experiments behind these articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website and blog: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;@letstalkdev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;mihailtd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;Mihai Farcas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>n8n</category>
      <category>argoworkflows</category>
      <category>workflowautomation</category>
    </item>
    <item>
      <title>Mount Local Storage to Kubernetes Pods: An Easy Tutorial Using K3s and the Local Path Provisioner</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Thu, 08 Oct 2026 17:17:21 +0000</pubDate>
      <link>https://dev.to/mihailtd/mount-local-storage-to-kubernetes-pods-an-easy-tutorial-using-k3s-and-the-local-path-provisioner-1052</link>
      <guid>https://dev.to/mihailtd/mount-local-storage-to-kubernetes-pods-an-easy-tutorial-using-k3s-and-the-local-path-provisioner-1052</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mihai.ltd/blog/mount-local-storage-to-kubernetes-pods-k3s" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Persistent storage remains one of the first hurdles engineers hit when deploying stateful applications to Kubernetes. While stateless services restart cleanly anywhere across a cluster, databases, message brokers, and ML workloads require durable disks that survive pod restarts and rescheduling.&lt;/p&gt;

&lt;p&gt;When developers first encounter storage on Kubernetes, they often find conflicting advice: enterprise blogs recommend multi-node distributed storage clusters like Ceph or Longhorn, while quickstart tutorials use raw &lt;code&gt;hostPath&lt;/code&gt; volumes. For single-node clusters, homelabs, and edge environments, both extremes cause problems.&lt;/p&gt;

&lt;p&gt;This guide clarifies how Kubernetes storage abstractions operate, examines how distributions handle local storage out of the box, and shows how Rancher's Local Path Provisioner enables automated, high-performance NVMe storage for your workloads.&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%2Fmt592uxxwpnkclj7wtp5.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%2Fmt592uxxwpnkclj7wtp5.png" alt="Storage Architecture Comparison" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Ephemeral Container Problem
&lt;/h2&gt;

&lt;p&gt;Kubernetes schedules Pods—groups of one or more co-located containers—onto worker nodes across a cluster. Containers provide lightweight, isolated process environments, but their file systems remain ephemeral by default. When a container crashes or restarts, Kubernetes recreates it from the base container image, discarding uncommitted runtime data.&lt;/p&gt;

&lt;p&gt;In traditional Docker environments on a single physical host, engineers solve persistence by mounting a host directory via bind mounts:&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;-v&lt;/span&gt; /var/data/postgres:/var/lib/postgresql/data postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a distributed Kubernetes cluster, direct host paths break down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling Blindness:&lt;/strong&gt; The Kubernetes scheduler places pods dynamically based on CPU, memory, and affinity rules. If Kubernetes terminates a pod on Node A and restarts it on Node B, a hardcoded path on Node A remains unreachable from the new node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Leaks:&lt;/strong&gt; When you delete a workload, Kubernetes deletes the pod, but the files on the host disk stay behind indefinitely. Without manual cleanup, dead workloads slowly fill host disks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Risks:&lt;/strong&gt; Hardcoding host filesystem paths in application manifests gives container processes direct access to node-level file systems, bypassing namespace isolation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve these problems without tying manifests to physical hardware, Kubernetes introduces three layers of storage abstraction: &lt;strong&gt;PersistentVolumes&lt;/strong&gt;, &lt;strong&gt;PersistentVolumeClaims&lt;/strong&gt;, and &lt;strong&gt;StorageClasses&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Kubernetes Storage Hierarchy
&lt;/h2&gt;

&lt;p&gt;Kubernetes separates storage management into distinct concerns for infrastructure administrators and application developers.&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%2F7eh734wmudnyxss511f4.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%2F7eh734wmudnyxss511f4.png" alt="Kubernetes Storage Pipeline" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. PersistentVolume (PV)
&lt;/h3&gt;

&lt;p&gt;A PersistentVolume represents an actual piece of storage in the cluster—such as an allocated NVMe directory on a worker node, an NFS export, or an AWS EBS volume. An administrator can provision PVs manually, or a storage driver can generate them dynamically. A PV defines capacity, access modes, and technical connection details.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. PersistentVolumeClaim (PVC)
&lt;/h3&gt;

&lt;p&gt;A PersistentVolumeClaim is an application developer's request for storage. Instead of specifying host paths or disk serial numbers, a developer specifies capacity (such as &lt;code&gt;10Gi&lt;/code&gt;), access modes, and an optional StorageClass name.&lt;/p&gt;

&lt;p&gt;When you submit a PVC, the Kubernetes control plane matches the request against available PVs. Once matched, it binds the PV to the PVC in a strict one-to-one relationship.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. StorageClass
&lt;/h3&gt;

&lt;p&gt;In production clusters, administrators do not manually create PersistentVolume manifests for every developer deployment. A &lt;strong&gt;StorageClass&lt;/strong&gt; defines a dynamic provisioner plugin and parameters. When a developer submits a PVC referencing that StorageClass, the provisioner creates the backing storage volume and registers the matching PV automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Homelab Storage Dilemma: Distributed SAN vs. Local Path
&lt;/h2&gt;

&lt;p&gt;When setting up storage for local clusters, engineers generally consider three architectures:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage Model&lt;/th&gt;
&lt;th&gt;Provisioning&lt;/th&gt;
&lt;th&gt;Cleanup on Delete&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Cluster Resource Overhead&lt;/th&gt;
&lt;th&gt;Ideal Workload&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw hostPath&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual YAML per volume&lt;/td&gt;
&lt;td&gt;None (leaks on host disk)&lt;/td&gt;
&lt;td&gt;Sub-millisecond&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Host node agents (Fluentbit, node-exporter)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local Path Provisioner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully automated via PVC&lt;/td&gt;
&lt;td&gt;Automated helper cleanup&lt;/td&gt;
&lt;td&gt;Sub-millisecond&lt;/td&gt;
&lt;td&gt;Negligible (ephemeral helper pods)&lt;/td&gt;
&lt;td&gt;Databases with app-level HA, single-node labs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed (Longhorn/Ceph)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully automated via PVC&lt;/td&gt;
&lt;td&gt;Fully automated&lt;/td&gt;
&lt;td&gt;Network sync delay&lt;/td&gt;
&lt;td&gt;Heavy (15–30% CPU, gigabytes RAM)&lt;/td&gt;
&lt;td&gt;Multi-node clusters requiring block failover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud CSI (EBS/PD)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully automated via API&lt;/td&gt;
&lt;td&gt;Fully automated&lt;/td&gt;
&lt;td&gt;Network attached&lt;/td&gt;
&lt;td&gt;Offloaded to cloud hypervisor&lt;/td&gt;
&lt;td&gt;Public cloud managed Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In enterprise data centers with 10GbE or 40GbE network fabrics, distributed storage systems like Rook-Ceph or Longhorn offer automatic replication and seamless volume failover between nodes. &lt;/p&gt;

&lt;p&gt;In single-node homelabs, edge nodes, and developer workstations, running a distributed storage layer wastes significant resources. Systems like Longhorn replicate data across the network across the network, consuming 15% to 30% of cluster CPU capacity and gigabytes of RAM.&lt;/p&gt;

&lt;p&gt;For high-throughput databases (such as PostgreSQL, Valkey, and ChromaDB), direct host NVMe storage provides the lowest latency and highest IOPS. Rancher's &lt;strong&gt;Local Path Provisioner&lt;/strong&gt; delivers that native performance while maintaining standard Kubernetes PVC workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Local Path Provisioner Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;The Local Path Provisioner runs as a lightweight controller pod inside the cluster. It watches for PVCs that request its StorageClass and manages host directories through a dedicated lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lifecycle Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PVC Submission:&lt;/strong&gt; A developer applies a PVC manifest specifying &lt;code&gt;storageClassName: local-path&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler Placement:&lt;/strong&gt; Because the storage class uses &lt;code&gt;volumeBindingMode: WaitForFirstConsumer&lt;/code&gt;, the claim stays in &lt;code&gt;Pending&lt;/code&gt; until a pod references it. Once you deploy the pod, the Kubernetes scheduler assigns the pod to an available worker node based on CPU and memory limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helper Pod Execution:&lt;/strong&gt; The Local Path controller detects the target node and launches a temporary helper pod (&lt;code&gt;rancher/library-busybox&lt;/code&gt;) on that specific machine. The helper pod executes:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0777 &lt;span class="nt"&gt;-p&lt;/span&gt; /var/lib/rancher/k3s/storage/pvc-&amp;lt;uuid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PV Generation and Binding:&lt;/strong&gt; The provisioner registers a new PersistentVolume with &lt;code&gt;nodeAffinity&lt;/code&gt; targeting the chosen node and binds it to the PVC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct Kubelet Bind-Mount:&lt;/strong&gt; The node's kubelet bind-mounts the newly initialized host directory directly into the pod container at the target path (such as &lt;code&gt;/data&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Teardown:&lt;/strong&gt; When you delete the PVC, the provisioner observes the &lt;code&gt;reclaimPolicy: Delete&lt;/code&gt; rule, launches a cleanup helper pod on the node, and runs &lt;code&gt;rm -rf&lt;/code&gt; on the directory, ensuring no orphaned data remains.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;WaitForFirstConsumer&lt;/code&gt; Is Non-Negotiable
&lt;/h3&gt;

&lt;p&gt;Standard cloud block storage often uses &lt;code&gt;volumeBindingMode: Immediate&lt;/code&gt;, which allocates a disk before assigning a pod to a node. &lt;/p&gt;

&lt;p&gt;For local node storage, immediate binding causes scheduling failures. If the controller allocates a directory on Node A before the scheduler runs, and the pod subsequently requires a GPU or memory capacity available only on Node B, the pod will remain unschedulable indefinitely. &lt;code&gt;WaitForFirstConsumer&lt;/code&gt; delays volume creation until the scheduler selects the target node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Distribution Support: Does Your Cluster Include It?
&lt;/h2&gt;

&lt;p&gt;Support for local storage provisioners varies across Kubernetes distributions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;K3s:&lt;/strong&gt; &lt;strong&gt;Included out of the box.&lt;/strong&gt; K3s deploys Rancher's Local Path Provisioner by default in the &lt;code&gt;kube-system&lt;/code&gt; namespace and sets &lt;code&gt;local-path&lt;/code&gt; as the cluster's default StorageClass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kind (Kubernetes in Docker):&lt;/strong&gt; &lt;strong&gt;Included out of the box.&lt;/strong&gt; Kind bundles Rancher's local-path provisioner under the &lt;code&gt;standard&lt;/code&gt; storage class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minikube:&lt;/strong&gt; Includes its own default &lt;code&gt;storage-provisioner&lt;/code&gt; addon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MicroK8s:&lt;/strong&gt; Requires running &lt;code&gt;microk8s enable hostpath-storage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla Kubernetes (kubeadm, Talos, RKE2, Bare-Metal):&lt;/strong&gt; &lt;strong&gt;Not included.&lt;/strong&gt; Fresh bare-metal clusters do not include a default StorageClass. Running &lt;code&gt;kubectl get sc&lt;/code&gt; returns an empty list, and any deployed PVC hangs in &lt;code&gt;Pending&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installing Local Path Provisioner on Any Cluster
&lt;/h3&gt;

&lt;p&gt;If you run bare-metal Kubernetes via &lt;code&gt;kubeadm&lt;/code&gt;, Talos, or RKE2, you can install the Rancher Local Path Provisioner with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To configure it as your cluster's default storage class, apply the standard default annotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch storageclass local-path &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the provisioner pod is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; local-path-storage get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Hands-On Walkthrough: Deploying a Stateful Pod
&lt;/h2&gt;

&lt;p&gt;Let us deploy a stateful test application to examine volume creation, file persistence, and cleanup in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a PersistentVolumeClaim
&lt;/h3&gt;

&lt;p&gt;Create a manifest named &lt;code&gt;pvc.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-data-pvc&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local-path&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the manifest and inspect its status:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Notice that the claim status remains &lt;code&gt;Pending&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME           STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
app-data-pvc   Pending                                      local-path     5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;local-path&lt;/code&gt; uses &lt;code&gt;WaitForFirstConsumer&lt;/code&gt;, Kubernetes intentionally waits until a pod requests this volume before choosing a node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Deploy the Stateful Pod
&lt;/h3&gt;

&lt;p&gt;Create a pod manifest named &lt;code&gt;pod.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage-demo-pod&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;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;alpine-app&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;alpine:latest&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'Data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;persisted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(date)'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/data/log.txt&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage-volume&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;storage-volume&lt;/span&gt;
      &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-data-pvc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the pod:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Check the PVC again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc,pv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The volume immediately transitions to &lt;code&gt;Bound&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/app-data-pvc   Bound    pvc-8e2b851a-7b3c-4b68-b80c-0744be958210   5Gi        RWO            local-path     30s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS   AGE
persistentvolume/pvc-8e2b851a-7b3c-4b68-b80c-0744be958210   5Gi        RWO            Delete           Bound    default/app-data-pvc   local-path     5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Verify Persistence Across Restarts
&lt;/h3&gt;

&lt;p&gt;Verify that the pod wrote data to the mounted volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;storage-demo-pod &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /data/log.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now delete and recreate the pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod storage-demo-pod
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the log contents again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;storage-demo-pod &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /data/log.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see both timestamp entries preserved across the pod lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Verify Host Directory Cleanup
&lt;/h3&gt;

&lt;p&gt;On the physical host running the node, inspect the local path storage directory (defaulting to &lt;code&gt;/var/lib/rancher/k3s/storage&lt;/code&gt; on K3s, or &lt;code&gt;/opt/local-path-provisioner&lt;/code&gt; on standard installs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/lib/rancher/k3s/storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will find the directory matching the generated PV volume name.&lt;/p&gt;

&lt;p&gt;When you delete the PVC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod storage-demo-pod
kubectl delete pvc app-data-pvc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provisioner launches its cleanup helper and removes the folder from the host filesystem, preventing disk accumulation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operational Caveats &amp;amp; Architectural Boundaries
&lt;/h2&gt;

&lt;p&gt;While local path storage delivers exceptional speed, engineers must weigh its architectural constraints before choosing it for multi-node production clusters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No Node-Level Redundancy:&lt;/strong&gt; Local storage pins data to a specific physical machine. If the underlying hardware fails, Kubernetes cannot remount that data on another node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replication Strategy:&lt;/strong&gt; Use local path storage for workloads that manage data replication at the application layer—such as PostgreSQL with CloudNativePG or patroni streaming replicas, Redis/Valkey clusters, and Kafka brokers. For workloads lacking internal replication, combine local storage with automated cloud snapshot backups (such as Barman S3 backups or Velero).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Filesystem Quota Enforcement:&lt;/strong&gt; By default, the &lt;code&gt;storage: 5Gi&lt;/code&gt; request in your PVC acts solely as a scheduling filter. The Local Path Provisioner does not enforce Linux filesystem quotas unless your host partition is explicitly formatted and mounted with ext4 or xfs project quota support. A runaway container can write past its claimed capacity and fill the host partition.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pod filesystems are ephemeral by default; persistent storage requires decoupling workload lifecycles from container lifecycles.&lt;/li&gt;
&lt;li&gt;Raw &lt;code&gt;hostPath&lt;/code&gt; mounts introduce security risks, lack dynamic provisioning, and orphan data on disk upon deletion.&lt;/li&gt;
&lt;li&gt;K3s includes Rancher's Local Path Provisioner out of the box under the &lt;code&gt;local-path&lt;/code&gt; storage class; bare-metal clusters can install it via a single manifest.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volumeBindingMode: WaitForFirstConsumer&lt;/code&gt; ensures the provisioner allocates volumes on the exact worker node selected by the scheduler.&lt;/li&gt;
&lt;li&gt;Local path storage delivers raw NVMe throughput with zero network overhead, making it the ideal storage choice for homelabs, edge nodes, and replicated database clusters.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article first appeared on &lt;a href="https://mihai.ltd/blog/mount-local-storage-to-kubernetes-pods-k3s" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;. Read the original for the full version.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software architect with over a decade of experience building and scaling production systems. I write about Kubernetes, n8n workflow automation, AI agents and local LLM infrastructure, and I share the good results and the face-palm moments alike.&lt;/p&gt;

&lt;p&gt;On my YouTube channel, &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;Let's Talk Dev&lt;/a&gt;, I break down complex engineering concepts and the experiments behind these articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website and blog: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;mihai.ltd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;@letstalkdev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;mihailtd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;Mihai Farcas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>k3s</category>
      <category>storage</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Hired Claude, But Slower and More Expensive</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Tue, 21 Jul 2026 18:37:22 +0000</pubDate>
      <link>https://dev.to/mihailtd/i-hired-claude-but-slower-and-more-expensive-4lij</link>
      <guid>https://dev.to/mihailtd/i-hired-claude-but-slower-and-more-expensive-4lij</guid>
      <description>&lt;p&gt;The comment landed on the pull request early afternoon, around 9 in the morning his time; we worked a few time zones apart. Four paragraphs defending his implementation, well structured, calm, thorough, closing with a suggestion that we align on the approach going forward. I still disagreed with the implementation, but I respected the argument. It read like something a senior engineer would write.&lt;/p&gt;

&lt;p&gt;The next morning, we had a live share screen session to present the solution. I gave him a small task on the spot: take a hardcoded value and move it to an environment variable. Housekeeping. Two minutes, maybe five if you have to go get a coffee.&lt;/p&gt;

&lt;p&gt;He opened Copilot and started typing a prompt.&lt;/p&gt;

&lt;p&gt;"What are you doing?" I said. "Come on - you don't need Copilot to add an env var to your own code."&lt;/p&gt;

&lt;p&gt;So he did it by hand, and that's when I saw it. He struggled to navigate his own code - the code he'd defended in four paragraphs - hunting for the &lt;code&gt;pydantic&lt;/code&gt; config like a visitor in someone else's repository. Then the second thing: environment variables come in as strings, this value needed to be a number, and he didn't know how to do that. Not "forgot the syntax" - didn't know that converting a string to a number in Python was a thing he was supposed to do, or how to look for it. This is a person whose CV said data science. Years of it, allegedly, in a language where &lt;code&gt;int()&lt;/code&gt; is the kind of thing you learn the first week and never think about again.&lt;br&gt;
Four fluent architecture paragraphs one afternoon, couldn't &lt;code&gt;int(x)&lt;/code&gt; the next morning. The interview had been great, the CV impressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the interview measured
&lt;/h2&gt;

&lt;p&gt;We ran the process remotely, like everyone did by then. He answered everything. Machine learning, evaluation metrics and scores, RAG and vector databases, Docker, SQL and NoSQL, database indexes, REST APIs, HTTPS, encryption, authentication and authorization - the whole spread, handled with unusual composure. No wrong turns, no backtracking, no "I'm not sure, but here's my guess."&lt;/p&gt;

&lt;p&gt;At the time, that looked like a strong signal. In hindsight it was the tell.&lt;/p&gt;

&lt;p&gt;A technical interview is built as a ladder. The early questions confirm the basics. The later ones climb in difficulty on purpose, because the interviewer is trying to find the step where you slow down, start reasoning out loud, and eventually say "I don't know." That step is the measurement. It tells me your level, and it tells me something more valuable: what you do when you run out of level. Do you reason from first principles? Ask a clarifying question? Bluff?&lt;/p&gt;

&lt;p&gt;A candidate who never reaches that step has, in theory, exceeded the ceiling of my interview. That happens. I've met engineers who genuinely out-climbed every question I had. But they have a texture to them - they get more engaged as the questions get harder, they argue with the premise, they tell you where your scenario is unrealistic. What they don't do is deliver each answer with the same even confidence, in the same structured cadence.&lt;/p&gt;

&lt;p&gt;Even confidence across all difficulty levels isn't a sign of skill. Skill has a gradient. You can hear it in how someone answers a question at their comfort level versus one just past it. When the gradient is missing - when the question about B-tree page splits gets the same fluent treatment as the question about basic data types - either you're talking to one of the best engineers of your generation, or you're talking to a second screen.&lt;br&gt;
I assumed the first. I shouldn't have.&lt;/p&gt;

&lt;p&gt;This is why we moved the technical round back to face-to-face, and it has nothing to do with tradition or nostalgia. A live conversation in the same room is the last environment where I can observe the thing I'm actually hiring: a person, reasoning, unassisted, at their real level. I don't need them to perform under pressure, a candidate can think in silence as long as they want. I need to see where the ladder ends. Remote interviews stopped being able to show me that, because for a mediocre engineer with a state of the art LLM, the ladder no longer has a top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debating a&amp;nbsp;proxy
&lt;/h2&gt;

&lt;p&gt;The env var morning changed how I read his messages, but the answers kept coming. Every review comment I left came back with a rebuttal. Fast, fluent, three or four paragraphs, complete with tradeoffs. The tone never wavered and nothing was ever conceded. I'd respond, and within the hour another one would arrive, just as calm, just as structured, addressing my points one by one with a weird politeness.&lt;/p&gt;

&lt;p&gt;Producing one of those rebuttals cost him a copy, a prompt and a paste. Verifying it cost me twenty minutes - reading the documentation he cited, checking whether the tradeoff he described applied to our case at all, writing a response precise enough not to spawn three new threads. Reviewing his work had quietly become the biggest line item in my calendar.&lt;/p&gt;

&lt;p&gt;And underneath every exchange sat the thing I couldn't say. I was fairly sure - the &lt;code&gt;pydantic&lt;/code&gt; morning had made me fairly sure - that the person I was debating didn't understand the position he was defending. But knowing it and saying it are different weight classes. Accuse someone of relaying a model's arguments and you'd better be right, and able to show it; otherwise you're the manager who calls his reports frauds whenever they push back. So I kept my cards down and answered the arguments on their merits. Disagreement is the job; I don't mind it. What drained me was writing rebuttals to a machine, routed through a person who couldn't evaluate either side of the debate.&lt;/p&gt;

&lt;p&gt;The second task was small and deliberately unglamorous: a script, no interface, no users. What he delivered had a web UI and a login screen. The script itself - the part anyone had asked for - still didn't work properly. When I asked why he'd spent his time on extras nobody requested while the requested part had problems, the answer came back without a flicker of doubt: "Because making a login is easy."&lt;/p&gt;

&lt;p&gt;I've been doing this long enough to flinch at that sentence. &lt;/p&gt;

&lt;p&gt;Authentication is a hard problem - session handling, token expiry, credential storage, password resets, and so on. Whole companies exist because engineering teams decided never to build it themselves. But I understood where his confidence came from. A model scaffolds a login page in thirty seconds, and it runs, so to him a login &lt;em&gt;was&lt;/em&gt; easy. AI has flattened the visible effort of hard problems and trivial ones down to the same short wait, and if visible effort is your only gauge of complexity, everything seems as easy. He wasn't lying to me. He couldn't tell the difference. Or, more likely, never could, and now had a tool that agreed with him.&lt;/p&gt;

&lt;p&gt;That was the shape of those weeks: confident essays in the pull requests, confident shortcuts in the work, and me in the middle, debating an LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do I need&amp;nbsp;you?
&lt;/h2&gt;

&lt;p&gt;The clearest version of the problem surfaced in the next task. I handed him a piece of work with clear requirements. A solution came back. Decent, tidy, obviously generated, and wrong in two places that mattered. I reviewed it, found both problems, and explained them, along with the direction of the fix. A corrected version came back shortly after. It was fine. On the project board, this looked like an engineer completing a ticket.&lt;/p&gt;

&lt;p&gt;Walk the steps, though. The requirements were mine. The review that caught the flaws was mine. The direction for the fix was mine. The code was the model's. His contribution was moving text between me and the LLM - my task description into the prompt, my corrections into the prompt, the output into a pull request. At which of those steps did I need him?&lt;/p&gt;

&lt;p&gt;I had, in effect, added a layer between myself and Claude. It contributed latency, demanded a salary, and it subtracted accountability - when the corrected solution worked, that was his ticket done; when the first one was broken, that was somehow my fault for not writing clear enough requirements. Which was, notably, the only kind of pushback he ever produced. While the work was in front of him he never questioned a requirement, never asked what should happen at an edge, never flagged that a spec was ambiguous - the annoying, valuable questions that mean someone has thought past the ticket. Questioning a spec requires understanding it. His objections only ever arrived afterward, in defense. Cut the layer out and I'd get the same code faster, minus the endless debates. Somewhere in that loop the sentence formed on its own: I had hired Claude, but slower and more expensive.&lt;/p&gt;

&lt;p&gt;To be fair to the tool - and to be honest about my own workflow - the tool was never the problem. I use Claude daily. So does everyone on the team whose work I don't re-check. The difference sits entirely in what happens before and after the final output. When I get a generated solution, a list forms in my head of the places it's most likely wrong: the edge case it silently ignores, the API that doesn't behave the way the training data thinks it does, the requirement it quietly rewrote into an easier one. He did no such due diligence. He trusted the output until someone else pointed at the flaw, and he didn't seem to think that checking was part of his job. The model produced, I verified, he forwarded.&lt;/p&gt;

&lt;p&gt;This is what AI has done to the profession, and it took a bad hire for me to see it clearly. The visible difference between a junior and a senior used to announce itself in the code. You could tell from the diff who had ten years and who had one. Now both hand you clean, confident, working-looking solutions by the afternoon. The difference that's left is invisible, and it only shows up when something is wrong. Knowing what to distrust is now the entire job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust, once it's&amp;nbsp;gone
&lt;/h2&gt;

&lt;p&gt;The man in our daily standups did not match the man from the interview. The candidate had walked through database indexes and RAG pipelines with confidence. The employee, asked what he did yesterday, produced hesitations and generalities like "still working on it," or "had some issues with the setup", and couldn't summarize his own ticket in two clean sentences. For a while I explained the gap away as nerves, or language barriers, or mornings. But it had a pattern. A model can answer questions about B-trees. It can't answer what you did yesterday.&lt;/p&gt;

&lt;p&gt;And yet - every couple of weeks there would be a call where he was fine. He'd explain a decision coherently, catch some small thing, make a reasonable point, and I'd leave the meeting thinking: maybe I've been unfair to this guy. Maybe the env var morning was one bad day, magnified by everything I'd stacked on top of it since. The relief was genuine, and it lasted on average until his next pull request. Then the cycle restarted. I came to recognize it as a carousel - disbelief, then belief, then disbelief again - and to notice how much I wanted the belief half to win. Believing meant the problem would dissolve on its own. Disbelieving meant months of documentation and meetings I could already picture.&lt;/p&gt;

&lt;p&gt;Trust is what lets a manager sample. You skim most pull requests, spot-check now and then, take "done" at face value, because the track record covers the rest. With him I had stopped sampling. I read his diffs line by line, re-ran what he claimed passed, re-verified every "done". One person on suspicion costs more oversight than five people on trust.&lt;/p&gt;

&lt;p&gt;The part I didn't expect is how unfairly suspicion generalizes. Some of his work was genuinely fine, a config change or a small fix, yet I caught myself second-guessing that too, hunting for the flaw in code that didn't have one. Trust doesn't fail per-task; it fails per-person. Once it failed, there was no setting in which I could take something from him at face value, and rebuilding that was, realistically, no longer on the table.&lt;/p&gt;

&lt;p&gt;Which left one decision, and a process famous for taking its time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you can't just fix it&amp;nbsp;quickly
&lt;/h2&gt;

&lt;p&gt;There's a version of this story where the env var morning is followed, a week later, by a handshake and a "see you never" letter. That version exists in no company I've worked at.&lt;/p&gt;

&lt;p&gt;Unfortunately the location he worked in had no such thing as a probation period. What existed instead was process: documented feedback, improvement objectives, meetings with agendas and alignment with the people whose job is to make sure no employee is ever removed carelessly. The process isn't designed to reach the obvious conclusion quickly; it's designed to make sure the conclusion is fair.&lt;/p&gt;

&lt;p&gt;He understood the process too. The closer it moved, the more friction appeared: time-off requests at delicate moments, sudden personal problems and grievances that had to be heard out, each one consuming a meeting, a week, a cycle of someone else's attention. I can't prove any single one of them was tactical. Collectively, they had excellent timing.&lt;/p&gt;

&lt;p&gt;And here is the conflict I didn't expect to be having with myself. The department defending him is the same department that would defend me. I want to work at a company where nobody can be removed on one manager's bad week. But wanting the shield to exist and watching it held, week after week, over someone I knew by then to be functionally a scammer, is an internal conflict I never expected to have.&lt;/p&gt;

&lt;p&gt;All the while, the meter ran: my review hours, the tasks quietly routed around him, the senior engineer pulled into second-checking, the teammates watching a colleague produce almost nothing and remain on the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;I'm not new to interviewing. I've sat on the interviewers' side of the table well over a hundred times, and I started in an era when remote interviews didn't exist - every single one was a conference room, a handshake, and a conversation. When the industry migrated to video calls and take-home assignments, I migrated with it, because it was efficient and, for a while, it worked.&lt;/p&gt;

&lt;p&gt;The take-home assignment is dead. I'll say it plainly because I know how much some teams have invested in theirs. A take-home used to measure how someone works without an audience: how they structure a project, what they test, where they take shortcuts. It now measures whether a candidate can operate a model for an evening, and the answer is always yes. Every submission comes back senior-looking - clean structure, docstrings, a test suite. The signal isn't weakened; it's gone.&lt;/p&gt;

&lt;p&gt;What replaced it, for me, is the same process of interviewing I started with 8 years ago: a face-to-face discussion, no augmentation, no second screen - just the candidate's head and mine. And I've changed what I probe for. What I care about is depth and logic - not "what is a database index," but "here's a query that got slower after we added an index; walk me through what could have happened and how you'd go about fixing it." Then I follow the answer downward. Everyone's understanding thins somewhere; the depth where it thins is their level. How they behave at that depth - guessing, bluffing, or saying "I don't know, but here's how I'd find out" - tells me the rest.&lt;/p&gt;

&lt;p&gt;That's the calibration I described at the start, and it's the part no tool has managed to fake in a live conversation. A model can hand someone an answer. It cannot hand them the second layer of the answer, or the third, or the ability to notice that my follow-up question quietly contradicted their previous claim.&lt;/p&gt;

&lt;p&gt;So that's how I interview for now. Not the production of answers - answers have become the cheapest thing in the profession. I look for the person who looks at a confident, plausible, generated solution and knows where to poke it, how to identify the edge cases where it doesn't work. Everything else, the tools cover.&lt;/p&gt;




&lt;p&gt;I'm Mihai Farcas, a software architect from Cluj-Napoca, Romania. &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;Add me on LinkedIn&lt;/a&gt;. I make &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;YouTube videos about software architecture and engineering careers&lt;/a&gt;. If you've lived through a hire like this one, I'd genuinely like to hear how it went. Also if you've changed your interviewing process because of AI, I'd be curious to know how.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>programming</category>
      <category>interview</category>
    </item>
    <item>
      <title>The $3/Month Enterprise-Ready Automation Stack: Zero-Trust n8n With One Command (n8n, Cloudflare, UpCloud, Pulumi)</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Tue, 07 Apr 2026 17:44:04 +0000</pubDate>
      <link>https://dev.to/mihailtd/the-3month-enterprise-ready-automation-stack-zero-trust-n8n-with-one-command-n8n-cloudflare-556g</link>
      <guid>https://dev.to/mihailtd/the-3month-enterprise-ready-automation-stack-zero-trust-n8n-with-one-command-n8n-cloudflare-556g</guid>
      <description>&lt;p&gt;I run one command and get a fully working n8n instance — HTTPS, zero-trust tunnel, PostgreSQL, the works. One more command and it's all gone. Total cost: $3/month.&lt;/p&gt;

&lt;p&gt;No SSH. No open ports. No clicking around in dashboards.&lt;/p&gt;

&lt;p&gt;Here's exactly how it works.&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%2F1vykmxq9qm2ni4p4d368.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%2F1vykmxq9qm2ni4p4d368.png" alt=" " width="799" height="121"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Self-Hosted Automation
&lt;/h2&gt;

&lt;p&gt;Every self-hosting tutorial I find tells me to do the same thing: open ports 80 and 443, set up Nginx, wrestle with Let's Encrypt, and pray nobody scans my server before I've hardened it.&lt;/p&gt;

&lt;p&gt;Then there's the database. Most n8n guides default to SQLite. It works — until two workflows fire at the same time and the whole thing locks up.&lt;/p&gt;

&lt;p&gt;I don't want to babysit a server. I want to deploy, use it, and tear it down when I'm done. No manual steps. No leftover infrastructure.&lt;/p&gt;

&lt;p&gt;So I built exactly that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;p&gt;One &lt;code&gt;pulumi up&lt;/code&gt; command creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;$3/month UpCloud server&lt;/strong&gt; (1 CPU, 1 GB RAM, 10 GB storage, Ubuntu 24.04). Use my &lt;a href="https://signup.upcloud.com/?promo=affiliate-letstalkdev250/?utm_source=letstalkdev&amp;amp;utm_medium=affiliate&amp;amp;utm_campaign=affiliate-letstalkdev250" rel="noopener noreferrer"&gt;UpCloud affiliate link&lt;/a&gt; and get started with this unbeatable deal!&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; — zero-trust, outbound-only connection. No open ports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic HTTPS&lt;/strong&gt; and DDoS protection via Cloudflare's edge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 18&lt;/strong&gt; for concurrent workflow execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; running behind the tunnel, accessible at your custom domain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS records&lt;/strong&gt; pointed at the tunnel automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One &lt;code&gt;pulumi destroy&lt;/code&gt; removes everything. Server, tunnel, DNS — gone.&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%2F0q2ww1w5o606juonx7dd.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%2F0q2ww1w5o606juonx7dd.png" alt=" " width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Zero-Trust Matters Here
&lt;/h2&gt;

&lt;p&gt;Traditional setups expose your server to the internet. You open ports, configure firewalls, manage certificates. Every open port is an attack surface.&lt;/p&gt;

&lt;p&gt;Cloudflare Tunnels flip this model. The &lt;code&gt;cloudflared&lt;/code&gt; daemon on your server creates an outbound-only connection to Cloudflare's network. Traffic flows in reverse:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User → Cloudflare (HTTPS + DDoS) → Tunnel → n8n container&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your server has zero open inbound ports. No firewall rules to manage. No certificates to renew. Cloudflare handles all of it at the edge.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 6-Pillar Stack
&lt;/h2&gt;

&lt;p&gt;Each tool has one job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pulumi&lt;/strong&gt; — Infrastructure as Code. Provisions everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UpCloud&lt;/strong&gt; — $3/month compute (Frankfurt, DE).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; — Zero-trust tunnel + HTTPS + DDoS protection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — Container runtime via Docker Compose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; — Workflow automation engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 18&lt;/strong&gt; — Production database for concurrent execution.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How It Wires Together
&lt;/h2&gt;

&lt;p&gt;The entire deployment is 180 lines of TypeScript. Here's what happens when you run &lt;code&gt;pulumi up&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Pulumi Creates the Cloudflare Tunnel
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tunnel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudflare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZeroTrustTunnelCloudflared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;n8n-tunnel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cfAccountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;n8n-tunnel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;configSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudflare&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tunnelSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 32-byte random secret gets generated. The tunnel routes traffic from your domain to &lt;code&gt;http://n8n:5678&lt;/code&gt; inside the Docker network.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Tunnel Token Gets Fetched via API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tunnelToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloudflare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getZeroTrustTunnelCloudflaredTokenOutput&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cfAccountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tunnelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key trick. Instead of SSHing into the server after boot to inject the token, Pulumi fetches it directly from Cloudflare's API. The token gets baked into the cloud-init script — zero post-deploy steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. DNS Points to the Tunnel
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dnsRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudflare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DnsRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;n8n-dns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;zoneId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cfZoneId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CNAME&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interpolate&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.cfargotunnel.com`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proxied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A proxied CNAME record. Cloudflare's edge handles TLS termination and DDoS filtering before traffic ever reaches your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cloud-Init Bootstraps the Server
&lt;/h3&gt;

&lt;p&gt;Pulumi builds a cloud-init script that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installs Docker Engine&lt;/li&gt;
&lt;li&gt;Writes &lt;code&gt;docker-compose.yml&lt;/code&gt; with all secrets baked in&lt;/li&gt;
&lt;li&gt;Writes the tunnel token to &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;docker compose up -d&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The server boots, runs this script, and 60 seconds later n8n is live. No SSH required.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Three Containers, One Compose File
&lt;/h3&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;postgres&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;postgres:18-alpine&lt;/span&gt;
    &lt;span class="c1"&gt;# Health check ensures n8n waits for DB&lt;/span&gt;

  &lt;span class="na"&gt;n8n&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;n8nio/n8n:latest&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="c1"&gt;# Full PostgreSQL config, HTTPS via tunnel&lt;/span&gt;

  &lt;span class="na"&gt;cloudflared&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;cloudflare/cloudflared:latest&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tunnel run&lt;/span&gt;
    &lt;span class="c1"&gt;# Token from .env file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL starts first. n8n waits for the health check. &lt;code&gt;cloudflared&lt;/code&gt; connects the tunnel. The Docker network handles service discovery — &lt;code&gt;n8n:5678&lt;/code&gt; just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup (5 Minutes)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://signup.upcloud.com/?promo=affiliate-letstalkdev250/?utm_source=letstalkdev&amp;amp;utm_medium=affiliate&amp;amp;utm_campaign=affiliate-letstalkdev250" rel="noopener noreferrer"&gt;UpCloud&lt;/a&gt; account (API credentials)&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; account with a domain (API token with Zone:DNS:Edit + Account:Tunnels:Edit)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/docs/install/" rel="noopener noreferrer"&gt;Pulumi CLI&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploy
&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;# Clone and install&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;infra &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Create .env from the template (in the project root)&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; ../.env.example ../.env
&lt;span class="c"&gt;# Edit .env — fill in your API tokens, domain, and Cloudflare IDs&lt;/span&gt;
&lt;span class="c"&gt;# SSH key is auto-detected from ~/.ssh/ (no config needed)&lt;/span&gt;

&lt;span class="c"&gt;# Load env vars and set secrets&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; ../.env &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; +a
pulumi config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt; postgresPassword &lt;span class="s2"&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;&lt;span class="s2"&gt;"&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt; n8nBasicAuthUser &lt;span class="s2"&gt;"admin"&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt; n8nBasicAuthPassword &lt;span class="s2"&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;&lt;span class="s2"&gt;"&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt; n8nEncryptionKey &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Deploy everything&lt;/span&gt;
pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Visit &lt;code&gt;https://n8n.yourdomain.com&lt;/code&gt; and log in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tear Down
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server, tunnel, DNS — all removed. Nothing left running. Nothing left billing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Persistence?
&lt;/h2&gt;

&lt;p&gt;PostgreSQL data is saved as a Docker volume on the server itself. Your workflows, credentials, and execution history survive container restarts and &lt;code&gt;docker compose down&lt;/code&gt; / &lt;code&gt;up&lt;/code&gt; cycles.&lt;/p&gt;

&lt;p&gt;What they don't survive is &lt;code&gt;pulumi destroy&lt;/code&gt; — that deletes the server and everything on it.&lt;/p&gt;

&lt;p&gt;For production, consider switching to a managed PostgreSQL service (UpCloud, Aiven, Neon) or mounting resilient block storage for the database volume. Either option decouples your data from the server lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About n8n Cloud?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://n8n.partnerlinks.io/700yejiez8he" rel="noopener noreferrer"&gt;n8n Cloud&lt;/a&gt; is the easiest way to get started. Managed hosting, automatic updates, built-in auth, and zero infrastructure to think about. If you don't want to manage servers, it's the right choice.&lt;/p&gt;

&lt;p&gt;This self-hosted stack is a different tradeoff. You get full control over the environment — custom domains, your own database, and the ability to spin up or tear down the whole thing in seconds. You also manage the infrastructure.&lt;/p&gt;

&lt;p&gt;Both are valid paths. Pick the one that fits how you work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Lifecycle
&lt;/h2&gt;

&lt;p&gt;Here's the entire flow, start to finish:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You (one-time):&lt;/strong&gt; Fill in a &lt;code&gt;.env&lt;/code&gt; file (API tokens, domain, Cloudflare IDs). Run 4 secret config commands. SSH key is auto-detected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pulumi (automated):&lt;/strong&gt; Creates tunnel → fetches token → configures routes → creates DNS → builds cloud-init → provisions server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server (automated):&lt;/strong&gt; Boots → installs Docker → writes configs → starts containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; n8n live at your domain with HTTPS, zero-trust networking, and PostgreSQL. No SSH. No open ports. $3/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done?&lt;/strong&gt; &lt;code&gt;pulumi destroy&lt;/code&gt;. Everything gone.&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%2Fkvt595qpqhuxayjno9u3.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%2Fkvt595qpqhuxayjno9u3.png" alt=" " width="748" height="1285"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Want to try it?&lt;/strong&gt; The full source code is on &lt;a href="https://github.com/mihailtd/iac-deploy-n8n-upcloud-cloudflare-pulumi" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Clone the repo, set your API keys, and deploy in 5 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Need help with your setup?&lt;/strong&gt; I build and consult on automation infrastructure professionally. Reach out and let's talk about your use case.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Automate Your Homelab Like a Pro with Ansible!</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Wed, 02 Apr 2025 07:02:04 +0000</pubDate>
      <link>https://dev.to/mihailtd/automate-your-homelab-like-a-pro-with-ansible-1f7i</link>
      <guid>https://dev.to/mihailtd/automate-your-homelab-like-a-pro-with-ansible-1f7i</guid>
      <description>&lt;p&gt;Spending too much time manually configuring servers in your homelab? Wish you could automate setup, updates, and deployments reliably? Ansible is a powerful yet simple, agentless automation tool that can tame your infrastructure, whether it's a personal lab or a production environment.&lt;/p&gt;

&lt;p&gt;This article walks through practical examples directly from my latest YouTube video, showing you how to automate Proxmox updates, VM setups, PostgreSQL configuration, and even handle secrets securely with Ansible Vault.&lt;/p&gt;

&lt;p&gt;If you'd rather watch a youtube video than read:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/NMSCOhK2rqY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If not, let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ansible for Your Homelab (and Beyond)?
&lt;/h2&gt;

&lt;p&gt;So, why choose Ansible over manual configurations or complex scripts? Here are a few compelling reasons:&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplicity &amp;amp; Readability
&lt;/h3&gt;

&lt;p&gt;Ansible uses YAML for its playbooks, which is incredibly easy for humans to read and write. You describe the &lt;em&gt;state&lt;/em&gt; you want your systems to be in, rather than scripting every single step. This makes your automation understandable and maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agentless Architecture
&lt;/h3&gt;

&lt;p&gt;This is a huge advantage! You don't need to install or manage special agent software on your servers (nodes). Ansible communicates primarily over standard SSH for Linux/Unix systems (or WinRM for Windows), using Python on the remote end (which is usually already there). This means less setup, less resource overhead, and a smaller attack surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Idempotency: The Automation Superpower
&lt;/h3&gt;

&lt;p&gt;As highlighted in the video, Ansible operations are typically &lt;em&gt;idempotent&lt;/em&gt;. This means you can run a playbook multiple times, and it will only make changes if needed to reach the desired state. Running it again won't break things or cause unintended side effects. This is absolutely crucial for reliable and predictable automation – run your updates, and only the nodes that need updating get touched!&lt;/p&gt;

&lt;h3&gt;
  
  
  Power &amp;amp; Flexibility ("Batteries Included")
&lt;/h3&gt;

&lt;p&gt;Don't let the simplicity fool you. Ansible comes with a vast library of built-in modules that handle thousands of common tasks – installing packages (&lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;), managing services (&lt;code&gt;service&lt;/code&gt;, &lt;code&gt;systemd&lt;/code&gt;), copying files (&lt;code&gt;copy&lt;/code&gt;), generating configs from templates (&lt;code&gt;template&lt;/code&gt;), managing users (&lt;code&gt;user&lt;/code&gt;), interacting with cloud providers, configuring databases (like PostgreSQL!), and much more. You describe &lt;em&gt;what&lt;/em&gt; you want done, and the module handles the &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you start automating, you need a couple of things set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. On Your Control Machine (Where you run Ansible):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python 3:&lt;/strong&gt; Ansible is built with Python. Make sure you have Python 3 installed. You can check with:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ansible:&lt;/strong&gt; Install Ansible itself. Using &lt;code&gt;pip&lt;/code&gt; (Python's package manager) is often recommended for getting the latest version: &lt;code&gt;pip3 install ansible&lt;/code&gt;. Alternatively, use your OS package manager (like &lt;code&gt;apt&lt;/code&gt; or &lt;code&gt;brew&lt;/code&gt;). Check your installation with:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;(Optional) Ansible VS Code Extension:&lt;/strong&gt; If you use Visual Studio Code, the official Ansible extension by Red Hat provides excellent syntax highlighting and autocompletion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ansible Packages&lt;/strong&gt;: Some Ansible community collections might need to be installed separately using commands like &lt;code&gt;ansible-galaxy collection install community.postgresql&lt;/code&gt; for example.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Access to Your Target Nodes (Your Homelab Servers):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSH Access:&lt;/strong&gt; Since Ansible is agentless for Linux/Unix, it relies on SSH to connect and execute commands. You need SSH access from your control machine to your homelab servers.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SSH Key Authentication (Recommended):&lt;/strong&gt; For seamless automation, set up SSH key-based authentication so Ansible doesn't need to ask for passwords. The easiest way is to copy your public SSH key (e.g., &lt;code&gt;~/.ssh/id_ed25519.pub&lt;/code&gt;) to your target nodes using &lt;code&gt;ssh-copy-id&lt;/code&gt;. Replace &lt;code&gt;root@&amp;lt;server_ip&amp;gt;&lt;/code&gt; with the appropriate user and IP for each server:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_ed25519.pub root@&amp;lt;your_homelab_server_ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;(Note: For Windows targets, Ansible uses WinRM instead of SSH, which requires different setup steps not covered in detail here.)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding Your Ansible Project: Structure &amp;amp; Core Concepts
&lt;/h2&gt;

&lt;p&gt;A well-structured Ansible project makes automation easier to manage and scale. Here's a typical layout, incorporating the core concepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-ansible-project/
├── ansible.cfg         # Project-specific Ansible configuration
├── inventory/          # Defines WHAT servers Ansible manages
│   └── hosts.ini       # Your inventory file(s)
├── playbooks/          # Contains your automation instructions (playbooks)
│   ├── proxmox.yml
│   ├── postgres.yml
│   └── ...
├── vars/               # Stores variables, including secrets
│   ├── common_vars.yml
│   └── secrets.yml     # Encrypted secrets using Ansible Vault
└── roles/              # (Optional) Reusable units of automation (Not covered in detail here)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;[Image: Simple diagram showing this directory structure]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's break down each part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;inventory/&lt;/code&gt; (The "What"):&lt;/strong&gt; This directory holds your &lt;strong&gt;Inventory&lt;/strong&gt; file(s) (often &lt;code&gt;hosts.ini&lt;/code&gt; or &lt;code&gt;inventory.yml&lt;/code&gt;). This tells Ansible &lt;em&gt;which&lt;/em&gt; servers (nodes) it should manage. You can list servers individually or group them logically (like &lt;code&gt;[proxmox_nodes]&lt;/code&gt; or &lt;code&gt;[webservers]&lt;/code&gt;). Your script showed an example &lt;code&gt;inventory.ini&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[proxmox_nodes]&lt;/span&gt;
&lt;span class="err"&gt;pve&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.31.100&lt;/span&gt;
&lt;span class="err"&gt;srv-1&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.31.101&lt;/span&gt;
&lt;span class="err"&gt;srv-2&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.31.110 # The new node&lt;/span&gt;

&lt;span class="nn"&gt;[k3s_nodes]&lt;/span&gt;
&lt;span class="err"&gt;k3s-master-1&lt;/span&gt; &lt;span class="py"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.31.50 ansible_user=k3s ansible_become_password="{{ k3s_password }}"&lt;/span&gt;
&lt;span class="c"&gt;# ... other nodes ...
&lt;/span&gt;
&lt;span class="nn"&gt;[all:vars]&lt;/span&gt;
&lt;span class="c"&gt;# Global variables can go here
&lt;/span&gt;&lt;span class="py"&gt;ansible_user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;root # Default user if not specified per host/group&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;[Note: Variables like &lt;code&gt;ansible_host&lt;/code&gt;, &lt;code&gt;ansible_user&lt;/code&gt;, and group variables (&lt;code&gt;[all:vars]&lt;/code&gt;) can also be defined here.]&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;playbooks/&lt;/code&gt; (The "How"):&lt;/strong&gt; This is where your &lt;strong&gt;Playbooks&lt;/strong&gt; live. Playbooks are written in YAML and define a series of &lt;strong&gt;Tasks&lt;/strong&gt; to be executed on hosts defined in your inventory. Each task typically uses an Ansible &lt;strong&gt;Module&lt;/strong&gt; (the building blocks like &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;template&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;postgresql_user&lt;/code&gt;) to perform a specific action. Remember, good modules are &lt;em&gt;idempotent&lt;/em&gt;. A basic task structure looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&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;Example Play&lt;/span&gt; &lt;span class="c1"&gt;# Descriptive name for the whole play&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxmox_nodes&lt;/span&gt; &lt;span class="c1"&gt;# Which group(s) from inventory to target&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Execute tasks with root privileges (like sudo)&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Update Proxmox&lt;/span&gt; &lt;span class="c1"&gt;# Descriptive name for this task&lt;/span&gt;
      &lt;span class="na"&gt;apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# The module being used&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# Module parameters&lt;/span&gt;
        &lt;span class="na"&gt;upgrade&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;vars/&lt;/code&gt; (The Data &amp;amp; Secrets):&lt;/strong&gt; Store your variables here, separating them from your playbooks for better organization. You can have files for common variables (&lt;code&gt;common_vars.yml&lt;/code&gt;) and crucially, for secrets (&lt;code&gt;secrets.yml&lt;/code&gt;). Never commit plain-text passwords! Use &lt;strong&gt;Ansible Vault&lt;/strong&gt; to encrypt sensitive files:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new encrypted file&lt;/span&gt;
ansible-vault create vars/secrets.yml

&lt;span class="c"&gt;# Edit an existing encrypted file&lt;/span&gt;
ansible-vault edit vars/secrets.yml

&lt;span class="c"&gt;# Run playbook asking for password&lt;/span&gt;
&lt;span class="c"&gt;# ansible-playbook my_playbook.yml --ask-vault-pass&lt;/span&gt;

&lt;span class="c"&gt;# Or better, use a password file (add vault.pass to .gitignore!)&lt;/span&gt;
ansible-playbook my_playbook.yml &lt;span class="nt"&gt;--vault-password-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./vault.pass
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Your &lt;code&gt;secrets.yml&lt;/code&gt; might contain things like &lt;code&gt;db_password: your_secret_password&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you run &lt;code&gt;ansible-vault create&lt;/code&gt; or &lt;code&gt;edit&lt;/code&gt;, Ansible opens your default text editor (&lt;code&gt;$EDITOR&lt;/code&gt;). Inside the editor, you just write standard YAML key-value pairs with your secrets, like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```yaml
# vars/secrets.yml
# --- This is just plain YAML while you edit! ---
k3s_password: "your_k3s_sudo_password"
app1_db_password: "supersafepassword123"
some_api_key: "xyz789abc123"
some_secret_value: 1234
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once you save and close the editor, Ansible encrypts the content. The actual file on disk becomes unreadable text, which &lt;em&gt;is&lt;/em&gt; safe to commit to version control. This file is encrypted and can be decrypted with the password you picked when creating the vault!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ansible.cfg&lt;/code&gt; (The Settings):&lt;/strong&gt; This optional file defines project-specific Ansible settings, overriding system defaults. Your script mentioned useful settings:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[defaults]&lt;/span&gt;
&lt;span class="py"&gt;inventory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;./inventory/hosts.ini  # Default inventory file location&lt;/span&gt;
&lt;span class="py"&gt;remote_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;root              # Default user to connect as&lt;/span&gt;
&lt;span class="py"&gt;private_key_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;~/.ssh/id_ed25519 # Default SSH key&lt;/span&gt;
&lt;span class="py"&gt;host_key_checking&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;False       # Disable host key checking (use with caution!). Acceptable in trusted homelabs, but avoid in production - Disabling key checking skips verification of the remote host's identity, making you vulnerable to man-in-the-middle attacks.&lt;/span&gt;

&lt;span class="nn"&gt;[privilege_escalation]&lt;/span&gt;
&lt;span class="py"&gt;become&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;True                   # Run tasks with privileges by default&lt;/span&gt;
&lt;span class="py"&gt;become_method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sudo            # How to gain privileges&lt;/span&gt;
&lt;span class="py"&gt;become_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;root              # Which user to become&lt;/span&gt;
&lt;span class="py"&gt;become_ask_pass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;False         # Don't ask for sudo password (assumes NOPASSWD)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizing your project this way keeps things tidy and makes your automation easier to understand, maintain, and share.&lt;/p&gt;
&lt;h2&gt;
  
  
  Homelab Automation in Action: Examples
&lt;/h2&gt;

&lt;p&gt;Theory is great, but let's see Ansible solve real homelab problems, based on the examples from the YouTube video.&lt;/p&gt;

&lt;p&gt;Before we look at example, do me a solid favor and check out my YouTube Channel, Let's Talk Dev&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fyt3.googleusercontent.com%2FRIzr-0Smkmg3dcAmBedb7FDoofVtML0mW5AtG8ixVmh9EDh9E8zPq7toYW243x3PChxCztcSxg%3Ds900-c-k-c0x00ffffff-no-rj" height="900" class="m-0" width="900"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g/" rel="noopener noreferrer" class="c-link"&gt;
            Let's Talk Dev - YouTube
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I share my insights and tips on software engineering topics such as web development, databases, cloud computing, and more.
Hi, I'm Mihai and I'm a full-stack software engineer with a passion for creating innovative and user-friendly solutions using web and cloud technologies. I have a home lab where I like to experiment with Kubernetes and other cutting-edge technologies. I'm also a big fan of open-source software.
When I'm not coding, I enjoy photography, reading books, learning about finance and entrepreneurship, and watching movies.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.youtube.com%2Fs%2Fdesktop%2F513a5249%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          youtube.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Who knows, you might find those videos helpful and even &lt;strong&gt;subscribe&lt;/strong&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fixing Proxmox Subscription Error by Adding the Non-Subscription Repositories
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Dealing with the Proxmox "no subscription" warnings by switching repositories, and keeping all Proxmox nodes consistently updated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; Use Ansible's dedicated &lt;code&gt;apt_repository&lt;/code&gt; module to manage APT sources correctly, and the &lt;code&gt;apt&lt;/code&gt; module for updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fixing Repositories (using &lt;code&gt;apt_repository&lt;/code&gt;):&lt;/strong&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="c1"&gt;# playbooks/proxmox_config.yml (Example Snippet)&lt;/span&gt;
&lt;span class="nn"&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;Fix Proxmox repository errors by adding the no-subscription repository&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxmox_nodes&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# Facts not strictly needed for this part&lt;/span&gt;
  &lt;span class="c1"&gt;# vars_files: # Only needed if secrets were used here&lt;/span&gt;
  &lt;span class="c1"&gt;#  - ../vars/secrets.yml&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Disable Proxmox enterprise repository&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deb&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[https://enterprise.proxmox.com/debian/pve](https://enterprise.proxmox.com/debian/pve)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bookworm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pve-enterprise"&lt;/span&gt; &lt;span class="c1"&gt;# Adjust 'bookworm' for your PVE version&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;absent&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pve-enterprise&lt;/span&gt; &lt;span class="c1"&gt;# Explicitly target the correct file&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;Add Proxmox no-subscription repository&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deb&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[http://download.proxmox.com/debian/pve](http://download.proxmox.com/debian/pve)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bookworm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pve-no-subscription"&lt;/span&gt; &lt;span class="c1"&gt;# Adjust 'bookworm'&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pve-no-subscription&lt;/span&gt; &lt;span class="c1"&gt;# Use a distinct filename&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;Disable Proxmox enterprise ceph repository (if applicable)&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deb&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[https://enterprise.proxmox.com/debian/ceph-quincy](https://enterprise.proxmox.com/debian/ceph-quincy)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bookworm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;enterprise"&lt;/span&gt; &lt;span class="c1"&gt;# Adjust 'quincy'/'bookworm'&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;absent&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ceph-enterprise&lt;/span&gt; &lt;span class="c1"&gt;# Explicitly target the correct file&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;Add Proxmox no-subscription ceph repository (if applicable)&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deb&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[http://download.proxmox.com/debian/ceph-quincy](http://download.proxmox.com/debian/ceph-quincy)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bookworm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no-subscription"&lt;/span&gt; &lt;span class="c1"&gt;# Adjust 'quincy'/'bookworm'&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ceph-no-subscription&lt;/span&gt; &lt;span class="c1"&gt;# Use a distinct filename&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;Update apt cache after repo changes&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Explanation:&lt;/em&gt; This automation uses the &lt;code&gt;ansible.builtin.apt_repository&lt;/code&gt; module, which is the idiomatic way to manage APT repositories. &lt;code&gt;state: absent&lt;/code&gt; ensures the enterprise repositories are removed or commented out in their respective files (specified by &lt;code&gt;filename&lt;/code&gt;), while &lt;code&gt;state: present&lt;/code&gt; ensures the no-subscription repositories are added correctly. Finally, &lt;code&gt;apt update_cache: true&lt;/code&gt; refreshes the package list.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keeping Proxmox up to date!
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Upgrading Proxmox Nodes (&lt;code&gt;upgrade-proxmox.yml&lt;/code&gt; logic):&lt;/strong&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="c1"&gt;# playbooks/proxmox_upgrade.yml (Example Snippet)&lt;/span&gt;
&lt;span class="nn"&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;Upgrade Proxmox Nodes&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxmox_nodes&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Update apt cache and perform dist-upgrade&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# Good practice before upgrade&lt;/span&gt;
        &lt;span class="na"&gt;upgrade&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt; &lt;span class="c1"&gt;# Handles kernel/PVE upgrades correctly&lt;/span&gt;
        &lt;span class="na"&gt;autoclean&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# Clean up downloaded package files&lt;/span&gt;
        &lt;span class="na"&gt;autoremove&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# Remove unused dependencies&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;Check if reboot is required&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.stat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/run/reboot-required&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reboot_required_file&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;Reboot the server if required&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.reboot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reboot&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;initiated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Ansible&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;due&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;updates"&lt;/span&gt;
        &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;reboot_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt; &lt;span class="c1"&gt;# Wait up to 10 mins for reboot&lt;/span&gt;
        &lt;span class="na"&gt;pre_reboot_delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="na"&gt;post_reboot_delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="c1"&gt;# Wait 30s after reboot before continuing&lt;/span&gt;
        &lt;span class="na"&gt;test_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uptime&lt;/span&gt; &lt;span class="c1"&gt;# Command to test if server is back up&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reboot_required_file.stat.exists&lt;/span&gt; &lt;span class="c1"&gt;# Only reboot if flag file exists&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Explanation:&lt;/em&gt; This automation uses the &lt;code&gt;apt&lt;/code&gt; module for the upgrade process (&lt;code&gt;upgrade: dist&lt;/code&gt; is important for Proxmox). Crucially, it checks if the system flagged a required reboot (&lt;code&gt;stat&lt;/code&gt; module) and then uses the &lt;code&gt;reboot&lt;/code&gt; module &lt;em&gt;only when&lt;/em&gt; that flag exists (&lt;code&gt;when&lt;/code&gt; condition). Idempotency ensures nodes already up-to-date are simply skipped by the &lt;code&gt;apt&lt;/code&gt; module.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Managing VMs Consistently
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Ensuring all your VMs (e.g., Ubuntu servers running Docker or k3s) have essential tools like Python and specific libraries installed.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Target a group of VMs in your inventory and use the &lt;code&gt;apt&lt;/code&gt; module.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# playbooks/vm_setup.yml (Example Snippet)&lt;/span&gt;
&lt;span class="nn"&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;Setup Base Packages on VMs&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s_nodes&lt;/span&gt; &lt;span class="c1"&gt;# Or a group like [all_vms]&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Needs sudo/root privileges&lt;/span&gt;
  &lt;span class="c1"&gt;# Assumes ansible_become_password might be needed (defined in inventory/vault)&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Install Python, Pip, and other required packages&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3-pip&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3-psycopg2&lt;/span&gt; &lt;span class="c1"&gt;# For PostgreSQL interaction later&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3-docker&lt;/span&gt;   &lt;span class="c1"&gt;# For Docker interaction later&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;Explanation:&lt;/em&gt; This playbook targets a specific group (&lt;code&gt;k3s_nodes&lt;/code&gt;). The &lt;code&gt;apt&lt;/code&gt; module ensures the listed packages are installed (&lt;code&gt;state: present&lt;/code&gt;). If a package is already installed, Ansible makes no changes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Automating PostgreSQL Initialization and Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Manually creating database users and databases for different applications is repetitive and error-prone.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use service-specific Ansible modules for more complex tasks.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# playbooks/postgres_setup.yml (Example Snippet)&lt;/span&gt;
&lt;span class="nn"&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;Configure PostgreSQL Users and Databases&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database_servers&lt;/span&gt; &lt;span class="c1"&gt;# Assuming a group for your DB server(s)&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;become_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt; &lt;span class="c1"&gt;# Run postgres tasks as the 'postgres' OS user, alternatively if postgres runs as root, keep using root.&lt;/span&gt;

  &lt;span class="na"&gt;vars_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../vars/secrets.yml&lt;/span&gt; &lt;span class="c1"&gt;# Load sensitive variables like passwords&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Ensure database user exists for App1&lt;/span&gt;
      &lt;span class="na"&gt;community.postgresql.postgresql_user&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;app1_user&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app1_db_password&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt; &lt;span class="c1"&gt;# Password from vault&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
      &lt;span class="c1"&gt;# This module handles creating the user idempotently&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;Ensure database exists for App1&lt;/span&gt;
      &lt;span class="na"&gt;community.postgresql.postgresql_db&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;app1_db&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app1_user&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
      &lt;span class="c1"&gt;# This module handles creating the database idempotently&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;Explanation:&lt;/em&gt; This example uses modules from the &lt;code&gt;community.postgresql&lt;/code&gt; collection (&lt;code&gt;postgresql_user&lt;/code&gt;, &lt;code&gt;postgresql_db&lt;/code&gt;). It runs tasks as the &lt;code&gt;postgres&lt;/code&gt; system user (&lt;code&gt;become_user&lt;/code&gt;) and pulls the password from an Ansible Vault encrypted file (&lt;code&gt;vars_files&lt;/code&gt; and &lt;code&gt;{{ app1_db_password }}&lt;/code&gt;). These modules are designed to be idempotent.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These examples show how Ansible moves beyond simple commands to reliably manage configurations, updates, and service setups across your homelab infrastructure.&lt;/p&gt;

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

&lt;p&gt;As we've seen, Ansible is a fantastic tool for bringing order and efficiency to your homelab (and potentially your day job!). By defining your infrastructure as code, you reduce tedious manual work, ensure configurations are consistent across machines, and gain confidence through idempotent operations. The examples here – fixing Proxmox repos, updating nodes, setting up VMs, and configuring PostgreSQL – are just the beginning. Think about initial server setups, Docker container deployments, or keeping k3s clusters up-to-date; Ansible can handle it all.&lt;/p&gt;

&lt;p&gt;Remember, the key concept is &lt;strong&gt;idempotency&lt;/strong&gt;: run your playbooks as often as you need, knowing they'll only make changes necessary to reach your desired state.&lt;/p&gt;

&lt;p&gt;I hope this gives you a solid starting point! For the full walkthrough and live demos, make sure to check out the companion &lt;a href="https://youtu.be/NMSCOhK2rqY" rel="noopener noreferrer"&gt;Ansible Automation YouTube video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the first thing &lt;em&gt;you&lt;/em&gt; plan to automate in your homelab using Ansible? Let me know in the comments below!&lt;/p&gt;

&lt;p&gt;If you found this article and the video helpful, consider subscribing to &lt;a href="https://youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;Let's Talk Dev on YouTube&lt;/a&gt; for more software engineering and DevOps content, and follow me here on Dev.to! Happy automating!&lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software architect with over a decade of experience under my belt. I'm passionate about writing code and love sharing knowledge with fellow developers.&lt;/p&gt;

&lt;p&gt;My YouTube channel, "Let's Talk Dev," is where I break down complex concepts, share my experiences (both the good and the face-palm moments).&lt;/p&gt;

&lt;p&gt;Connect with me:&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;https://mihai.ltd&lt;/a&gt;&lt;br&gt;
YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;https://www.youtube.com/@letstalkdev&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;https://github.com/mihailtd&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/mihai-farcas-ltd/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>ansible</category>
      <category>devops</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Functional Programming Face-Off: Python vs JavaScript vs Go!</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Wed, 05 Mar 2025 20:50:20 +0000</pubDate>
      <link>https://dev.to/mihailtd/functional-programming-face-off-python-vs-javascript-vs-go-3bmj</link>
      <guid>https://dev.to/mihailtd/functional-programming-face-off-python-vs-javascript-vs-go-3bmj</guid>
      <description>&lt;p&gt;Functional programming has been gaining popularity in recent years, and for good reason. It offers a number of benefits over traditional imperative programming, such as improved code readability, testability, and maintainability.&lt;/p&gt;

&lt;p&gt;In this article, we'll compare and contrast how three popular programming languages - Python, JavaScript, and Go - handle functional programming concepts. We'll explore how each language supports features like higher-order functions, immutability, and function composition, and discuss their strengths and weaknesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather watch than read:
&lt;/h2&gt;

&lt;p&gt;Check out the accompanying YouTube video!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/56i63VPtZGY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Defining and Calling Functions
&lt;/h2&gt;

&lt;p&gt;At the heart of functional programming lies, unsurprisingly, the function. The way functions are defined and invoked can significantly impact a language's suitability for functional programming paradigms. We'll examine how Python, JavaScript, and Go handle function definitions, looking at syntax, typing, and overall structure, setting the stage for more advanced functional concepts. Specifically how similar (or different) the syntax is, and the implications of different approaches to type systems (static, dynamic, type hints).&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;square&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In Python, functions are defined using the &lt;code&gt;def&lt;/code&gt; keyword followed by the function name, parentheses for parameters, and a colon. The code block within the function is indented. Type hints can be optionally added to specify the expected types of parameters and return values.&lt;/p&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;square&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;JavaScript offers a more compact syntax for defining functions, particularly with arrow functions. Arrow functions use the &lt;code&gt;=&amp;gt;&lt;/code&gt; operator, providing a concise way to express a function's logic.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&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;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&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;Go, a statically-typed language, requires explicit type declarations for both function parameters and return values. Functions are defined using the func keyword, followed by the function name, a parenthesized list of parameters (with their types), and the return type. The function body is enclosed in curly braces &lt;code&gt;{}&lt;/code&gt;.  Go's syntax is very similar to python in that it uses a keyword, followed by the name, followed by parameters. However, Go's strict typing and the need for explicit imports, even for basic operations like printing to the console (using the fmt package), introduce a slightly higher level of formality compared to Python and JavaScript. The Go example defines a function that, like the Python and JavaScript counterparts, takes an integer and returns its square.&lt;/p&gt;

&lt;p&gt;However, Go differs significantly in its overall structure. Every Go file belongs to a package, and the &lt;code&gt;package main&lt;/code&gt; declaration is special. It signifies that the file is part of the main package, which is the entry point for executable programs.  Without &lt;code&gt;package main&lt;/code&gt; and the &lt;code&gt;func main()&lt;/code&gt; within it, Go wouldn't know where to begin executing your code. It's a fundamental difference from Python and JavaScript, where you don't need such explicit entry point declarations for simple scripts. Another key distinction is Go's requirement for explicit imports, even for basic functionalities like printing to the console.  In the provided example, &lt;code&gt;fmt.Println&lt;/code&gt; is used, requiring an import of the &lt;code&gt;fmt&lt;/code&gt; (format) package.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Higher-Order Functions
&lt;/h2&gt;

&lt;p&gt;This section delves into a cornerstone of functional programming: &lt;strong&gt;treating functions as first-class citizens&lt;/strong&gt;. This means functions can be passed as arguments to other functions, returned as values from functions, and generally treated like any other variable.  A direct consequence of this is the ability to create higher-order functions – functions that either take other functions as arguments or return them (or both).  This allows for highly flexible and reusable code. We will also be touching on Closures, though the dedicated section for them comes later.&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;


&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 15
&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Python supports higher-order functions elegantly. In the Python snippet, &lt;code&gt;applyOperation&lt;/code&gt; is a higher-order function because it takes another function (&lt;code&gt;operation&lt;/code&gt;) as an argument.  &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;subtract&lt;/code&gt; are regular functions that perform addition and subtraction, respectively.  &lt;code&gt;applyOperation&lt;/code&gt; then calls the passed-in function (&lt;code&gt;operation&lt;/code&gt;) with the provided arguments (a and b).  Notice that the type hints are optional in Python. While the example does use type hints for clarity (specifying that operation should be a function taking two integers and returning an integer, and that a and b are integers), they are not enforced at runtime by default. This makes the Python code concise and similar in that aspect to JavaScript.&lt;/p&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 15&lt;/span&gt;

&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;JavaScript, like Python, treats functions as first-class citizens and readily supports higher-order functions. The JavaScript snippet demonstrates the same &lt;code&gt;applyOperation&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt;, and &lt;code&gt;subtract&lt;/code&gt; structure as the Python example. &lt;code&gt;applyOperation&lt;/code&gt; accepts a function (&lt;code&gt;operation&lt;/code&gt;) and calls it with arguments &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. Because JavaScript is dynamically typed, there are no type annotations.  The code is very concise and mirrors the Python example in its functional approach.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&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;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 15&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;applyOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Go also supports higher-order functions, although with its characteristic explicitness due to static typing. In the Go snippet, &lt;code&gt;applyOperation&lt;/code&gt; is again the higher-order function. However, the type of the operation parameter is explicitly declared: &lt;code&gt;func(int, int) int&lt;/code&gt;. This signifies that operation must be a function that takes two int arguments and returns an int.  This explicit type declaration is a key difference from Python (with optional type hints) and JavaScript (dynamically typed). The &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;subtract&lt;/code&gt; functions are defined with their types, and &lt;code&gt;applyOperation&lt;/code&gt; calls the provided function just like in the other languages. While all three languages achieve the same result, Go's static typing makes the function signatures more verbose. This reinforces Go's emphasis on explicitness and compile-time type safety.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Closures
&lt;/h2&gt;

&lt;p&gt;Closures are a powerful feature often used in conjunction with higher-order functions. A closure is a function that "remembers" its surrounding environment (its lexical scope) even after the outer function that created it has finished executing.  This means the inner function can access and modify variables defined in the outer function's scope, even if the outer function has returned. This ability to "capture" state is crucial for many functional programming patterns.&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;createCounter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;  &lt;span class="c1"&gt;# count is not a local variable,
&lt;/span&gt;        &lt;span class="c1"&gt;# it refers to the count variable in the outer scope
&lt;/span&gt;        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;


&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCounter&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;counter&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;counter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Python snippet demonstrates a classic closure example with &lt;code&gt;createCounter&lt;/code&gt;.  &lt;code&gt;createCounter&lt;/code&gt; is a function that returns another function, &lt;code&gt;counter&lt;/code&gt;.  The key here is the &lt;code&gt;nonlocal count&lt;/code&gt; declaration within &lt;code&gt;counter&lt;/code&gt;.  Because Python has stricter scoping rules, you need to explicitly tell the inner function (&lt;code&gt;counter&lt;/code&gt;) that &lt;code&gt;count&lt;/code&gt; is not a local variable within counter itself, but rather refers to the count variable in the &lt;strong&gt;enclosing *&lt;em&gt;scope of &lt;code&gt;createCounter&lt;/code&gt;. Without &lt;code&gt;nonlocal&lt;/code&gt;, Python would create a new, local &lt;code&gt;count&lt;/code&gt; variable inside &lt;code&gt;counter&lt;/code&gt;, and the outer count would remain unchanged. The returned &lt;code&gt;counter&lt;/code&gt; function *&lt;/em&gt;"closes over"&lt;/strong&gt; the count variable, maintaining its state between calls.&lt;/p&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;count&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCounter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;JavaScript handles closures very naturally.  In the JavaScript snippet, &lt;code&gt;createCounter&lt;/code&gt; also returns an anonymous function (the closure). This inner function increments and returns the &lt;code&gt;count&lt;/code&gt; variable.  Crucially, JavaScript's scoping rules automatically allow the inner function to access and modify the count variable from the &lt;code&gt;createCounter&lt;/code&gt; scope, even after &lt;code&gt;createCounter&lt;/code&gt; has completed.  There's no need for a special keyword like Python's &lt;code&gt;nonlocal&lt;/code&gt;; the closure behavior is &lt;strong&gt;implicit&lt;/strong&gt;. This makes the JavaScript code very concise.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;createCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&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;Go's closure mechanism is similar to JavaScript's.  The Go snippet defines &lt;code&gt;createCounter&lt;/code&gt;, which returns an anonymous function that increments and returns the count variable.  Like JavaScript, and unlike Python, there's no need for any special declaration to indicate that &lt;code&gt;count&lt;/code&gt; is from the outer scope. The inner function automatically has access to and can modify &lt;code&gt;count&lt;/code&gt;.  The returned function &lt;strong&gt;"closes over"&lt;/strong&gt; the count variable, preserving its state between invocations.  Go's approach, like JavaScript's, is straightforward and doesn't require extra keywords for closure behavior. The syntax, returning a &lt;code&gt;func() int&lt;/code&gt;, clearly expresses that &lt;code&gt;createCounter&lt;/code&gt; returns a function that takes no arguments and returns an integer.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Partial Application
&lt;/h2&gt;

&lt;p&gt;Partial application is a technique for creating new functions from existing ones by pre-filling some of their arguments.  It's a way to specialize a general function into a more specific one.  Instead of calling a function with all its arguments at once, you "fix" some of the arguments, creating a new function that expects only the remaining arguments. This can lead to more reusable and readable code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greetingFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;greeting&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;greetingFn&lt;/span&gt;


&lt;span class="n"&gt;firstGreeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Well, hello there &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;secondGreeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hola &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="nf"&gt;firstGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Remi&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="nf"&gt;secondGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ana&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;p&gt;In the Python snippet, &lt;code&gt;createGreeting&lt;/code&gt; is a function that demonstrates partial application. It takes a &lt;code&gt;greeting&lt;/code&gt; string as input and returns a new function (&lt;code&gt;greetingFn&lt;/code&gt;). This returned function takes a name string and combines it with the pre-filled greeting.  When you call &lt;code&gt;createGreeting("Well, hello there ")&lt;/code&gt;, you're not getting the final greeting string; you're getting a new function (&lt;code&gt;firstGreeting&lt;/code&gt;) that has the "Well, hello there " greeting already "baked in."  You can then call &lt;code&gt;firstGreeting&lt;/code&gt; with different names to get the complete greeting. The same principle applies to &lt;code&gt;secondGreeting&lt;/code&gt;, prefilling a different initial greeting. The type hints help show the flow: &lt;code&gt;createGreeting&lt;/code&gt; takes a string and returns a function that takes a string and returns a string.&lt;/p&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createGreeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&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;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstGreeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Well, hello there &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secondGreeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;firstGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;secondGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The JavaScript snippet achieves the same result using a concise arrow function syntax. &lt;code&gt;createGreeting&lt;/code&gt; takes a greeting and returns another arrow function that takes a name.  This inner function uses template literals (backticks) to create the final greeting string.  Like the Python example, calling &lt;code&gt;createGreeting&lt;/code&gt; with a greeting creates a new, specialized function (&lt;code&gt;firstGreeting&lt;/code&gt;, &lt;code&gt;secondGreeting&lt;/code&gt;) with that greeting pre-applied. The nested arrow functions clearly illustrate the process of creating a function that returns another function.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;firstGreeting&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Well, hello there "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;secondGreeting&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;createGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hola "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Remi"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secondGreeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ana"&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;The Go snippet implements partial application in a similar manner.  &lt;code&gt;createGreeting&lt;/code&gt; takes a greeting string and returns a function that takes a name string and returns the combined string.  Go's explicit type signature, &lt;code&gt;func(string) string&lt;/code&gt;, clearly indicates that &lt;code&gt;createGreeting&lt;/code&gt; returns a function. The use of &lt;code&gt;fmt.Sprintf&lt;/code&gt; is Go's way of formatting strings, analogous to Python's f-strings and JavaScript's template literals.  The result is the same: &lt;code&gt;firstGreeting&lt;/code&gt; and &lt;code&gt;secondGreeting&lt;/code&gt; become specialized functions with their respective greetings pre-filled.  Go's syntax, while slightly more verbose due to type declarations, achieves the same functional goal.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Currying
&lt;/h2&gt;

&lt;p&gt;Currying is a technique closely related to partial application, but with a subtle, yet &lt;strong&gt;important&lt;/strong&gt;, difference.  While partial application allows you to pre-fill any number of arguments, &lt;strong&gt;currying transforms a function that takes multiple arguments into a sequence of functions, each taking a single argument&lt;/strong&gt;. Each function in the sequence returns the next function in the sequence, until all arguments have been supplied, at which point the final result is returned. Currying isn't built-in to any of these languages; we need to implement a curry function ourselves.&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;curried&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&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;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__code__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;co_argcount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;curried&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;curried&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;


&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Python snippet implements a general-purpose &lt;code&gt;curry&lt;/code&gt; function. This function is more complex than the previous examples because it needs to handle functions with an arbitrary number of arguments. It uses &lt;code&gt;fn.__code__.co_argcount&lt;/code&gt; to determine the number of arguments the original function (&lt;code&gt;fn&lt;/code&gt;) expects.  The inner curried function uses &lt;code&gt;*args&lt;/code&gt; to collect arguments.  If enough arguments have been collected (the length of args is greater or equal to the number of arguments that &lt;code&gt;fn&lt;/code&gt; expects), it calls the original function fn with those arguments.  Otherwise, it returns a new lambda function that takes more arguments (&lt;code&gt;*args2&lt;/code&gt;), combines them with the existing arguments (&lt;code&gt;args + args2&lt;/code&gt;), and recursively calls &lt;code&gt;curried&lt;/code&gt; again. This recursive process continues until all arguments are provided. The add function, which takes three arguments, is then curried and called with one argument at a time: &lt;code&gt;curry(add)(1)(2)(3)&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;curried&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;curried&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The JavaScript snippet provides a similar curry function implementation. It uses &lt;code&gt;fn.length&lt;/code&gt; to get the number of arguments the original function expects.  The curried function uses the rest parameter syntax (&lt;code&gt;...args&lt;/code&gt;) to gather arguments.  If enough arguments are present, it calls the original function &lt;code&gt;fn&lt;/code&gt; using &lt;code&gt;apply&lt;/code&gt; (to handle the &lt;code&gt;this&lt;/code&gt; context correctly). Otherwise, it returns a new function that takes more arguments (&lt;code&gt;...args2&lt;/code&gt;), concatenates them with the existing arguments using &lt;code&gt;concat&lt;/code&gt;, and recursively calls &lt;code&gt;curried&lt;/code&gt;.  Like the Python version, this allows us to call a curried function with one argument at a time, as shown with &lt;code&gt;curry(add)(1)(2)(3)&lt;/code&gt;. The Javascript and Python versions are strikingly similar.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;

&lt;p&gt;The Go snippet presents a significantly more complex implementation of curry. This is where the differences between Go's static typing and the dynamic typing of Python and JavaScript become most apparent.  Because Go is statically typed, creating a generic curry function that works with functions of any arity (number of arguments) and any type requires using the &lt;code&gt;reflect&lt;/code&gt; package. &lt;strong&gt;Reflection allows us to inspect and manipulate types at runtime, which is necessary because the curry function doesn't know the types of the function it's currying beforehand.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"reflect"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ft&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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;ft&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curry: argument must be a function"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;numArgs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;curried&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;curried&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;numArgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;curried&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;curried&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;curriedAdd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;add1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;curriedAdd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;add12&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;add1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&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;add12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 6&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;curry&lt;/code&gt; function first checks if the input f is actually a function using &lt;code&gt;reflect.TypeOf(f).Kind() != reflect.Func&lt;/code&gt;.  It then determines the number of expected arguments using &lt;code&gt;ft.NumIn()&lt;/code&gt;.  It uses a slice args (of type &lt;code&gt;reflect.Value&lt;/code&gt;) to store the accumulated arguments. The curried function (a closure) is defined recursively. When called with an argument x, it appends x to args. If enough arguments have been collected, it calls the original function using &lt;code&gt;reflect.ValueOf(f).Call(args)[0].Interface()&lt;/code&gt;.  This line is crucial: it converts the function &lt;code&gt;f&lt;/code&gt; to a &lt;code&gt;reflect.Value&lt;/code&gt;, calls it with the collected arguments, extracts the result (which is also a &lt;code&gt;reflect.Value&lt;/code&gt;), and then converts it back to a regular Go &lt;code&gt;interface{}&lt;/code&gt; using &lt;code&gt;.Interface()&lt;/code&gt;. If not enough arguments have been collected, it returns itself (curried), continuing the currying process.&lt;/p&gt;

&lt;p&gt;The main function shows how to use the curried function.  Because the curry function returns the most generic type &lt;code&gt;interface{}&lt;/code&gt;, we need multiple type assertions (e.g., .&lt;code&gt;(func(reflect.Value) interface{})&lt;/code&gt;) to convert the intermediate curried functions to their correct types before we can call them. This is much more verbose than the Python and JavaScript examples, highlighting the added complexity of implementing currying in a statically-typed language without generics (prior to Go 1.18). The final result is retrieved and printed, demonstrating that the currying works, but at the cost of significantly more complex code. This example clearly shows that functional programming patterns like currying, while possible in Go, are often more natural and concise in dynamically typed languages.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Immutability
&lt;/h2&gt;

&lt;p&gt;Immutability is a core principle of functional programming.  An immutable object is one whose state cannot be changed after it's created.  This contrasts with mutable objects, which can be modified in place.  Immutability simplifies reasoning about code, prevents unintended side effects, and is crucial for concurrent programming (as immutable data is inherently thread-safe).  Different languages have varying levels of built-in support for immutability.&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&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;copy&lt;/span&gt;

&lt;span class="n"&gt;points1&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;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="c1"&gt;# Shallow Copy
&lt;/span&gt;&lt;span class="n"&gt;points2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;points2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;# Modifies only points2
&lt;/span&gt;&lt;span class="n"&gt;points2&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&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="mi"&gt;10&lt;/span&gt;  &lt;span class="c1"&gt;# Modifies the object in both lists
&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;points1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: [{'x': 10, 'y': 2}, {'x': 3, 'y': 4}] (affected!)
&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;points2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: [{'x': 10, 'y': 2}, {'x': 3, 'y': 4}, {'x': 5, 'y': 6}]
&lt;/span&gt;
&lt;span class="c1"&gt;# Deep Copy
&lt;/span&gt;&lt;span class="n"&gt;points3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;points3&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&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="mi"&gt;20&lt;/span&gt;  &lt;span class="c1"&gt;# Modifies only points3
&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;points1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: [{'x': 10, 'y': 2}, {'x': 3, 'y': 4}] (unaffected)
&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;points3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: [{'x': 20, 'y': 2}, {'x': 3, 'y': 4}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Python's built-in data structures, like lists and dictionaries, are mutable.  This means you can modify them directly after creation. The Python snippet highlights the importance of understanding the difference between shallow and deep copies when working with nested mutable objects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Shallow Copy (&lt;code&gt;copy.copy()&lt;/code&gt;): A shallow copy creates a new top-level object, but the elements within that object are still references to the original objects.  So, if you have a list of dictionaries, a shallow copy will create a new list, but the dictionaries inside the new list will be the same dictionaries as in the original list.  Modifying a nested object in the shallow copy will affect the original object as well. As shown, appending to &lt;code&gt;points2&lt;/code&gt; affects only the copy because append creates a new array, but modifying &lt;code&gt;points2[0]["x"]&lt;/code&gt; modifies the underlying object referenced by both &lt;code&gt;points1&lt;/code&gt; and &lt;code&gt;points2&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deep Copy (&lt;code&gt;copy.deepcopy()&lt;/code&gt;): A deep copy recursively creates new copies of all objects, including nested ones.  This means modifying a nested object in a deep copy will not affect the original object. As shown, after a deep copy, modifying &lt;code&gt;points3[0]["x"]&lt;/code&gt; only affects &lt;code&gt;points3&lt;/code&gt;, leaving &lt;code&gt;points1&lt;/code&gt; unchanged. You need to explicitly use &lt;code&gt;copy.deepcopy()&lt;/code&gt; to achieve true immutability with nested mutable objects in Python.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;points1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;points2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;points1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Creates a shallow copy&lt;/span&gt;

&lt;span class="nx"&gt;points2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Modifies only points2&lt;/span&gt;
&lt;span class="nx"&gt;points2&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="nx"&gt;x&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="c1"&gt;// Modifies the object in both arrays&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;points1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: [{ x: 10, y: 2 }, { x: 3, y: 4 }] (affected!)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;points2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: [{ x: 10, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;JavaScript, like Python, has mutable objects (arrays and objects) by default.  The JavaScript snippet uses the spread operator (&lt;code&gt;...&lt;/code&gt;) to create a shallow copy of an array of objects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Spread Operator for Arrays:  The spread operator, when used with an array, creates a &lt;strong&gt;shallow copy&lt;/strong&gt;.  Similar to Python's &lt;code&gt;copy.copy()&lt;/code&gt;, it creates a new array, but the objects within the array are still references to the same objects in the original array.  Appending to &lt;code&gt;points2&lt;/code&gt; doesn't modify &lt;code&gt;points1&lt;/code&gt;, as a new array instance is created, but changing &lt;code&gt;points2[0].x&lt;/code&gt; does modify the corresponding object in &lt;code&gt;points1&lt;/code&gt; because it's a &lt;strong&gt;shared reference&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deep Copying in JavaScript: JavaScript doesn't have a built-in deep copy function like Python's &lt;code&gt;copy.deepcopy()&lt;/code&gt;.  You typically need to use a custom function or a library (like Lodash's &lt;code&gt;_.cloneDeep&lt;/code&gt;) to perform a deep copy. One common (but potentially problematic) approach is to use &lt;code&gt;JSON.parse(JSON.stringify(object))&lt;/code&gt;, but this only works for objects that can be safely serialized to JSON (no functions, circular references, etc.). The lack of a standard, built-in deep copy mechanism is a notable weakness of JavaScript in terms of supporting immutability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;points1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="n"&gt;points2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;points1&lt;/span&gt; &lt;span class="c"&gt;// Creates a copy of the slice header and underlying array&lt;/span&gt;

    &lt;span class="c"&gt;// Appending creates a new slice, doesn't modify points1&lt;/span&gt;
    &lt;span class="n"&gt;points2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Modifying an element in points2 creates a copy of that element.&lt;/span&gt;
    &lt;span class="n"&gt;points2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: [{1 2} {3 4}] (original unaffected)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: [{10 2} {3 4} {5 6}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Go takes a different approach to immutability.  Go's structs are value types. When you assign a struct to a new variable or pass it to a function, a copy of the struct is created. This promotes immutability by default.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Value Types: In the Go snippet, Point is a struct.  When &lt;code&gt;points2 := points1&lt;/code&gt; is executed, &lt;code&gt;points2&lt;/code&gt; receives a copy of the slice header.  Importantly, in this case, the underlying array elements (the Point structs) are also copied because they are value types.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;append&lt;/code&gt;: The &lt;code&gt;append&lt;/code&gt; function in Go is designed to work well with immutability. When you append to a slice, Go might create a new underlying array if the existing array doesn't have enough capacity. This means that &lt;code&gt;append&lt;/code&gt; often returns a new slice, leaving the original slice untouched. As demonstrated, p&lt;code&gt;oints2 = append(points2, Point{5, 6})&lt;/code&gt; creates a new slice for &lt;code&gt;points2&lt;/code&gt;, leaving &lt;code&gt;points1&lt;/code&gt; unmodified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modifying Struct Fields: Because of the value semantics of structs, changing a field within the struct inside a slice in Go doesn't change the value of the struct in another.&lt;br&gt;
In the example provided, modifying &lt;code&gt;points2&lt;/code&gt;, does not affect &lt;code&gt;points1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go's design, with its emphasis on value types for structs and the behavior of &lt;code&gt;append&lt;/code&gt;, encourages immutability. While you can use pointers to create mutable behavior in Go, the language's default behavior for structs promotes immutability, making it easier to write code with fewer side effects.  This is a significant advantage of Go for functional programming.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Map, Filter, Reduce
&lt;/h2&gt;

&lt;p&gt;Map, filter, and reduce are fundamental higher-order functions in functional programming that operate on collections of data (like lists or arrays). They provide a concise and declarative way to transform and process data without using explicit loops.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Map&lt;/strong&gt;: Applies a given function to each element of a collection, producing a new collection with the transformed elements. The original collection remains unchanged.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Filter&lt;/strong&gt;: Creates a new collection containing only the elements from the original collection that satisfy a given condition (a predicate function).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Reduce&lt;/strong&gt;: Combines all elements of a collection into a single value using a specified function. This function takes an accumulator and the current element, updating the accumulator with each iteration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&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;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;reduce&lt;/span&gt;

&lt;span class="c1"&gt;# Map: Square each number
&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Squares:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Filter: Get even numbers
&lt;/span&gt;&lt;span class="n"&gt;evens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Evens:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Reduce: Sum all numbers
&lt;/span&gt;&lt;span class="n"&gt;sum_of_numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbers&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="c1"&gt;# 0 is the initial value
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sum:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum_of_numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Python has built-in support for &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and &lt;code&gt;reduce&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;map&lt;/strong&gt;: The &lt;code&gt;map&lt;/code&gt; function takes a function and an iterable (like a list) as arguments.  In the example, &lt;code&gt;lambda n: n * n&lt;/code&gt; is an anonymous function (a &lt;code&gt;lambda&lt;/code&gt;) that squares a number.  map applies this function to each element of the numbers list. The result of &lt;code&gt;map&lt;/code&gt; is an &lt;code&gt;iterator&lt;/code&gt;, so we use &lt;code&gt;list()&lt;/code&gt; to convert it to a list for printing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;filter&lt;/strong&gt;: The &lt;code&gt;filter&lt;/code&gt; function also takes a function and an iterable.  The function (&lt;code&gt;lambda n: n % 2 == 0&lt;/code&gt;) acts as a predicate, returning &lt;code&gt;True&lt;/code&gt; for even numbers and &lt;code&gt;False&lt;/code&gt; otherwise. &lt;code&gt;filter&lt;/code&gt; returns an iterator containing only the elements for which the predicate returns &lt;code&gt;True&lt;/code&gt;. Again, we use &lt;code&gt;list()&lt;/code&gt; to convert it to a list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;reduce&lt;/strong&gt;:  &lt;code&gt;reduce&lt;/code&gt; is not directly built-in; it's part of the &lt;code&gt;functools&lt;/code&gt; module.  It takes a function, an iterable, and an optional initial value.  The function (&lt;code&gt;lambda acc, n: acc + n&lt;/code&gt;) takes the accumulator (&lt;code&gt;acc&lt;/code&gt;) and the current element (&lt;code&gt;n&lt;/code&gt;), returning the updated accumulator. reduce applies this function cumulatively to the items of the sequence, from left to right, so as to reduce the sequence to a single value. In the snippet, it calculates the sum of all numbers in the list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Map: Square each number&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Squares:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Filter: Get even numbers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Evens:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;evens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Reduce: Sum all numbers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// 0 is the initial value&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sum:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;JavaScript also provides &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and &lt;code&gt;reduce&lt;/code&gt; as built-in methods on arrays. This makes the syntax very concise and readable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;map&lt;/strong&gt;: The &lt;code&gt;map&lt;/code&gt; method is called directly on the numbers array. It takes a callback function (&lt;code&gt;n =&amp;gt; n * n&lt;/code&gt;) that's applied to each element, creating a new array (&lt;code&gt;squares&lt;/code&gt;) with the results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;filter&lt;/strong&gt;: The &lt;code&gt;filter&lt;/code&gt; method, similarly, is called on the array. It takes a predicate function (&lt;code&gt;n =&amp;gt; n % 2 === 0&lt;/code&gt;) and returns a new array (&lt;code&gt;evens&lt;/code&gt;) containing only the elements that satisfy the predicate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;reduce&lt;/strong&gt;: The &lt;code&gt;reduce&lt;/code&gt; method is also called on the array.  It takes a callback function (&lt;code&gt;(acc, n) =&amp;gt; acc + n&lt;/code&gt;) and an initial value (&lt;code&gt;0&lt;/code&gt;). The callback function takes the accumulator (&lt;code&gt;acc&lt;/code&gt;) and the current element (&lt;code&gt;n&lt;/code&gt;), returning the updated accumulator. &lt;code&gt;reduce&lt;/code&gt; iterates through the array, accumulating the result into the &lt;code&gt;sum&lt;/code&gt; variable. The JavaScript syntax for these operations is very clean and directly integrated into the array object.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;slice&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Reduce&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="p"&gt;{&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;initial&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;{&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Map: Square each number&lt;/span&gt;
    &lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Squares:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Filter: Get even numbers&lt;/span&gt;
    &lt;span class="n"&gt;evens&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Evens:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Reduce: Sum all numbers&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sum:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum&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;Go, unlike Python and JavaScript, does not have built-in &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and &lt;code&gt;reduce&lt;/code&gt; functions. However, you can easily implement them using Go's &lt;code&gt;for...range&lt;/code&gt; loops and functions. The Go snippet shows generic implementations of &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Filter&lt;/code&gt;, and &lt;code&gt;Reduce&lt;/code&gt; using Go 1.18+ &lt;strong&gt;generics&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Map&lt;/strong&gt;: The provided &lt;code&gt;Map&lt;/code&gt; function takes a slice of type &lt;code&gt;T&lt;/code&gt;, a function that transforms a &lt;code&gt;T&lt;/code&gt; to a &lt;code&gt;U&lt;/code&gt;, and it returns a slice of type &lt;code&gt;U&lt;/code&gt;. The types &lt;code&gt;T&lt;/code&gt; and &lt;code&gt;U&lt;/code&gt; can be any types. It iterates through the input slice, applies the provided function &lt;code&gt;f&lt;/code&gt; to each element, and stores the result in a new slice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filter&lt;/strong&gt;: The &lt;code&gt;Filter&lt;/code&gt; function takes a slice of type &lt;code&gt;T&lt;/code&gt; and a predicate function (a function that returns a boolean). It iterates through the slice, and for each element where the predicate function returns &lt;code&gt;true&lt;/code&gt;, it appends the element to a new slice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduce&lt;/strong&gt;: The &lt;code&gt;Reduce&lt;/code&gt; function takes a slice of type &lt;code&gt;T&lt;/code&gt;, an initial value of type &lt;code&gt;U&lt;/code&gt;, and a function which takes a value of type &lt;code&gt;U&lt;/code&gt; and a value of type &lt;code&gt;T&lt;/code&gt;, returning another &lt;code&gt;U&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While Go requires more code to achieve the same functionality as Python and JavaScript's built-ins, &lt;strong&gt;the generic implementations provide flexibility and type safety.&lt;/strong&gt; The use of anonymous functions (like &lt;code&gt;func(n int) int { return n * n }&lt;/code&gt;) makes the code resemble the lambda expressions used in Python and JavaScript.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Function Composition
&lt;/h2&gt;

&lt;p&gt;Function composition is the process of combining two or more functions to create a new function. The output of one function becomes the input of the next, forming a pipeline of operations. This is a powerful way to build complex logic from simpler, reusable building blocks, enhancing code modularity and readability. It avoids creating intermediate variables and nested function calls.&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&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;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;reduce&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply_by_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="n"&gt;composed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiply_by_two&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Order is correct
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composed&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;# ((5 + 1) * 2)^2 = 144
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Composed result:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Python snippet defines a compose function that takes a variable number of functions (&lt;code&gt;*funcs&lt;/code&gt;) as arguments. It returns a new function (a lambda) that takes an initial value &lt;code&gt;x&lt;/code&gt;. Inside the lambda, it uses &lt;code&gt;reduce&lt;/code&gt; to apply the functions in sequence. The trick here is &lt;code&gt;reversed(funcs)&lt;/code&gt;. reduce normally applies functions from left to right, but in function composition, we usually want the rightmost function to be applied first. &lt;code&gt;reversed(funcs)&lt;/code&gt; reverses the order of functions, so reduce effectively applies them from right to left. The functions &lt;code&gt;add_one&lt;/code&gt;, &lt;code&gt;square&lt;/code&gt;, &lt;code&gt;multiply_by_two&lt;/code&gt; demonstrates what functions can be composed. The order matters in composition. The comment ((5 + 1) * 2)^2 = 144 shows how the functions are applied step by step, starting with &lt;code&gt;add_one&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceRight&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addOne&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiplyByTwo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;composed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;multiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addOne&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Order is correct&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composed&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;// ((5 + 1) * 2)^2 = 144&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Composed result:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The JavaScript snippet defines a compose function using arrow function syntax. It's very concise. &lt;code&gt;...funcs&lt;/code&gt; collects all function arguments into an array. The returned function takes an initial value &lt;code&gt;x&lt;/code&gt; and uses &lt;code&gt;funcs.reduceRight((acc, f) =&amp;gt; f(acc), x)&lt;/code&gt;. The &lt;code&gt;reduceRight&lt;/code&gt; method is the key here; it's a built-in array method that applies a function against an accumulator and each element of the array (&lt;strong&gt;from right to left&lt;/strong&gt;), reducing it to a single value. This achieves the correct order of function application directly, without needing to reverse the function list like in Python.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;funcs&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&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;x&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;funcs&lt;/span&gt; &lt;span class="p"&gt;{&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;addOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;multiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;composed&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;)&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;composed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// ((5 + 1) * 2)^2 = 144&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Composed result:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&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;The go snippet defines a &lt;code&gt;compose&lt;/code&gt; function takes advantage of Go 1.18+ generics.&lt;br&gt;
It is defined to take a variadic number of functions (&lt;code&gt;funcs ...func(T) T&lt;/code&gt;). The &lt;code&gt;T&lt;/code&gt; represents any data type. The key difference is in how the functions are applied. It uses a simple &lt;code&gt;for...range&lt;/code&gt; loop to iterate through the funcs slice &lt;strong&gt;in the order they were provided&lt;/strong&gt;. Unlike Python and JavaScript, this Go compose function applies functions from left to right. This is a deliberate choice to make the code simpler, and in the provided &lt;code&gt;main()&lt;/code&gt; function, this is accounted for. The composed variable receives the composition, but the functions are listed in the reverse order (&lt;code&gt;addOne&lt;/code&gt;, &lt;code&gt;multiplyByTwo&lt;/code&gt;, &lt;code&gt;square&lt;/code&gt;) compared to what would be mathematically expected. The final call to &lt;code&gt;composed(5)&lt;/code&gt; produces correct results.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Each of these three languages brings its own unique approach to functional programming. Python and JavaScript, with their dynamic typing and built-in support for many functional features, offer a relatively lower barrier to entry. Go, on the other hand, while requiring a bit more effort to implement some functional concepts, provides strong immutability support and explicitness, which can be advantageous in certain scenarios.&lt;/p&gt;
&lt;h2&gt;
  
  
  Subscribe for More!
&lt;/h2&gt;

&lt;p&gt;If you enjoyed this comparison, be sure to subscribe to my YouTube channel for more content on programming languages, software development, and other tech topics.&lt;/p&gt;

&lt;p&gt;Let me know in the comments which language you prefer for functional programming and why!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fyt3.googleusercontent.com%2FRIzr-0Smkmg3dcAmBedb7FDoofVtML0mW5AtG8ixVmh9EDh9E8zPq7toYW243x3PChxCztcSxg%3Ds900-c-k-c0x00ffffff-no-rj" height="900" class="m-0" width="900"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g/" rel="noopener noreferrer" class="c-link"&gt;
            Let's Talk Dev - YouTube
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I share my insights and tips on software engineering topics such as web development, databases, cloud computing, and more.
Hi, I'm Mihai and I'm a full-stack software engineer with a passion for creating innovative and user-friendly solutions using web and cloud technologies. I have a home lab where I like to experiment with Kubernetes and other cutting-edge technologies. I'm also a big fan of open-source software.
When I'm not coding, I enjoy photography, reading books, learning about finance and entrepreneurship, and watching movies.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.youtube.com%2Fs%2Fdesktop%2F513a5249%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          youtube.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software engineer with a few years of experience under my belt. I'm passionate about writing code and love sharing knowledge with fellow developers.&lt;/p&gt;

&lt;p&gt;My YouTube channel, "Let's Talk Dev," is where I break down complex concepts, share my experiences (both the good and the face-palm moments).&lt;/p&gt;

&lt;p&gt;Connect with me:&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;https://mihai.ltd&lt;/a&gt;&lt;br&gt;
YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;https://www.youtube.com/@letstalkdev&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;https://github.com/mihailtd&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/mihai-farcas-ltd/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>python</category>
      <category>javascript</category>
      <category>functional</category>
    </item>
    <item>
      <title>Secure Self-Hosting with Cloudflare Tunnels and Docker: Zero Trust Security</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Wed, 08 Jan 2025 11:07:20 +0000</pubDate>
      <link>https://dev.to/mihailtd/secure-self-hosting-with-cloudflare-tunnels-and-docker-zero-trust-security-5bbn</link>
      <guid>https://dev.to/mihailtd/secure-self-hosting-with-cloudflare-tunnels-and-docker-zero-trust-security-5bbn</guid>
      <description>&lt;p&gt;For many software engineers, the allure of self-hosting is undeniable. It represents autonomy, a playground for experimentation and helps you learn about the infrastructure that makes the internet work.&lt;/p&gt;

&lt;p&gt;However, the freedom of self-hosting comes with a critical responsibility: security. Exposing your home network to the internet is like opening your front door to everyone. Without proper protection, your home setup can quickly become a target.&lt;/p&gt;

&lt;p&gt;This article explains the key methods and tools for securing your self-hosted applications, changing it from a weak spot into a secure setup. We'll go through DNS, look at the threat of DDoS attacks, and show how Cloudflare Tunnels, especially with Docker, provide a strong solution for achieving zero-trust security for your self-hosted environment.&lt;/p&gt;

&lt;p&gt;To complement this written guide, I've created two accompanying YouTube videos:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/q4IXxTg-nCc"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This video delves into the underlying concepts of DNS, DDoS attacks, reverse proxies, and how Cloudflare's infrastructure works to mitigate these threats.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/SivE_EfUNd8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This video provides a hands-on walkthrough, demonstrating how to set up Cloudflare Tunnels with Docker, securing a sample web application step-by-step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Self-Hosting Needs Special Attention
&lt;/h2&gt;

&lt;p&gt;When you self-host a website, you're essentially making your home network a part of the public internet. This drastically increases your attack surface. Let's look at some key implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your Public IP Address: This is your home network's "address" on the internet. Traditionally, to make your website accessible, you'd point your domain's DNS records to this IP. This direct association creates a clear target for malicious actors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open Ports: To serve web traffic, you typically need to open ports 80 (HTTP) and 443 (HTTPS) on your router, forwarding them to your web server. Open ports are like open doors, inviting potential intruders to probe your network for weaknesses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dynamic IP Addresses: Most residential ISPs assign dynamic IP addresses, which change periodically. This makes self-hosting less reliable because your website's DNS records need to be updated constantly to point to the correct IP. If they're not, your site can become unreachable. We'll see how to solve this problem later on.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is DNS and how does it work?
&lt;/h2&gt;

&lt;p&gt;Domain Name System (DNS) is like the internet's phone book. It's the technology that translates human-friendly website names (like &lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt;) into the numerical IP addresses that computers use to communicate. Without DNS, browsing the web would be incredibly difficult.&lt;/p&gt;

&lt;p&gt;However, for self-hosting, the traditional DNS system has a weakness: it can expose your home IP address. When someone looks up your domain, the DNS lookup process eventually leads back to your home's IP address, if you are using a basic self hosting setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  DDoS Attacks: The Digital Siege
&lt;/h2&gt;

&lt;p&gt;Once a malicious actor gets hold of your home IP address, which as we saw, can be exposed through the DNS resolution process, they can launch a Distributed Denial of Service (DDoS) attack.&lt;/p&gt;

&lt;p&gt;DDoS attacks are a major threat to self-hosted websites. In a DDoS attack, attackers use a network of infected computers (called a "botnet") to flood your server with traffic. This overwhelms your server's resources, making your website slow or completely inaccessible to legitimate users.&lt;/p&gt;

&lt;p&gt;For self-hosted sites, a DDoS attack can be devastating. Not only can it take your website offline, but the flood of traffic can also disrupt your entire home network, affecting other services and even your household's internet access.&lt;/p&gt;

&lt;p&gt;If you're interested in diving deeper into topics like this, including software engineering, self-hosting, home labs, Docker, Kubernetes, and other DevOps practices, head over to the Let's Talk Dev YouTube channel.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fyt3.googleusercontent.com%2FRIzr-0Smkmg3dcAmBedb7FDoofVtML0mW5AtG8ixVmh9EDh9E8zPq7toYW243x3PChxCztcSxg%3Ds900-c-k-c0x00ffffff-no-rj" height="900" class="m-0" width="900"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g/" rel="noopener noreferrer" class="c-link"&gt;
            Let's Talk Dev - YouTube
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I share my insights and tips on software engineering topics such as web development, databases, cloud computing, and more.
Hi, I'm Mihai and I'm a full-stack software engineer with a passion for creating innovative and user-friendly solutions using web and cloud technologies. I have a home lab where I like to experiment with Kubernetes and other cutting-edge technologies. I'm also a big fan of open-source software.
When I'm not coding, I enjoy photography, reading books, learning about finance and entrepreneurship, and watching movies.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.youtube.com%2Fs%2Fdesktop%2F513a5249%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          youtube.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Cloudflare: Your First Line of Defense
&lt;/h2&gt;

&lt;p&gt;Cloudflare, at its core, is a Content Delivery Network (CDN) with a strong focus on security. It acts as an intermediary between your users and your origin server, providing numerous benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reverse Proxy: Cloudflare operates as a reverse proxy, meaning all traffic to your website flows through its network first. This masks your origin server's IP address, shielding it from direct exposure.&lt;/li&gt;
&lt;li&gt;DNS Resolution Through Cloudflare: When you use Cloudflare's DNS services, the resolution process is handled by their vast network of globally distributed DNS servers. This not only improves performance but also adds another layer of obfuscation, preventing attackers from easily discovering your home IP through DNS queries.&lt;/li&gt;
&lt;li&gt;Basic DDoS Mitigation: Cloudflare's network is designed to absorb and filter malicious traffic. Their anycast network distributes traffic across numerous data centers, making it difficult for attackers to overwhelm a single point. It also identifies and blocks known malicious traffic patterns, preventing them from reaching your server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations of Traditional Cloudflare Protection for Self-Hosting
&lt;/h2&gt;

&lt;p&gt;While Cloudflare's basic DNS and DDoS protection offer a substantial improvement, they are not a complete solution for self-hosting scenarios. Here's why:&lt;/p&gt;

&lt;p&gt;To allow Cloudflare to communicate with your web server, you still need to open ports 80 and 443 on your router and forward them to your server. This keeps a potential vulnerability open. Attackers can directly target those open ports, attempting to exploit any weaknesses in your web server (i.e. Nginx) or other services running on your network.&lt;/p&gt;

&lt;p&gt;This also doesn't get rid of the dynamic IP address issue. If your home IP address changes, your website becomes inaccessible until you manually update your DNS records on Cloudflare. This manual intervention is prone to errors and can lead to downtime. There are more automated solutions out there, however they all rely on making the change on a schedule dinterval, which still leads to downtime.&lt;/p&gt;

&lt;p&gt;Also, while Cloudflare hides your IP from direct DNS queries, determined attackers might still uncover it through other means, such as examining email headers or exploiting vulnerabilities in third-party services you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare Tunnels: The Zero Trust Solution
&lt;/h2&gt;

&lt;p&gt;Cloudflare Tunnels provide a paradigm shift in how you expose self-hosted services to the internet. Instead of opening inbound ports and relying on traditional DNS pointing to your home IP, Tunnels create a secure, outbound-only connection from your server to the Cloudflare network. This fundamentally changes the security model to one of "zero trust."&lt;/p&gt;

&lt;h2&gt;
  
  
  How Cloudflare Tunnels Work: A Deep Dive
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;cloudflared Daemon: You install a lightweight agent called cloudflared on your server (or within a Docker container, as we'll see later). This daemon is responsible for establishing and maintaining the secure tunnel.&lt;/li&gt;
&lt;li&gt;Outbound-Only Connection: cloudflared initiates an outbound connection to Cloudflare's edge network. This is crucial because it eliminates the need to open any inbound ports on your router, drastically reducing your attack surface.&lt;/li&gt;
&lt;li&gt;Tunnel Creation: You create a tunnel in the Cloudflare dashboard, giving it a unique name.&lt;/li&gt;
&lt;li&gt;Service Configuration: You configure the tunnel to route traffic to specific services running on your local network. For instance, you can specify that traffic for &lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt; should be routed to your web server running on port 80. So you can choose which services are accessible through the tunnel and which aren't.&lt;/li&gt;
&lt;li&gt;DNS Integration: Finally, when creating the tunnel, DNS records in Cloudflare are automatically configured to point to the Cloudflare network, specifying the tunnel to be used for each service. So even if your home IP changes, the tunnel will still be opened and connected to Cloudflare's network.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To recap, the benefits of using Cloudflare Tunnels are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not reliant on opened ports&lt;/li&gt;
&lt;li&gt;Dynamic IP resilience&lt;/li&gt;
&lt;li&gt;Granular access control&lt;/li&gt;
&lt;li&gt;Better security&lt;/li&gt;
&lt;li&gt;Simplified management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Docker and Cloudflare Tunnels
&lt;/h2&gt;

&lt;p&gt;You can run the &lt;code&gt;cloudflared&lt;/code&gt; daemon within a separate Docker container alongside your application containers.&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;cloudflared&lt;/code&gt; in a &lt;code&gt;docker-compose.yml&lt;/code&gt;. Docker Compose simplifies the process of defining and managing multi-container applications. You can define your web application, database, and cloudflared container in a single docker-compose.yml file, ensuring they call all communicate between each other easily through Docker's internal DNS.&lt;/p&gt;

&lt;p&gt;Refer to my accompanying YouTube video for a detailed &lt;a href="https://youtu.be/SivE_EfUNd8" rel="noopener noreferrer"&gt;step-by-step guide on setting up Cloudflare Tunnels with Docker&lt;/a&gt;. The video covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating and managing tunnels through the Cloudflare dashboard&lt;/li&gt;
&lt;li&gt;Installing and configuring cloudflared within a Docker container&lt;/li&gt;
&lt;li&gt;Using docker compose (and Portainer) to expose a self hosted service to the internet!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tldr; is:&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;cloudflared&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;cloudflare/cloudflared:latest&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tunnel --no-autoupdate run&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TUNNEL_TOKEN=${TUNNEL_TOKEN}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Embracing Secure Self-Hosting
&lt;/h2&gt;

&lt;p&gt;Self-hosting offers a unique blend of control, flexibility, and learning opportunities. However, security should always be paramount. Cloudflare Tunnels, provide a robust and elegant solution for securing your self-hosted environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software engineer with a few years of experience under my belt. I'm passionate about writing code and love sharing knowledge with fellow developers.&lt;/p&gt;

&lt;p&gt;My YouTube channel, "Let's Talk Dev," is where I break down complex concepts, share my experiences (both the good and the face-palm moments).&lt;/p&gt;

&lt;p&gt;Connect with me:&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;https://mihai.ltd&lt;/a&gt;&lt;br&gt;
YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;https://www.youtube.com/@letstalkdev&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;https://github.com/mihailtd&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/mihai-farcas-ltd/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>networking</category>
      <category>security</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Kubernetes Debugging Made Easy with mirrord</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Fri, 25 Oct 2024 18:04:46 +0000</pubDate>
      <link>https://dev.to/mihailtd/kubernetes-debugging-made-easy-with-mirrord-105d</link>
      <guid>https://dev.to/mihailtd/kubernetes-debugging-made-easy-with-mirrord-105d</guid>
      <description>&lt;p&gt;Developing and debugging applications in a Kubernetes cluster can be complex. However, mirrord, an open-source project by MetalBear, offers a solution to simplify this process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Use Case
&lt;/h2&gt;

&lt;p&gt;Imagine you're a software engineer working on a complex project involving multiple interconnected microservices. You encounter a bug that only occurs in the staging environment. Reproducing this bug locally is a nightmare for two main reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Running the application locally requires a complex setup with multiple microservices, making it difficult to replicate the staging environment accurately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The bug may be data-dependent, and you lack the specific data present in the staging environment. Downloading this data from the staging database is a convoluted process.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where mirrord comes in!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is mirrord?
&lt;/h2&gt;

&lt;p&gt;mirrord is an open-source tool designed to simplify the development and debugging of applications running in Kubernetes clusters. It achieves this by enabling developers to work on their code directly within the cluster, eliminating the need for extensive local setups or continuous deployments to staging environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;mirrord operates as a "mirror" (hence the name) between your local development environment and the Kubernetes cluster. It intercepts requests destined for your application pods in the cluster and duplicates them, sending a copy to your local machine.  &amp;nbsp; &lt;/p&gt;

&lt;p&gt;This allows you to run and debug your code locally while interacting with real production data and services within the cluster. In essence, it's like having a magic portal that connects your local machine to the live Kubernetes environment without affecting actual users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Debugging a Production-Only Bug
&lt;/h2&gt;

&lt;p&gt;The video demonstrates how mirrord can help debug an upper-environment-only bug. The application, written in Go, runs fine locally but displays an error in the production Kubernetes cluster. In the following video I show how to use the mirrord along with it's VS Code extension to debug the application. mirrord allows you to select a target to mirror (pod or deployment) and provides access to the same resources and environment variables as the remote application.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/KJpEebC1tNE"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Keep in mind you have to have &lt;code&gt;kubectl&lt;/code&gt; access to the Kubernetes cluster you want to target pods in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is Mirrord for?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Developers Tired of the "Push-and-Pray" Development Cycle
&lt;/h3&gt;

&lt;p&gt;If you're tired of the constant cycle of pushing code to a staging environment and praying it works, Mirrord is for you. With mirrord, you can see your code running in a near-production environment instantly, without waiting for deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Teams Struggling with Complex Local Setups
&lt;/h3&gt;

&lt;p&gt;Setting up a local environment that accurately mirrors a production Kubernetes cluster can be a real headache, especially in microservice architectures. mirrord eliminates this pain by allowing you to develop and debug directly in the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developers Who Need to Debug with Production-Like Data
&lt;/h3&gt;

&lt;p&gt;Debugging with real-world data is crucial for finding and fixing those tricky bugs that only show up in upper environments. mirrord lets you do this safely, without impacting your live application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anyone Facing Challenges in Reproducing and Fixing Environment-Specific Bugs
&lt;/h3&gt;

&lt;p&gt;Some bugs just won't show up in your local environment, no matter how hard you try to replicate it. mirrord makes it easy to reproduce and fix these environment-specific bugs by giving you direct access to the target environment.&lt;/p&gt;

&lt;p&gt;TLDR; mirrord is for any developer who wants to simplify Kubernetes development and debugging, improve their workflow, and ship higher-quality code faster.&lt;/p&gt;

&lt;p&gt;For more details check out the documentation on the &lt;a href="https://mirrord.dev/" rel="noopener noreferrer"&gt;mirrord website&lt;/a&gt; along with the &lt;a href="https://github.com/mihailtd/mirrord-demo" rel="noopener noreferrer"&gt;code repository I created to demonstrate mirrod's features&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software engineer with a few years of experience under my belt. I'm passionate about writing code and love sharing knowledge with fellow developers. &lt;/p&gt;

&lt;p&gt;My YouTube channel, "Let's Talk Dev," is where I break down complex concepts, share my experiences (both the good and the face-palm moments).&lt;/p&gt;

&lt;p&gt;Connect with me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;https://mihai.ltd&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;YouTube&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;https://www.youtube.com/@letstalkdev&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;https://github.com/mihailtd&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/mihai-farcas-ltd/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>opensource</category>
      <category>tooling</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Why Go is Popular Right Now and Why I Started Learning Go as a Node.js Developer</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Tue, 22 Oct 2024 07:28:06 +0000</pubDate>
      <link>https://dev.to/mihailtd/why-go-is-popular-right-now-and-how-and-why-i-started-learning-go-as-a-nodejs-developer-2jcg</link>
      <guid>https://dev.to/mihailtd/why-go-is-popular-right-now-and-how-and-why-i-started-learning-go-as-a-nodejs-developer-2jcg</guid>
      <description>&lt;p&gt;As a Node.js developer for over a decade, I've built countless web applications, APIs, and microservices. Node.js has been my go-to language for its versatility and vast ecosystem. However, I've recently become increasingly interested in Go, particularly its performance, concurrency model, and growing popularity in the cloud-native space. This interest led me to start learning Go, and I'm thrilled to share my experiences and insights with you all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasons for Go's Popularity
&lt;/h2&gt;

&lt;p&gt;Go has been steadily gaining popularity among developers, and for good reason. It offers a unique blend of features that make it a compelling choice for building modern, high-performance applications.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance:&lt;/strong&gt; Go's compiled nature and efficient garbage collection result in impressive execution speed, often outperforming interpreted languages like Node.js and Python.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency:&lt;/strong&gt; Go's built-in concurrency features, through goroutines and channels, make it easier to write concurrent code, which is essential for building scalable applications.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity:&lt;/strong&gt; Go's syntax is clean, minimal, and easy to learn, reducing cognitive overhead and promoting code readability.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety:&lt;/strong&gt; Go's static typing helps catch errors during compilation, reducing runtime surprises and improving code reliability.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling:&lt;/strong&gt; Go's comprehensive standard library and rich ecosystem of third-party tools provide developers with the necessary resources to build and maintain robust applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go's sweet spot lies in its ability to balance performance, concurrency, and simplicity. It's a language that allows you to write efficient, scalable code without sacrificing readability or ease of development.&lt;/p&gt;

&lt;p&gt;Go's popularity has also been fueled by the rise of Kubernetes. As Kubernetes has become the de facto standard for container orchestration, Go has benefited from its close association. Kubernetes is written in Go, and many of its components and extensions are also built using Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Started Learning Go
&lt;/h2&gt;

&lt;p&gt;Beyond the technical benefits, I was drawn to Go's simplicity and elegance. Coming from the world of JavaScript with its occasional quirks and complexities, Go's minimalist syntax and clear design principles were a breath of fresh air. The language's focus on readability and maintainability aligned with my belief in writing clean, understandable code.&lt;/p&gt;

&lt;p&gt;I've been writing code in a functional-lite style in Node.js, and Go allows me to continue with that as it has many of the traits that make this possible in JS. Go's support for first-class functions, closures, and immutability makes it well-suited for functional programming paradigms. This was a significant advantage, as I could leverage my existing functional programming skills while exploring Go's unique features. Check out my &lt;a href="https://youtu.be/q1aNVIq3K7c" rel="noopener noreferrer"&gt;video about functional programming in JavaScript&lt;/a&gt; - many of the concepts apply to Go as well.&lt;/p&gt;

&lt;p&gt;I was particularly interested in Kubernetes and the possibility of writing Kubernetes extensions and operators in Go. Kubernetes has become the de facto standard for container orchestration, and Go's close association with Kubernetes made it a natural choice for extending its functionality. The prospect of building custom Kubernetes tools and controllers in Go further fueled my interest in learning the language. &lt;/p&gt;

&lt;p&gt;If you're interested in similar topics, check out my YouTube channel, where I share content about Kubernetes, functional programming, and many other software development topics:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fyt3.googleusercontent.com%2FRIzr-0Smkmg3dcAmBedb7FDoofVtML0mW5AtG8ixVmh9EDh9E8zPq7toYW243x3PChxCztcSxg%3Ds900-c-k-c0x00ffffff-no-rj" height="900" class="m-0" width="900"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.youtube.com/channel/UC_tS0kNJZXkMZn2mRJRTA_g" rel="noopener noreferrer" class="c-link"&gt;
            
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I share my insights and tips on software engineering topics such as web development, databases, cloud computing, and more.
Hi, I'm Mihai and I'm a full-stack software engineer with a passion for creating innovative and user-friendly solutions using web and cloud technologies. I have a home lab where I like to experiment with Kubernetes and other cutting-edge technologies. I'm also a big fan of open-source software.
When I'm not coding, I enjoy photography, reading books, learning about finance and entrepreneurship, and watching movies.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.youtube.com%2Fs%2Fdesktop%2F513a5249%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          youtube.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Transitioning from Node.js to Go:
&lt;/h2&gt;

&lt;p&gt;While I found several similarities between Go and Node.js, particularly in their approach to concurrency and support for functional programming paradigms, there are also some notable differences that shaped my transition experience. Initially, I felt a bit disoriented switching between the two languages, but as I gained more experience with Go, the transition became smoother.&lt;/p&gt;

&lt;p&gt;Go's strict type system and compilation process were significant changes from Node.js with TypeScript.  While TypeScript introduces static typing to JavaScript, Go's type system is more strict and enforced during compilation.  This stricter approach reduces runtime errors and improves code reliability.  Go's compilation process also differs from TypeScript's transpilation process.  Go compiles directly to machine code, resulting in faster execution speeds compared to TypeScript, which is transpiled to JavaScript that then runs on a JavaScript engine.&lt;/p&gt;

&lt;p&gt;A big difference was Go's minimalist approach to error handling. Unlike Node.js's reliance on exceptions, Go treats errors as values, requiring explicit handling. This approach, while initially surprising, encouraged me to write more robust and error-aware code. It also made me realize the importance of carefully considering error handling strategies in my Node.js projects. I must say that I much prefer Go’s approach to error handling!&lt;/p&gt;

&lt;p&gt;Go's built-in support for concurrency through goroutines and channels was a significant advantage over Node.js's single-threaded model with its event loop and asynchronous callbacks. Goroutines, Go's lightweight concurrency units, allowed me to write concurrent code without the overhead of managing threads manually.  They are managed by the Go runtime, and multiple goroutines can be multiplexed onto a smaller number of OS threads.  This is unlike Node.js, where the developer has to make use of the event loop and manage asynchronous code and callbacks.  Channels, the communication mechanism between goroutines, provided a safe and efficient way to synchronize data between concurrent processes.  They can be used to pass data and also to signal that a certain task is done.&lt;/p&gt;

&lt;p&gt;For other Node.js developers looking to learn Go, I recommend embracing the type system, understanding the concurrency model, and adopting Go’s error handling approach. There are plenty of resources available online, including tutorials, documentation, and community forums, to help you get started with Go. You can also check out my video about how I started learning Go:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Wv-vgKk-3ds"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Learning Go
&lt;/h2&gt;

&lt;p&gt;Learning Go has been a rewarding experience, and I encourage other Node.js developers to explore this powerful language. By taking this step, you'll be investing in your own growth and development as a software engineer. Here are some of the benefits I've gained from learning Go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved performance and scalability of applications&lt;/strong&gt;: Go's compiled nature and efficient memory management translate to faster execution speeds and reduced resource consumption. This can lead to significant improvements in application performance, especially for high-traffic web services and data-intensive applications.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced understanding of concurrency and parallelism&lt;/strong&gt;: Go's built-in concurrency features, using goroutines and channels, provide an elegant and efficient way to handle concurrent operations. Learning Go can deepen your understanding of concurrency concepts.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased productivity and code maintainability&lt;/strong&gt;: Go's simplicity, strong typing, and tooling contribute to increased developer productivity. The language's focus on readability and clarity makes it easier to write, understand, and maintain code, even in large and complex projects.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposure to a new programming language and its community&lt;/strong&gt;: Learning a new language like Go exposes you to new ideas, paradigms, and approaches to problem-solving. It also opens doors to a vibrant and supportive community of Go developers, where you can learn, collaborate, and contribute.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Go's popularity is a testament to its ability to address the challenges of building modern, high-performance applications. Its performance, concurrency model, simplicity, and tooling have made it a compelling choice for developers across various domains.&lt;/p&gt;

&lt;p&gt;My journey into Go as a Node.js developer has been a rewarding experience. The transition, while initially challenging, has broadened my perspective and enhanced my skill set. I encourage other Node.js developers to consider learning Go and explore its potential for building robust and scalable applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software engineer with a few years of experience under my belt. I'm passionate about writing code and love sharing knowledge with fellow developers. &lt;/p&gt;

&lt;p&gt;My YouTube channel, "Let's Talk Dev," is where I break down complex concepts, share my experiences (both the good and the face-palm moments).&lt;/p&gt;

&lt;p&gt;Connect with me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;https://mihai.ltd&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;YouTube&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;https://www.youtube.com/@letstalkdev&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;https://github.com/mihailtd&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/mihai-farcas-ltd/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Prompt Engineering and Other Bullshit Jobs in Tech</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Wed, 28 Aug 2024 20:31:22 +0000</pubDate>
      <link>https://dev.to/mihailtd/prompt-engineering-and-other-bullshit-jobs-in-tech-4lk4</link>
      <guid>https://dev.to/mihailtd/prompt-engineering-and-other-bullshit-jobs-in-tech-4lk4</guid>
      <description>&lt;p&gt;In the tech industry, we love our buzzwords. "Prompt engineering" is the latest buzzword, a shiny new term for a role that shouldn’t and probably doesn’t exist. As usual, the internet and tech companies have latched onto this buzzword and are now creating entire "prompt engineering" teams.&lt;/p&gt;

&lt;p&gt;I'm calling BS. Sure, there's a skill to getting chatbots to spit out what you want, but it's not that hard to do, nor does it deserve its own “role” in a company. It's more like knowing how to sweet-talk or clearly define what you want. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Bullshit Job Phenomenon in Tech
&lt;/h2&gt;

&lt;p&gt;Bullshit jobs are not a new phenomenon in tech. Long before "prompt engineer" became a thing, the tech world was already a breeding ground for pointless positions.&lt;/p&gt;

&lt;p&gt;The Compliance Officer Who Clicks "Approve": In the wake of scandals and regulations, companies hired hordes of compliance officers. Many of them spent their days rubber-stamping documents, ensuring the illusion of oversight rather than actual scrutiny.&lt;/p&gt;

&lt;p&gt;The "Innovation Strategist" Who Never Innovates: With titles like "Chief Innovation Officer" or "VP of Disruption," these folks were paid handsomely to brainstorm ideas that rarely see the light of day. Their real job? To make the company look forward-thinking.&lt;/p&gt;

&lt;p&gt;The "Customer Experience Manager" Who Doesn't Talk to Customers: Tasked with improving the customer experience, these employees often have little contact with actual customers. Instead, they focused on internal metrics and reports, creating the illusion of progress without actually making a difference.&lt;/p&gt;

&lt;p&gt;In David Graeber's book Bullshit Jobs, he describes the phenomenon of pointless jobs that exist only to make companies or individuals appear busy and important. Think of all those endless meetings that could've been an email. He argues that these jobs are not only useless but also soul-crushing for the people who hold them.&lt;/p&gt;

&lt;p&gt;The rise of "prompt engineering" seems to fit Graeber's definition of a bullshit job perfectly. It's a job that exists solely because of the hype surrounding AI, not because there's any real need for it. It exists because it sounds cutting-edge and cool, not because it solves real problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Prompt Engineers" Actually Do (or Don't Do)
&lt;/h2&gt;

&lt;p&gt;So, you're probably wondering, what do these so-called "prompt engineers" actually do all day? Well, the truth is, it's not as glamorous as it sounds. The reality is that much of what's touted as "prompt engineering" is just the same old stuff dressed up in fancy new tech jargon.&lt;/p&gt;

&lt;p&gt;The fundamental skills remain the same. If you were a marketer before, you still need marketing expertise to craft effective prompts for AI. If you were a coder, you still need coding knowledge to guide the AI in generating functional code. AI is a tool, not a replacement for domain knowledge.&lt;/p&gt;

&lt;p&gt;It boils down to crafting clear and specific instructions for AI models. Think of it like giving directions to a particularly literal-minded friend. You need to be explicit, avoid ambiguity, and anticipate potential misunderstandings. It's less about engineering and more about effective communication.&lt;/p&gt;

&lt;p&gt;Of course, there are some nuances involved. Prompt engineers might experiment with different phrasing, iterate on instructions, and fine-tune parameters to optimize the AI's output. But let's be honest, it's not exactly rocket science. If you can write a clear email or follow a recipe, you've got the basic skills down.&lt;/p&gt;

&lt;p&gt;You can confidently dismiss the "prompt engineer" fad. Prompting LLMs or AI is not a job; it's a skill, and a basic one at that. It certainly won't be a key differentiator in the job market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Expertise vs. Prompt Engineering: What Employers Really Want?
&lt;/h2&gt;

&lt;p&gt;Let's cut through the BS. The real value in today's AI-driven world isn't in "prompt engineering" – it's in good old-fashioned domain expertise. AI is a tool, and like any tool, it's only as good as the person wielding it. You can give a monkey a paintbrush, but it won't create a masterpiece - actually never mind, that would probably sell for millions. Similarly, you can give someone with zero marketing experience the ability to prompt an AI, but they won't generate a winning campaign.&lt;/p&gt;

&lt;p&gt;The hype around "prompt engineering" might be driven by the same factors that create bullshit jobs. It's a shiny new title that creates an illusion of specialized knowledge and importance, masking the reality that the core tasks remain unchanged. Companies might be tempted to hire "prompt engineers" to project an image of being at the forefront of AI innovation, even if the role itself adds little genuine value.&lt;/p&gt;

&lt;p&gt;Employers ultimately care about results. They might think they need people with AI prompting skills, but what they really need is someone who can deliver. And delivering results requires experience, expertise, and intelligence. AI can assist in the process, but it can't replace the human element that drives success.&lt;/p&gt;

&lt;p&gt;The future of work isn't about becoming a "prompt engineer." It's about becoming an expert in your field and learning how to leverage AI to enhance your skills and productivity. AI can automate mundane tasks, generate ideas, and provide insights, but it can't replace the critical thinking, creativity, and problem-solving skills that come with true expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Work in the Age of AI
&lt;/h2&gt;

&lt;p&gt;The future belongs to those who can adapt and evolve, not those who chase fleeting trends. Instead, invest in yourself and your skills. Focus on building your knowledge in your chosen field. The real winners in the age of AI will be those who combine their domain expertise, with their unique set of skills and experience and use AI as a tool to enhance these.&lt;/p&gt;

&lt;p&gt;"Prompt engineering" is largely a myth. Don't be fooled by the hype. Focus on what you're good at, and let the AI tools help you where they actually can. The essence of meaningful work lies in its ability to create value, solve problems, and contribute to the world. AI can be a powerful ally in this endeavor, but it's the human element - our knowledge, creativity, and passion - that will ultimately define the future of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm Mihai Farcas, a software engineer with a few years of experience under my belt. I'm passionate about writing code and love sharing knowledge with fellow developers. &lt;/p&gt;

&lt;p&gt;My YouTube channel, "Let's Talk Dev," is where I break down complex concepts, share my experiences (both the good and the face-palm moments).&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.youtube.com/@letstalkdev" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fyt3.googleusercontent.com%2FRIzr-0Smkmg3dcAmBedb7FDoofVtML0mW5AtG8ixVmh9EDh9E8zPq7toYW243x3PChxCztcSxg%3Ds900-c-k-c0x00ffffff-no-rj" height="900" class="m-0" width="900"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer" class="c-link"&gt;
            Let's Talk Dev - YouTube
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I share my insights and tips on software engineering topics such as web development, databases, cloud computing, and more.
Hi, I'm Mihai and I'm a full-stack software engineer with a passion for creating innovative and user-friendly solutions using web and cloud technologies. I have a home lab where I like to experiment with Kubernetes and other cutting-edge technologies. I'm also a big fan of open-source software.
When I'm not coding, I enjoy photography, reading books, learning about finance and entrepreneurship, and watching movies.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.youtube.com%2Fs%2Fdesktop%2F513a5249%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          youtube.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Connect with me:&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://mihai.ltd" rel="noopener noreferrer"&gt;https://mihai.ltd&lt;/a&gt;&lt;br&gt;
YouTube: &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer"&gt;https://www.youtube.com/@letstalkdev&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mihailtd" rel="noopener noreferrer"&gt;https://github.com/mihailtd&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/mihai-farcas-ltd/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/mihai-farcas-ltd/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>career</category>
    </item>
    <item>
      <title>Simplify PostgreSQL Deployments with Kubernetes &amp; GitOps (with Crunchy Data Operator)</title>
      <dc:creator>Mihai Farcas</dc:creator>
      <pubDate>Mon, 22 Jul 2024 21:17:15 +0000</pubDate>
      <link>https://dev.to/mihailtd/simplify-postgresql-deployments-with-kubernetes-gitops-with-crunchy-data-operator-4hca</link>
      <guid>https://dev.to/mihailtd/simplify-postgresql-deployments-with-kubernetes-gitops-with-crunchy-data-operator-4hca</guid>
      <description>&lt;p&gt;Ever felt frustrated with the complexity of running PostgreSQL on Kubernetes? Manually setting up data volumes, services, deployments, managing backups, managing secrets holding credentials, not to mention high availability... There is a lot of complexity in running stateful applications in Kubernetes. This is especially true if you're aiming to achieve this in a declarative GitOps way.&lt;/p&gt;

&lt;p&gt;There is a way to simplify this process, to deploy PostgreSQL declaratively, and have a production-ready setup with just a few lines of declarative yaml.&lt;/p&gt;

&lt;p&gt;That's where the Crunchy Data Postgres Operator comes in.&lt;br&gt;
In this article, let's deploy PostgreSQL declaratively, creating a production-ready setup on Kubernetes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you'd rather watch than read:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Y2V5HqCqx34"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Crunchy Data Postgres Operator
&lt;/h2&gt;

&lt;p&gt;To follow along, you need to have a kubernetes cluster running. On my local machine I will be using Rancher Desktop - see my video on that topic. Or you can do so in the cloud or even in your own homelab, and wouldn’t you know, I have a video on how to easily set up a kubernetes homelab as well.&lt;/p&gt;

&lt;p&gt;Let’s get back to the task at hand. Installing PGO is not that complicated, however it presented a few challenges when I initially started out, especially since I was a beginner in Kubernetes back then. So I am aiming to simplify this process for you.&lt;/p&gt;

&lt;p&gt;To help us out Crunchy has prepared a &lt;a href="https://github.com/CrunchyData/postgres-operator-examples" rel="noopener noreferrer"&gt;github repo with code examples&lt;/a&gt;. You can find that in the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/latest" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; from the documentation.&lt;/p&gt;

&lt;p&gt;Let’s clone that repo locally and see what’s inside.&lt;/p&gt;

&lt;p&gt;There are a couple of ways to install PGO, or actually most things in kubernetes. One is through Helm and one is through Kustomize.&lt;/p&gt;

&lt;p&gt;I will use Kustomize, it is the closest thing to native kubernetes manifests and I believe the easiest to figure out what exactly is being installed.&lt;/p&gt;

&lt;p&gt;You'll want to check out the folder conveniently named &lt;code&gt;install&lt;/code&gt;. And yet again, there are a couple of ways of installing it. One is the single namespace install, where PGO will manage Postgres instances in, well, a single namespace, and the other one is a cluster-wide installation where PGO can manage postgres instances in any namespace in the cluster. The main difference is in the permissions you give PGO within your cluster. &lt;/p&gt;

&lt;p&gt;This type of cluster-wide operator is important and we will see that later on we'll talk about the interesting topic of Platform Development.&lt;/p&gt;

&lt;p&gt;The cluster-wide installation manifests can be found in the &lt;code&gt;default&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Here we can check the manifest file, it references other manifests, but we can understand what it’s trying to do: install a CRD or a custom resource definition, which is how you extend the kubernetes API, it applies some role based access controls (RBAC), and some thing called “manager” which is just a deployment, a pod that will run an image with the code that makes all of this possible.&lt;/p&gt;

&lt;p&gt;OK let’s try to apply these manifests, &lt;code&gt;kubectl apply -k ./kustomize/install/default&lt;/code&gt; and here we get the first error. The annotation is too long. To fix it you just need server-side apply. I added the --force-conflicts flag as well but that’s optional. Here is the final command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl apply -k ./kustomize/install/default --server-side --force-conflicts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And that’s it, we now have the Crunchy Data Postgres Operator installed!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying PostgreSQL using PGO
&lt;/h2&gt;

&lt;p&gt;In the examples repository you can find manifests that showcase the options you have when deploying PostgreSQL. Let's take a look at &lt;code&gt;/kustomize/postgres/postgres.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With PGO installed we can now create a custom resource of kind &lt;code&gt;PostgresCluster&lt;/code&gt;.&lt;br&gt;
In the manifest we can define the Postgres image and version to use, in this case version 16.&lt;br&gt;
We can have multiple instances in the same manifest and each instance can be a replica set with multiple replicas.&lt;br&gt;
For example here there is a single instance named &lt;code&gt;instance1&lt;/code&gt;.&lt;br&gt;
We need to allocate some storage - 1 gb should be enough.&lt;/p&gt;

&lt;p&gt;Another required field to have a minimal postgres cluster is the &lt;code&gt;backup&lt;/code&gt; configuration. It’s nice that they "force" you to have at least a minimal backup configuration. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a production scenario the backups should be stored at least on a separate drive or even better, offsite or in the cloud.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are a bunch of options you can configure here, and after we apply the manifest, PGO will take care of backups automatically.&lt;/p&gt;

&lt;p&gt;As you can see there are a few pods that started, you can see one pod for that postgres instance. There is also a backup job that ran immediately and now it’s stopped. It also created some kubernetes services so you can connect to this postgres instance.&lt;/p&gt;

&lt;p&gt;Speaking of connecting to postgres, one of the best things about this operator is that it manages credentials in kubernetes secrets automatically!&lt;/p&gt;

&lt;p&gt;You should see a secret for each user configured for the database. Let’s check one of these secrets out to see what’s in there: a few properties, basically everything you need to successfully connect to the database!&lt;/p&gt;

&lt;p&gt;No need to manually configure secrets, this is all done for us!&lt;/p&gt;

&lt;p&gt;Actually let me show you how easy it is to just create a user and a new database.&lt;br&gt;
There is a &lt;code&gt;users&lt;/code&gt; key in the manifest, and we can give it a name, we can specify the databases this new user has access to. The nice thing is that if the database doesn’t exist PGO will create it. Let’s call it testdb. And we can give this new user SUPERUSER access:&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;users&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;test&lt;/span&gt;
      &lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;testdb&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SUPERUSER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now it’s as simple as applying this new manifest and there we go, a new secret has been created for this new user with all the credentials. &lt;br&gt;
More importantly behind the scene, all the required operations were done for us, creating the database, creating the user and so on. And the beauty of it all is that it’s all declarative!&lt;/p&gt;
&lt;h3&gt;
  
  
  Deploying PGAdmin
&lt;/h3&gt;

&lt;p&gt;I’d really love to test all of this in a visual way, if only there was a UI interface to see what’s going on in a Postgres database. Oh wait, PGO can also manage a PGAdmin installation for us! Let’s check out the manifest for that: &lt;code&gt;./kustomize/pgadmin/pgadmin.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As you can see the other custom resource that this operator installed is of kind &lt;code&gt;PGAdmin&lt;/code&gt;.&lt;br&gt;
Let’s just apply this example manifest. We can see a pod PGAdmin was created, let’s port forward and open it in a browser.&lt;/p&gt;

&lt;p&gt;Credentials for the PGAdmin web interface are also stored in a kubernetes secret automatically.&lt;/p&gt;
&lt;h3&gt;
  
  
  High Availability
&lt;/h3&gt;

&lt;p&gt;Scaling a PostgreSQL instance with PGO is as easy as flipping an integer:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-operator.crunchydata.com/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PostgresCluster&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hippo-ha&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.3-1&lt;/span&gt;
  &lt;span class="na"&gt;postgresVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
  &lt;span class="na"&gt;instances&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;pgha1&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Just change the number of replicas to whatever you need, and PGO will take care of it. See &lt;code&gt;./kustomize/high-availability/ha-postgres.yaml&lt;/code&gt; for a more complete example that also includes &lt;code&gt;pgbouncer&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is a Kubernetes Operator?
&lt;/h2&gt;

&lt;p&gt;Kubernetes is great at managing stateless applications, it has everything you need out of the box. But stateful applications like PostgreSQL, which store persistent data and require special care and procedures to run, that’s a bit more tricky. This is where Kubernetes Operators come in. They are software extensions that enhance Kubernetes' capabilities, specifically designed to automate the management of complex, stateful applications like PostgreSQL databases.&lt;/p&gt;

&lt;p&gt;Like we saw earlier, operators extend the kubernetes API with new types of resources, in this case we had the &lt;code&gt;PostgresCluster&lt;/code&gt; and the &lt;code&gt;PGAdmin&lt;/code&gt; resource types.&lt;/p&gt;

&lt;p&gt;The operators usually also run a container in the background and can handle certain administrative tasks for you, for example they can scale Postgres instances, automate backup and restores, disaster recovery etc.&lt;/p&gt;

&lt;p&gt;And most importantly they can do so in a declarative way!&lt;/p&gt;
&lt;h2&gt;
  
  
  Platform Development
&lt;/h2&gt;

&lt;p&gt;This leads me to the other topic I wanted to cover in the video and that is &lt;strong&gt;Platform Development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is a concept where you have a platform team that manages a Kubernetes cluster that has all of these operators installed.&lt;/p&gt;

&lt;p&gt;This platform then enables self-provisioning and managing of resources without complex scripting, manual actions - it’s all done through declarative configuration!&lt;/p&gt;

&lt;p&gt;This is especially important for a few use cases, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spinning up new environments like staging or test environments&lt;/li&gt;
&lt;li&gt;Accelerating development cycles and productivity&lt;/li&gt;
&lt;li&gt;Faster iteration on new ideas&lt;/li&gt;
&lt;li&gt;Implementing GitOps principles ensuring consistency, repeatability and auditability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So there you have it! The Crunchy Data Postgres Operator takes the complexity out of running PostgreSQL on Kubernetes. Head over to their GitHub page and give it a try. I'd love to hear about your experiences in the comments below!&lt;/p&gt;

&lt;p&gt;Don't forget to subscribe to my youtube channel for more content like this!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.youtube.com/@letstalkdev" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fyt3.googleusercontent.com%2FRIzr-0Smkmg3dcAmBedb7FDoofVtML0mW5AtG8ixVmh9EDh9E8zPq7toYW243x3PChxCztcSxg%3Ds900-c-k-c0x00ffffff-no-rj" height="900" class="m-0" width="900"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.youtube.com/@letstalkdev" rel="noopener noreferrer" class="c-link"&gt;
            Let's Talk Dev - YouTube
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I share my insights and tips on software engineering topics such as web development, databases, cloud computing, and more.
Hi, I'm Mihai and I'm a full-stack software engineer with a passion for creating innovative and user-friendly solutions using web and cloud technologies. I have a home lab where I like to experiment with Kubernetes and other cutting-edge technologies. I'm also a big fan of open-source software.
When I'm not coding, I enjoy photography, reading books, learning about finance and entrepreneurship, and watching movies.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.youtube.com%2Fs%2Fdesktop%2F513a5249%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          youtube.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>postgres</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
