<?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: Tony Miller</title>
    <description>The latest articles on DEV Community by Tony Miller (@tmlr).</description>
    <link>https://dev.to/tmlr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F777402%2F3720094b-aa92-4fb3-81ec-77757ae2cc7b.png</url>
      <title>DEV Community: Tony Miller</title>
      <link>https://dev.to/tmlr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tmlr"/>
    <language>en</language>
    <item>
      <title>The Definitive Guide to _safe_ Docker-in-Docker with Gitea Actions</title>
      <dc:creator>Tony Miller</dc:creator>
      <pubDate>Fri, 29 Aug 2025 14:41:58 +0000</pubDate>
      <link>https://dev.to/tmlr/the-definitive-guide-to-safe-docker-in-docker-with-gitea-actions-331l</link>
      <guid>https://dev.to/tmlr/the-definitive-guide-to-safe-docker-in-docker-with-gitea-actions-331l</guid>
      <description>&lt;h1&gt;
  
  
  The Definitive Guide to Docker-in-Docker with Gitea Actions
&lt;/h1&gt;

&lt;p&gt;It has come to my attention that someone is wrong on the &lt;em&gt;Internet&lt;/em&gt;. Actually, not just someone—seemingly &lt;strong&gt;everyone&lt;/strong&gt; writing guides about setting up Docker-in-Docker (DIND) with Gitea Actions. The multitudes of tutorials, blog posts, and StackOverflow answers all seem to miss critical architectural limitations and security considerations that make their solutions either incomplete, insecure, or simply non-functional in real-world scenarios.&lt;/p&gt;

&lt;p&gt;This guide presents a &lt;strong&gt;complete, almost production-ready example&lt;/strong&gt; for isolated Docker-in-Docker CI/CD using Gitea Actions with proper security boundaries and full functionality.&lt;/p&gt;

&lt;p&gt;You can find all the examples mentioned here in &lt;a href="https://github.com/tnymlr/gitea-actions-docker-example" rel="noopener noreferrer"&gt;this repository on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Enterprise Security Warning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This setup is intended for development and testing environments.&lt;/strong&gt; For true enterprise-grade production deployments, additional security measures are required:&lt;/p&gt;

&lt;h3&gt;
  
  
  Critical Security Enhancements Needed for Enterprise-grade Production
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network Firewall Protection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy firewall rules to isolate the CI/CD network from internal corporate networks&lt;/li&gt;
&lt;li&gt;Implement egress filtering to prevent build containers from accessing internal services&lt;/li&gt;
&lt;li&gt;Use network segmentation to contain potential container breakouts&lt;/li&gt;
&lt;li&gt;Consider running the entire stack in a separate VLAN or VPC&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Container Image Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gitea needs a feature that only white listed images are allowed to run in priveleged-mode&lt;/li&gt;
&lt;li&gt;Only allow pre-approved, security-scanned base images&lt;/li&gt;
&lt;li&gt;Implement image signing and verification workflows&lt;/li&gt;
&lt;li&gt;Regular vulnerability scanning of all container images&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DIND Image Hardening&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove unnecessary packages and tools from the custom DIND image&lt;/li&gt;
&lt;li&gt;Implement read-only root filesystem where possible&lt;/li&gt;
&lt;li&gt;Use distroless or minimal base images&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Plenty more with various compliance stuff but the above state is a good start.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The configuration presented here prioritizes functionality and ease of setup over maximum security hardening.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements &amp;amp; Use Case
&lt;/h2&gt;

&lt;p&gt;We need a CI/CD environment that provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complete isolation&lt;/strong&gt; from the host Docker daemon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker functionality&lt;/strong&gt; available to both services and build steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-contained deployment&lt;/strong&gt; with no external dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper security boundaries&lt;/strong&gt; between jobs and the host system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Docker API access&lt;/strong&gt; for build, test, and deployment workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: Gitea Actions Limitations
&lt;/h2&gt;

&lt;p&gt;Gitea's &lt;code&gt;act_runner&lt;/code&gt; is based on the excellent &lt;code&gt;nektos/act&lt;/code&gt; project, but it has several critical limitations when compared to GitHub Actions:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Incomplete Services Support
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incomplete &lt;code&gt;volumes&lt;/code&gt; mounting&lt;/strong&gt; capability for services in workflow YAML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited &lt;code&gt;options&lt;/code&gt; support&lt;/strong&gt; compared to full &lt;code&gt;docker run&lt;/code&gt; functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;command&lt;/code&gt; override&lt;/strong&gt; support in the &lt;code&gt;services:&lt;/code&gt; section&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Docker Configuration Challenges
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Cannot mount &lt;code&gt;daemon.json&lt;/code&gt; configuration files into service containers&lt;/li&gt;
&lt;li&gt;No way to inject custom Docker daemon startup parameters&lt;/li&gt;
&lt;li&gt;Services are treated as immutable "black boxes"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. GitHub Actions Parity Issues
&lt;/h3&gt;

&lt;p&gt;GitHub Actions itself doesn't support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt; overrides in the &lt;code&gt;services:&lt;/code&gt; section&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Custom DIND Images Are Required
&lt;/h2&gt;

&lt;p&gt;Standard &lt;code&gt;docker:dind&lt;/code&gt; images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listen on Unix socket by default (&lt;code&gt;/var/run/docker.sock&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Have TLS enabled by default (requires certificates)&lt;/li&gt;
&lt;li&gt;Cannot be configured via environment variables for network settings&lt;/li&gt;
&lt;li&gt;Cannot be customized through workflow YAML due to services limitations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our solution:&lt;/strong&gt; Build a custom DIND image with hardcoded configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; docker:dind&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["dockerd", "--host", "tcp://0.0.0.0:2376", "--tls=false"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture Overview: Triple-Nested Isolation
&lt;/h2&gt;

&lt;p&gt;Our architecture provides three layers of Docker isolation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Host Docker&lt;/strong&gt; (docker-compose level) - Orchestrates the entire CI/CD stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runner DIND&lt;/strong&gt; (act_runner execution environment) - Provides Docker services for workflows
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build DIND&lt;/strong&gt; (workflow build steps) - Enables Docker operations within build containers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This triple nesting is essential because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Services&lt;/strong&gt; run in the runner's Docker daemon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build steps&lt;/strong&gt; run inside containers with no Docker daemon access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each layer&lt;/strong&gt; provides different security boundaries and functional contexts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Custom DIND Image
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dockerfile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; docker:dind&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --start-period=30s --retries=3 \&lt;/span&gt;
    CMD docker version || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["dockerd", "--host", "tcp://0.0.0.0:2376", "--tls=false"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Configure Gitea for Actions
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; The &lt;code&gt;INTERNAL_TOKEN&lt;/code&gt; and &lt;code&gt;JWT_SECRET&lt;/code&gt; values in the example &lt;code&gt;app.ini&lt;/code&gt; below are provided for convenience to make this docker-compose example work out-of-the-box. &lt;strong&gt;In production deployments, these tokens MUST be regenerated using fresh, cryptographically secure random values.&lt;/strong&gt; Never use these example tokens in any environment beyond local development and testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;app.ini:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;APP_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Gitea: Git with a cup of tea&lt;/span&gt;
&lt;span class="py"&gt;RUN_MODE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
&lt;span class="py"&gt;WORK_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea&lt;/span&gt;

&lt;span class="nn"&gt;[actions]&lt;/span&gt;
&lt;span class="py"&gt;ENABLED&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[repository]&lt;/span&gt;
&lt;span class="py"&gt;ROOT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/git/repositories&lt;/span&gt;

&lt;span class="nn"&gt;[repository.local]&lt;/span&gt;
&lt;span class="py"&gt;LOCAL_COPY_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/tmp/local-repo&lt;/span&gt;

&lt;span class="nn"&gt;[repository.upload]&lt;/span&gt;
&lt;span class="py"&gt;TEMP_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/uploads&lt;/span&gt;

&lt;span class="nn"&gt;[server]&lt;/span&gt;
&lt;span class="py"&gt;APP_DATA_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea&lt;/span&gt;
&lt;span class="py"&gt;DOMAIN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="py"&gt;SSH_DOMAIN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="py"&gt;HTTP_PORT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3000&lt;/span&gt;
&lt;span class="py"&gt;ROOT_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;
&lt;span class="py"&gt;LOCAL_ROOT_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;http://gitea:3000&lt;/span&gt;
&lt;span class="py"&gt;DISABLE_SSH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;SSH_PORT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2222&lt;/span&gt;
&lt;span class="py"&gt;SSH_LISTEN_PORT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;22&lt;/span&gt;
&lt;span class="py"&gt;LFS_START_SERVER&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[database]&lt;/span&gt;
&lt;span class="py"&gt;PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/gitea.db&lt;/span&gt;
&lt;span class="py"&gt;DB_TYPE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sqlite3&lt;/span&gt;
&lt;span class="py"&gt;HOST&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;localhost:3306&lt;/span&gt;
&lt;span class="py"&gt;NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;gitea&lt;/span&gt;
&lt;span class="py"&gt;USER&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
&lt;span class="py"&gt;PASSWD&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
&lt;span class="s"&gt;LOG_SQL = false&lt;/span&gt;

&lt;span class="nn"&gt;[indexer]&lt;/span&gt;
&lt;span class="py"&gt;ISSUE_INDEXER_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/indexers/issues.bleve&lt;/span&gt;

&lt;span class="nn"&gt;[session]&lt;/span&gt;
&lt;span class="py"&gt;PROVIDER_CONFIG&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/sessions&lt;/span&gt;

&lt;span class="nn"&gt;[picture]&lt;/span&gt;
&lt;span class="py"&gt;AVATAR_UPLOAD_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/avatars&lt;/span&gt;
&lt;span class="py"&gt;REPOSITORY_AVATAR_UPLOAD_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/repo-avatars&lt;/span&gt;

&lt;span class="nn"&gt;[attachment]&lt;/span&gt;
&lt;span class="py"&gt;PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/attachments&lt;/span&gt;

&lt;span class="nn"&gt;[log]&lt;/span&gt;
&lt;span class="py"&gt;MODE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;console&lt;/span&gt;
&lt;span class="py"&gt;LEVEL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
&lt;span class="py"&gt;ROOT_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/gitea/log&lt;/span&gt;

&lt;span class="nn"&gt;[security]&lt;/span&gt;
&lt;span class="py"&gt;INSTALL_LOCK&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;REVERSE_PROXY_LIMIT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;REVERSE_PROXY_TRUSTED_PROXIES&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;*&lt;/span&gt;
&lt;span class="py"&gt;INTERNAL_TOKEN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3NTY0NzI0OTF9.lXfJEgeQCkXcQx3VKm-TwLQktTYrccm_JK1P0xiDmEw&lt;/span&gt;

&lt;span class="nn"&gt;[service]&lt;/span&gt;
&lt;span class="py"&gt;DISABLE_REGISTRATION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;REQUIRE_SIGNIN_VIEW&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[lfs]&lt;/span&gt;
&lt;span class="py"&gt;PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/data/git/lfs&lt;/span&gt;

&lt;span class="nn"&gt;[oauth2]&lt;/span&gt;
&lt;span class="py"&gt;JWT_SECRET&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;nq7Fpd5bAPFHOWHZJJah2rKfXdC3pKaF0pMgtaQwAdw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Docker Compose Configuration
&lt;/h3&gt;

&lt;p&gt;The complete docker-compose.yml orchestrates six services with proper dependency management:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker-compose.yml:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gitea&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;gitea/gitea:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitea&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2222:22"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitea_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./app.ini:/data/gitea/conf/app.ini:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/timezone:/etc/timezone:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USER_UID=1000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USER_GID=1000&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/api/healthz"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;dind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dind&lt;/span&gt;
    &lt;span class="na"&gt;privileged&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;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dind_data:/var/lib/docker&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOCKER_HOST=tcp://localhost:2376&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docker"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&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;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;registry&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:2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;registry_data:/var/lib/registry&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wget"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--quiet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--tries=1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--spider"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:5000/v2/"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;image-builder&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;docker:dind&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;image-builder&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;registry&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="na"&gt;dind&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="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Dockerfile:/workspace/Dockerfile&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dind_data:/var/lib/docker&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/workspace&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;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;sh -c "&lt;/span&gt;
        &lt;span class="s"&gt;export DOCKER_HOST=tcp://dind:2376&lt;/span&gt;
        &lt;span class="s"&gt;export DOCKER_TLS_VERIFY=\"\"&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Building and pushing DIND image to local registry...'&lt;/span&gt;
        &lt;span class="s"&gt;docker build -t registry:5000/dind-plain:latest .&lt;/span&gt;
        &lt;span class="s"&gt;docker push registry:5000/dind-plain:latest&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Image build and push complete!'&lt;/span&gt;
      &lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;

  &lt;span class="na"&gt;runner-configurator&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;gitea/gitea:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runner-configurator&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;gitea&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="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;runner_config:/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./app.ini:/data/gitea/conf/app.ini:ro&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;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;sh -c "&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Gitea is healthy, proceeding...'&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Generating runner token...'&lt;/span&gt;
        &lt;span class="s"&gt;su - git -c 'cd /data &amp;amp;&amp;amp; /usr/local/bin/gitea actions generate-runner-token' &amp;gt; /config/token&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Creating runner config...'&lt;/span&gt;
        &lt;span class="s"&gt;GITEA_IP=$$(getent hosts gitea | awk '{print $$1}')&lt;/span&gt;
        &lt;span class="s"&gt;REGISTRY_IP=$$(getent hosts registry | awk '{print $$1}')&lt;/span&gt;
        &lt;span class="s"&gt;echo \"Resolved Gitea IP: $$GITEA_IP\"&lt;/span&gt;
        &lt;span class="s"&gt;cat &amp;gt; /config/config.yaml &amp;lt;&amp;lt; EOF&lt;/span&gt;
      &lt;span class="s"&gt;log:&lt;/span&gt;
        &lt;span class="s"&gt;level: info&lt;/span&gt;
      &lt;span class="s"&gt;runner:&lt;/span&gt;
        &lt;span class="s"&gt;file: .runner&lt;/span&gt;
        &lt;span class="s"&gt;capacity: 1&lt;/span&gt;
        &lt;span class="s"&gt;timeout: 3h&lt;/span&gt;
        &lt;span class="s"&gt;insecure: false&lt;/span&gt;
        &lt;span class="s"&gt;fetch_timeout: 5s&lt;/span&gt;
        &lt;span class="s"&gt;fetch_interval: 2s&lt;/span&gt;
        &lt;span class="s"&gt;labels:&lt;/span&gt;
          &lt;span class="s"&gt;- 'ubuntu-latest:docker://gitea/runner-images:ubuntu-latest'&lt;/span&gt;
          &lt;span class="s"&gt;- 'ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04'&lt;/span&gt;
          &lt;span class="s"&gt;- 'ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04'&lt;/span&gt;
          &lt;span class="s"&gt;- 'linux:docker://gitea/runner-images:ubuntu-latest'&lt;/span&gt;
      &lt;span class="s"&gt;cache:&lt;/span&gt;
        &lt;span class="s"&gt;enabled: true&lt;/span&gt;
        &lt;span class="s"&gt;dir: '/tmp/cache'&lt;/span&gt;
        &lt;span class="s"&gt;host: ''&lt;/span&gt;
        &lt;span class="s"&gt;port: 0&lt;/span&gt;
      &lt;span class="s"&gt;container:&lt;/span&gt;
        &lt;span class="s"&gt;network_mode: bridge&lt;/span&gt;
        &lt;span class="s"&gt;enable_ipv6: false&lt;/span&gt;
        &lt;span class="s"&gt;privileged: true&lt;/span&gt;
        &lt;span class="s"&gt;valid_volumes:&lt;/span&gt;
          &lt;span class="s"&gt;- '**'&lt;/span&gt;
        &lt;span class="s"&gt;docker_host: tcp://dind:2376&lt;/span&gt;
        &lt;span class="s"&gt;options: '--add-host=gitea:$$GITEA_IP --add-host=registry:$$REGISTRY_IP'&lt;/span&gt;
      &lt;span class="s"&gt;host:&lt;/span&gt;
        &lt;span class="s"&gt;workdir_parent: /tmp&lt;/span&gt;
      &lt;span class="s"&gt;EOF&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Configuration complete!'&lt;/span&gt;
      &lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;

  &lt;span class="na"&gt;admin-setup&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;gitea/gitea:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin-setup&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;gitea&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="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITEA_URL=http://gitea:3000&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitea_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./app.ini:/data/gitea/conf/app.ini:ro&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;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;sh -c "&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Creating admin user...'&lt;/span&gt;
        &lt;span class="s"&gt;su - git -c '/usr/local/bin/gitea admin user create --admin --username admin --password admin --email admin@localhost.local --must-change-password=false' || echo 'Admin user already exists'&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Admin setup complete!'&lt;/span&gt;
      &lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;

  &lt;span class="na"&gt;runner&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;gitea/act_runner:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runner&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;gitea&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="na"&gt;dind&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="na"&gt;admin-setup&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_completed_successfully&lt;/span&gt;
      &lt;span class="na"&gt;runner-configurator&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_completed_successfully&lt;/span&gt;
      &lt;span class="na"&gt;image-builder&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_completed_successfully&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;runner_config:/config:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;runner_data:/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOCKER_HOST=tcp://dind:2376&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOCKER_TLS_VERIFY=""&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITEA_INSTANCE_URL=http://gitea:3000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITEA_RUNNER_REGISTRATION_TOKEN_FILE=/config/token&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CONFIG_FILE=/config/config.yaml&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;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;sh -c "&lt;/span&gt;
        &lt;span class="s"&gt;echo 'All dependencies ready, token file available...'&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Registering and starting runner...'&lt;/span&gt;
        &lt;span class="s"&gt;act_runner register --config /config/config.yaml --no-interactive&lt;/span&gt;
        &lt;span class="s"&gt;act_runner daemon --config /config/config.yaml&lt;/span&gt;
      &lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gitea_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dind_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runner_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runner_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;registry_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Example Workflow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;.gitea/workflows/test-dind.yml:&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test DIND Integration&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test-docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;

    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;docker&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:5000/dind-plain:latest&lt;/span&gt;

    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp://docker:2376&lt;/span&gt;
      &lt;span class="na"&gt;DOCKER_TLS_VERIFY&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;steps&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;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Wait&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sleep &lt;/span&gt;&lt;span class="m"&gt;10&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;Verify Docker daemon is running&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Testing Docker daemon connection..."&lt;/span&gt;
          &lt;span class="s"&gt;docker info&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;List running containers&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Listing all containers..."&lt;/span&gt;
          &lt;span class="s"&gt;docker ps -a&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 Docker functionality&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Testing basic Docker operations..."&lt;/span&gt;
          &lt;span class="s"&gt;docker run --rm hello-world&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;Verify DIND isolation&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Testing container isolation..."&lt;/span&gt;
          &lt;span class="s"&gt;docker run --rm alpine:latest echo "DIND is working perfectly!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;This architecture provides multiple security boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host Isolation&lt;/strong&gt;: Docker-compose isolates the entire CI/CD stack from the host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runner Isolation&lt;/strong&gt;: Each workflow job gets its own Docker environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Isolation&lt;/strong&gt;: Docker operations in build steps use separate DIND instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Isolation&lt;/strong&gt;: Partial. Services and builds cannot directly access host resources but can access host network.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This guide provides a complete examplt of a production-ready solution for Docker-in-Docker with Gitea Actions. The architecture addresses the fundamental limitations of both GitHub Actions and Gitea's act_runner while providing proper security isolation and full Docker functionality.&lt;/p&gt;

&lt;p&gt;The key insights that most guides miss:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Custom DIND images are required&lt;/strong&gt; due to services configuration limitations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triple-nested isolation&lt;/strong&gt; provides both security and functionality&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>gitea</category>
      <category>githubactions</category>
      <category>dockercompose</category>
    </item>
    <item>
      <title>Achieving 30ms Zsh Startup</title>
      <dc:creator>Tony Miller</dc:creator>
      <pubDate>Tue, 12 Aug 2025 16:14:54 +0000</pubDate>
      <link>https://dev.to/tmlr/achieving-30ms-zsh-startup-40n1</link>
      <guid>https://dev.to/tmlr/achieving-30ms-zsh-startup-40n1</guid>
      <description>&lt;p&gt;Me and Claude went on a dotfiles journey the other day...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flex 💪
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🍎  time zsh -i -c exit
zsh -i -c exit  0.02s user 0.01s system 87% cpu 0.033 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;33 milliseconds.&lt;/strong&gt; That's faster than most people can blink. While others wait half a second&lt;br&gt;
for their shell to load, we're already productive.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Most zsh configurations suffer from slow startup times ranging from 200ms to 500ms or more. Heavy frameworks like Oh-My-Zsh can push startup times beyond 1 second. Our goal was to achieve blazingly&lt;br&gt;
fast shell initialization while maintaining full functionality for a modern development environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Final startup time: 33ms&lt;/strong&gt; - putting us in the top 0.1% of shell performance.&lt;/p&gt;
&lt;h2&gt;
  
  
  Core Optimization Strategy
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. KISS Philosophy: Single-File Architecture
&lt;/h3&gt;

&lt;p&gt;Instead of fragmenting configuration across multiple files, we organized everything within &lt;code&gt;.zshrc&lt;/code&gt; using clear section headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;### BREW&lt;/span&gt;
&lt;span class="c"&gt;### FZF  &lt;/span&gt;
&lt;span class="c"&gt;### COMPLETIONS&lt;/span&gt;
&lt;span class="c"&gt;### NVM&lt;/span&gt;
&lt;span class="c"&gt;### DOCKER&lt;/span&gt;
&lt;span class="c"&gt;### KUBE&lt;/span&gt;
&lt;span class="c"&gt;### PYTHON&lt;/span&gt;
&lt;span class="c"&gt;### RUBY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eliminates the overhead of sourcing multiple files while keeping code organized and maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Aggressive Lazy Loading
&lt;/h3&gt;

&lt;p&gt;The biggest performance wins came from deferring expensive operations until actually needed:&lt;/p&gt;

&lt;h4&gt;
  
  
  NVM (Node Version Manager)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;load_nvm&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;unset&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; npm npx yarn nvm load_nvm 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/bash_completion"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/bash_completion"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
npm&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; load_nvm&lt;span class="p"&gt;;&lt;/span&gt; npm &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
npx&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; load_nvm&lt;span class="p"&gt;;&lt;/span&gt; npx &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
yarn&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; load_nvm&lt;span class="p"&gt;;&lt;/span&gt; yarn &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
nvm&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; load_nvm&lt;span class="p"&gt;;&lt;/span&gt; nvm &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Python Environment (pyenv)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;load_pyenv&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; pyenv &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pyenv init &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pyenv init - &lt;span class="nt"&gt;--no-rehash&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;which pyenv-virtualenv-init &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pyenv virtualenv-init -&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;pyenv virtualenvwrapper
    &lt;span class="nb"&gt;unset&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; load_pyenv
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;pyenv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'load_pyenv &amp;amp;&amp;amp; pyenv'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The Fuck (command correction)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;init_thefuck&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;unalias &lt;/span&gt;fuck
  &lt;span class="nb"&gt;unset&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; init_thefuck
  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;thefuck &lt;span class="nt"&gt;--alias&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  fuck
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;fuck&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"init_thefuck"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Strategic Performance Settings
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Completion Optimization
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fast completion initialization&lt;/span&gt;
compinit &lt;span class="nt"&gt;-C&lt;/span&gt;  &lt;span class="c"&gt;# Skip security checks for speed&lt;/span&gt;

&lt;span class="c"&gt;# Completion caching&lt;/span&gt;
zstyle &lt;span class="s1"&gt;':completion:*'&lt;/span&gt; use-cache on
zstyle &lt;span class="s1"&gt;':completion:*'&lt;/span&gt; cache-path ~/.zsh/cache
zstyle &lt;span class="s1"&gt;':completion:*'&lt;/span&gt; accept-exact &lt;span class="s1"&gt;'*(N)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Disable Expensive Features
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Turn off features that slow startup&lt;/span&gt;
unsetopt AUTO_CD
unsetopt AUTO_PUSHD
unsetopt PUSHD_IGNORE_DUPS
unsetopt FLOW_CONTROL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Smart Git Branch Caching
&lt;/h3&gt;

&lt;p&gt;Instead of running &lt;code&gt;git&lt;/code&gt; commands on every prompt render, we cache branch information:&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="k"&gt;function &lt;/span&gt;git_ps1 &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git rev-parse &lt;span class="nt"&gt;--is-inside-work-tree&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        return
    fi&lt;/span&gt;

    &lt;span class="c"&gt;# Use cached branch info&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$GIT_BRANCH_CACHE&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH_CACHE&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$branch&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$branch&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_BRANCH_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
        &lt;span class="k"&gt;fi
    fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Clear cache when changing directories&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;git_branch_cache_clear&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;unset &lt;/span&gt;GIT_BRANCH_CACHE
&lt;span class="o"&gt;}&lt;/span&gt;
add-zsh-hook chpwd git_branch_cache_clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Conditional Loading
&lt;/h3&gt;

&lt;p&gt;Only load completions and tools that are actually installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;### KUBE&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; kubectl &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl completion zsh&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;### DOCKER&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; docker &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;fpath&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.docker/completions"&lt;/span&gt; &lt;span class="nv"&gt;$fpath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Smart Hooks
&lt;/h3&gt;

&lt;p&gt;Use hooks sparingly and efficiently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Auto-load NVM when entering projects with .nvmrc&lt;/span&gt;
maybe_nvm_after_cd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local dir
  dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;/.nvmrc"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;load_nvm
      nvm use 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
      &lt;/span&gt;&lt;span class="k"&gt;return
    fi
    &lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
add-zsh-hook chpwd maybe_nvm_after_cd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Principles Applied
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lazy Everything&lt;/strong&gt;: Defer expensive operations until needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Aggressively&lt;/strong&gt;: Store computation results and reuse them
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional Loading&lt;/strong&gt;: Only load what's installed and needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single File&lt;/strong&gt;: Avoid sourcing overhead by keeping everything in one organized file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile and Measure&lt;/strong&gt;: Use &lt;code&gt;time zsh -i -c exit&lt;/code&gt; to validate improvements&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What We Avoided
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Oh-My-Zsh&lt;/strong&gt;: Adds 200-400ms overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple sourced files&lt;/strong&gt;: Each &lt;code&gt;source&lt;/code&gt; adds ~5-10ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous tool initialization&lt;/strong&gt;: NVM, pyenv, etc. add 50-200ms each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex prompt calculations&lt;/strong&gt;: Git status checks on every render&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unnecessary zsh options&lt;/strong&gt;: Many defaults are performance killers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Outcome
&lt;/h2&gt;

&lt;p&gt;With these optimizations, we achieved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;33ms startup time&lt;/strong&gt; (top 0.1% performance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full development environment&lt;/strong&gt; (Node, Python, Docker, Kubernetes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich completions&lt;/strong&gt; for all tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainable single-file configuration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero functionality compromised&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: aggressive lazy loading combined with smart caching and conditional execution can&lt;br&gt;
deliver both speed and functionality without compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full config
&lt;/h2&gt;

</description>
      <category>zsh</category>
      <category>performance</category>
      <category>nvm</category>
      <category>dotfiles</category>
    </item>
    <item>
      <title>TIL: How Node's event loop actually works.</title>
      <dc:creator>Tony Miller</dc:creator>
      <pubDate>Wed, 03 Jul 2024 10:54:06 +0000</pubDate>
      <link>https://dev.to/tmlr/til-how-nodes-event-loop-actually-works-3e2h</link>
      <guid>https://dev.to/tmlr/til-how-nodes-event-loop-actually-works-3e2h</guid>
      <description>&lt;p&gt;It has come to my attention that someone is wrong on the Internet. So here’s yet another page about Event Loop in Node, this time it is actually correct.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why would you read that?&lt;br&gt;
Here I’m talking about low level details of Node.JS, what it uses for async IO and how different parts of Node.JS (v8 and others) are  glued together.&lt;br&gt;
Read on if you want to understand Node.JS and the problem it’s solving a bit better.&lt;br&gt;
There’s a bit of C and C++ examples that help to build understanding of what Node.JS is doing behind the scenes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also a warning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not use this to reason about your code in Chrome. Chrome’s even loop is a separate implementation and might work in totally different way. Chrome uses &lt;code&gt;libevent&lt;/code&gt; for event loop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Those are not the droids you’re looking for.
&lt;/h2&gt;

&lt;p&gt;Here are some breaking news for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;there’s no event loop in Node.JS.&lt;/li&gt;
&lt;li&gt;there are no event queue in Node.JS.&lt;/li&gt;
&lt;li&gt;and there’s no micro-tasks queue.&lt;/li&gt;
&lt;li&gt;there’s no thread in which your JavaScript runs continuously. I don’t know how widespread this one is but some people think that JS is just running all the time in a single thread. It is not. It is on and off, on and off, on and off. We enter V8, we exit V8, we enter V8, we exit V8.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get the simple stuff out of the sight first.&lt;/p&gt;

&lt;h3&gt;
  
  
  There’s no micro-tasks queue in Node.JS
&lt;/h3&gt;

&lt;p&gt;Here’s C++ implementation of &lt;code&gt;runMicrotasks()&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;static void RunMicrotasks(const FunctionCallbackInfo&amp;lt;Value&amp;gt;&amp;amp; args) {
  Environment* env = Environment::GetCurrent(args);
  env-&amp;gt;context()-&amp;gt;GetMicrotaskQueue()-&amp;gt;PerformCheckpoint(env-&amp;gt;isolate());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env-&amp;gt;context()-&amp;gt;GetMicrotaskQueue()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is a call into V8 API which actually contains micro-tasks queue. And no, &lt;code&gt;process.nextTick()&lt;/code&gt; does not go in there. &lt;code&gt;process.nextTick()&lt;/code&gt; goes into this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const FixedQueue = require('internal/fixed_queue'); // tasks_queues.js:36
const queue = new FixedQueue(); // tasks_queues.js:55
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it is run by &lt;code&gt;runNextTicks()&lt;/code&gt; and it so happens this one plugged into every place whenever your JS runs pretty much manually. We go into a bit of details about that later.&lt;br&gt;
TL/DR: Micro tasks are V8 thing, managed and implemented by V8, Node.JS C++ code just tells it to run them (&lt;code&gt;PerformCheckpoint()&lt;/code&gt;) and it has nothing to do with &lt;code&gt;process.nextTick()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  There’s no event loop in Node.JS.
&lt;/h3&gt;

&lt;p&gt;But there used to be one 😉.&lt;/p&gt;

&lt;p&gt;Another bits of news: Node.JS is low level programming platform that provides you with access to low level Linux, *BSD (including MacOS) and Windows primitives for asynchronous IO. It wraps those primitives with something called &lt;code&gt;libuv&lt;/code&gt; which is a library that provides simplified API for those low level OS primitives and it allows you to write your callbacks in JavaScript. Besides the automatic memory management of JS the whole thing is waaaaaay more low level (and &lt;em&gt;honest&lt;/em&gt;) than Go’s “gorutines”, any kind of green thread implementation or async/await of C# and Kotlin.&lt;/p&gt;
&lt;h4&gt;
  
  
  libuv
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;libuv&lt;/code&gt; is the event loop. This is the actual library that implements event loop. It splits it into stages and allows you to create handles for each stage and use those handles to schedule callbacks for those stages. It ref counts those handles and keeps on running as long as there are handles.&lt;/p&gt;

&lt;p&gt;For demonstration purposes I have written a &lt;a href="https://github.com/tnymlr/hello-libuv" rel="noopener noreferrer"&gt;very primitive web server&lt;/a&gt; and that’s what we’re going to use to learn.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;libuv&lt;/code&gt; allows you to create as many event loops as you want with the only caveat - one event loop per thread, please and thank you. You don’t have you create your own though, there’s a default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loop = uv_default_loop();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you leave it like that it will exit just like Node exits when there are no stuff to poll left. In fact, Node exits exactly because libuv exits. Or even better: it is not Node who exists, it is &lt;code&gt;libuv&lt;/code&gt;. So we need to give it something for &lt;code&gt;poll&lt;/code&gt; stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_tcp_init(loop, &amp;amp;server);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I’ve got a handle - &lt;code&gt;server&lt;/code&gt; which &lt;code&gt;libuv&lt;/code&gt; will register for &lt;code&gt;poll&lt;/code&gt; stage. Still, nothing to poll yet, gotta set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_ip4_addr("0.0.0.0", PORT, &amp;amp;addr);
uv_tcp_bind(&amp;amp;server, (const struct sockaddr *)&amp;amp;addr, 0);
int err = uv_listen(AS_STREAM(&amp;amp;server), 128, on_new_connection);
if(err) {
    fprintf(stderr, "Listen error: %s\n", uv_strerror(err));
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and after that we can run the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_run(loop, UV_RUN_DEFAULT);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, when Node.JS is initialising it’s doing exactly the same kind of thing. It’s just that it sets up V8, then bootstraps and loads your code. Your code runs and (hopefully) calls some of the APIs that does similar kind of handles and callbacks registration for libuv’s poll stage. It’s just you use Node’s JS API to do that. Only after your file is finished running Node will actually start the event loop.&lt;br&gt;
Now, back to our server, I have registered callback here for new connections - &lt;code&gt;on_new_connection&lt;/code&gt;. So whenever new connection comes, &lt;code&gt;libuv&lt;/code&gt; will execute this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void on_new_connection(uv_stream_t *server, int status) {
    if (status &amp;lt; 0) {
        fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
        return;
    }
    uv_tcp_t *client = malloc(sizeof(uv_tcp_t));
    uv_tcp_init(loop, client);
    if (uv_accept(server, AS_STREAM(client)) == 0) {
        uv_read_start(AS_STREAM(client), alloc_buffer, on_receive);
    } else {
        uv_close(AS_HANDLE(client), on_close);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t read too much into it, what we do here is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accept connection: &lt;code&gt;uv_accept(server, AS_STREAM(client))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;start reading: &lt;code&gt;uv_read_start(AS_STREAM(client), alloc_buffer, on_receive)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, “start reading” part is interesting because that’s where we register another callback (actually two, but we omit one of them): &lt;code&gt;on_receive&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;void on_receive(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
    if (nread &amp;gt; 0) {
        serve_t * serve = malloc(sizeof(serve_t));
        serve-&amp;gt;req = http_inflight_parse(http_inflight_new(buf));
        serve-&amp;gt;client = client;
        serve-&amp;gt;buf = uv_buf_init(serve-&amp;gt;reserved, sizeof(serve-&amp;gt;reserved));
        fs_req_t *fs = malloc(sizeof(fs_req_t));
        fs-&amp;gt;serve = serve;
        uv_fs_open(loop, AS_FS(fs), serve-&amp;gt;req-&amp;gt;path, O_RDONLY, 0, on_open);
    } else if (nread &amp;lt; 0) {
        if (nread != UV_EOF) {
            fprintf(stderr, "Read error %s\n", uv_err_name(nread));
        }
        uv_close(AS_HANDLE(client), on_close);
        free(client);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we could read something then we do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parse the request (:fingers_crossed: it all came in one TCP buffer, as I said very primitive HTTP server).&lt;/li&gt;
&lt;li&gt;figure out which file client wants and open this file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_fs_open(loop, AS_FS(fs), serve-&amp;gt;req-&amp;gt;path, O_RDONLY, 0, on_open);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we register another callback here: &lt;code&gt;on_open&lt;/code&gt;. Now whenever &lt;code&gt;libuv&lt;/code&gt; opens the file it’ll run this &lt;code&gt;on_open&lt;/code&gt; callback. I won’t print it here, it’s bit too long but what it’s doing is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check if the open result is “ok”, not an error.&lt;/li&gt;
&lt;li&gt;look into request to determine the type of the file requested and&lt;/li&gt;
&lt;li&gt;pick appropriate HTTP headers.&lt;/li&gt;
&lt;li&gt;send the headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s right, we don’t start reading it here, only sending headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_write(AS_WRITE(write), serve-&amp;gt;client, &amp;amp;serve-&amp;gt;buf, 1, on_send);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We politely ask &lt;code&gt;libuv&lt;/code&gt; “please dear write this stuff into socket for us and once you’re done tell about it to this guy: &lt;code&gt;on_send&lt;/code&gt;". &lt;code&gt;on_send&lt;/code&gt; will be called once the buffer is completely written into the socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void on_send(uv_write_t *res, int status) {
    write_req_t *write = WRITE_REQ(res);
    serve_t *serve = write-&amp;gt;serve;
    if (status) {
        fprintf(stderr, "Write error %s\n", uv_strerror(status));
    }
    if (write-&amp;gt;done) {
        uv_close(AS_HANDLE(serve-&amp;gt;client), on_close);
        free_serve(serve);
    } else {
        serve-&amp;gt;buf.len = sizeof(serve-&amp;gt;reserved);
        fs_req_t *read = malloc(sizeof(fs_req_t));
        read-&amp;gt;serve = write-&amp;gt;serve;
        uv_fs_read(loop, AS_FS(read), serve-&amp;gt;file, &amp;amp;serve-&amp;gt;buf, 1, -1, on_read);
    }
    free(write);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, a bit of validation and the important part is that we will ask &lt;code&gt;libuv&lt;/code&gt; to start reading the actual file that the client requested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_fs_read(loop, AS_FS(read), serve-&amp;gt;file, &amp;amp;serve-&amp;gt;buf, 1, -1, on_read);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aaaaand, you guessed it, yet another call back: &lt;code&gt;on_read&lt;/code&gt;. This callback is executed by &lt;code&gt;libuv&lt;/code&gt; whenever it is able to read enough data from the file to fill the buffer or whenever it receives &lt;code&gt;EOF&lt;/code&gt; or any other error.&lt;br&gt;
&lt;code&gt;on_read&lt;/code&gt; then validates the state and again asks &lt;code&gt;libuv&lt;/code&gt; to send it down the socket with the same &lt;code&gt;on_send&lt;/code&gt; callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void on_read(uv_fs_t *res) {
    fs_req_t *fs = FS_REQ(res);
    serve_t *serve = fs-&amp;gt;serve;
    uv_fs_req_cleanup(res);
    bool done = false;
    if (res-&amp;gt;result &amp;lt; 0) {
        fprintf(stderr, "Read error: %s\n", uv_strerror(res-&amp;gt;result));
        serve-&amp;gt;buf.len = 0;
        done = true;
    } else if (res-&amp;gt;result == 0) {
        serve-&amp;gt;buf.len = 0;
        done = true;
        uv_fs_close(loop, res, serve-&amp;gt;file, NULL); // synchronous
    } else if (res-&amp;gt;result &amp;gt; 0) {
        serve-&amp;gt;buf.len = res-&amp;gt;result;
    }
    write_req_t *write = malloc(sizeof(write_req_t));
    write-&amp;gt;serve = serve;
    write-&amp;gt;done = done;
    uv_write(AS_WRITE(write), serve-&amp;gt;client, &amp;amp;serve-&amp;gt;buf, 1, on_send);
    uv_fs_req_cleanup(res);
    free(res);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And so it goes, jumping between those callbacks back and forth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stages
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;libuv&lt;/code&gt; runs in stages:&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%2F2oew6504l8p84ue0cxcq.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%2F2oew6504l8p84ue0cxcq.png" alt="libuv stages" width="522" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might’ve seen this picture in Node’s docs but it’s really from &lt;code&gt;libuv&lt;/code&gt; &lt;a href="http://docs.libuv.org/en/v1.x/design.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;What does it have to do with Node.JS? Well, Node.JS does the same. Look at the starting sequence:&lt;/p&gt;

&lt;p&gt;It configures the default loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME); // node.cc:1173
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some time later it calls this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MaybeLocal&amp;lt;Value&amp;gt; LoadEnvironment(
    Environment* env,
    StartExecutionCallback cb) {
  env-&amp;gt;InitializeLibuv();
  env-&amp;gt;InitializeDiagnostics();
  return StartExecution(env, cb);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part for us is: &lt;code&gt;env-&amp;gt;InitializeLibuv();&lt;/code&gt;&lt;br&gt;
It registers a handle for &lt;code&gt;timers&lt;/code&gt; stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CHECK_EQ(0, uv_timer_init(event_loop(), timer_handle()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a handle for &lt;code&gt;check&lt;/code&gt; stage (which runs your &lt;code&gt;setImmediate()&lt;/code&gt;s):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CHECK_EQ(0, uv_check_init(event_loop(), immediate_check_handle()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also creates  a handle for &lt;code&gt;idle&lt;/code&gt; stage that &lt;em&gt;may&lt;/em&gt; run immediates as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CHECK_EQ(0, uv_idle_init(event_loop(), immediate_idle_handle()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why two places to run immediates ask you? Well, here’s a plot twist: the event loop actually &lt;strong&gt;&lt;em&gt;blocks&lt;/em&gt;&lt;/strong&gt;. In the &lt;code&gt;poll&lt;/code&gt; stage it blocks until it is woken up by Linux/BSD/Windows to get data from one of the sockets/descriptors/handles it’s listening. If there are no timers and no idles then it blocks indefinitely. If there are timers it blocks until the next timer. If there’s at least one idle it doesn’t block. So Node.JS uses idle stage and its handle to prevent &lt;code&gt;libuv&lt;/code&gt; from blocking and process your &lt;code&gt;setImmediate()&lt;/code&gt;s ASAP. Note that the &lt;code&gt;check&lt;/code&gt; stage is started (actually “started”, just marked as started) and callback is supplied right away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CHECK_EQ(0, uv_check_start(immediate_check_handle(), CheckImmediate));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn’t happen for the idle stage handle.&lt;br&gt;
It does some more initialisation, reading bootstrapping &lt;code&gt;node.js&lt;/code&gt; and your entry file and running all of that and then finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*exit_code = SpinEventLoop(env).FromMaybe(1); //node_main_instance.cc:140 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where it does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv_run(env-&amp;gt;event_loop(), UV_RUN_DEFAULT); //embed_helpers.cc:36
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;there’s no even loop in Node, it’s from &lt;code&gt;libuv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;there is no constant JS running, it is &lt;code&gt;libuv&lt;/code&gt; that is running&lt;/li&gt;
&lt;li&gt;JS code is only run at the startup and then in the event handlers from libuv.&lt;/li&gt;
&lt;li&gt;Node.JS is just a fancy JS wrapper around &lt;code&gt;libuv&lt;/code&gt; so you don’t have to chase your lost mallocs around all over of the place and so you don’t get too much SEGFAULTs in prod.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Potential questions (at least I asked myself).
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How does &lt;code&gt;process.nextTick()&lt;/code&gt; work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is simple, Node’s C++ side provides an API that is accessible from JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void SetupTimers(const FunctionCallbackInfo&amp;lt;Value&amp;gt;&amp;amp; args) {
  CHECK(args[0]-&amp;gt;IsFunction());
  CHECK(args[1]-&amp;gt;IsFunction());
  auto env = Environment::GetCurrent(args);
  env-&amp;gt;set_immediate_callback_function(args[0].As&amp;lt;Function&amp;gt;());
  env-&amp;gt;set_timers_callback_function(args[1].As&amp;lt;Function&amp;gt;());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice, that callbacks for that C++ function are JavaScript functions from V8. And notice that it adds callbacks to immediate handle (which we know runs on &lt;code&gt;check&lt;/code&gt; and sometimes on &lt;code&gt;idle&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;And it also adds timers callback. So basically it adds callbacks into every stage of even loop when JS code is running.  Why is that important? Well because in node.js bootstrapping script will register couple callbacks for those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Sets two per-Environment callbacks that will be run from libuv:
// - processImmediate will be run in the callback of the per-Environment
//   check handle.
// - processTimers will be run in the callback of the per-Environment timer.
setupTimers(processImmediate, processTimers);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those two come from &lt;code&gt;timers.js&lt;/code&gt; and they execute &lt;code&gt;runNextTicks()&lt;/code&gt; which in turn does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs micro tasks from V8&lt;/li&gt;
&lt;li&gt;runs &lt;code&gt;process.nextTick()&lt;/code&gt; callbacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From what I’m seeing is that JS runs twice per stage, first time in native callback to process IO (runs your code), then another &lt;code&gt;libuv&lt;/code&gt; callback that was registered from node.js bootstrapper enters V8 again to run microtasks and &lt;code&gt;process.nextTick()&lt;/code&gt;. I might be wrong here, I didn’t dig too deep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is libuv locking on during &lt;code&gt;poll&lt;/code&gt; stage?&lt;/strong&gt;&lt;br&gt;
Various operating systems provide various facilities for asynchronous IO.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in Linux it is &lt;a href="https://en.wikipedia.org/wiki/Epoll" rel="noopener noreferrer"&gt;epoll&lt;/a&gt;. In a nutshell it allows you to create over 9000 file descriptors, give it to Linux and tell it “please wake me up whenever anyone has got anything for me”. The API allows you to lock until anything comes, lock for a limited amount of time or simply check without locking.&lt;/li&gt;
&lt;li&gt;in *BSD (and MacOS, because it is BSD, duh) it is &lt;a href="https://en.wikipedia.org/wiki/Kqueue" rel="noopener noreferrer"&gt;kqueue&lt;/a&gt;. Much better than &lt;code&gt;epoll&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and then there’s Windows, the pinnacle of async APIs: &lt;a href="https://stackoverflow.com/questions/5283032/i-o-completion-ports-advantages-and-disadvantages" rel="noopener noreferrer"&gt;IO Completion Ports&lt;/a&gt;. This is not a sarcasm. Fun fact, Solaris also used approach of IO Completion Ports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of those APIs (except may be for Windows?  not sure here) provide you with facilities to lock and wait until something comes, lock for limited amount of time or just check and not to lock at al.&lt;br&gt;
Because &lt;code&gt;libuv&lt;/code&gt; is an IO library it only makes sense that it locks and waits for new IO to happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens with file IO? I heard there’s a thread pool there as well?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, here’s a thing: async File IO API in POSIX &lt;strong&gt;&lt;em&gt;SUCKS&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So instead of dealing with its &lt;em&gt;quirks and features&lt;/em&gt; &lt;code&gt;libuv&lt;/code&gt; simply does it synchronously but offloads it to a thread pool that is provided by default and by default the size of this thread pool is 4. Btw, it is not only for File IO, you can throw any long running task to run there. There are C++ encryption libraries for Node.JS that offload CPU intensive encryption onto this thread pool to avoid blocking the event loop.&lt;/p&gt;

&lt;p&gt;How then &lt;code&gt;libuv&lt;/code&gt; runs callbacks, they are supposed to run on the main thread, aren’t they?&lt;/p&gt;

&lt;p&gt;What &lt;code&gt;libuv&lt;/code&gt; does is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if it is Unix, setup a pipe, &lt;code&gt;epoll&lt;/code&gt; and &lt;code&gt;kqueue&lt;/code&gt; support pipes, this pipe’s file descriptor is one of the descriptors to block on during &lt;code&gt;poll&lt;/code&gt; stage. When file operation is completed on a worker thread it writes into this pipe to wake up the event loop and call handlers.&lt;/li&gt;
&lt;li&gt;if it is Windows do the same with named pipe. Technically IO Completion Ports allow proper async File IO but as far as I understand &lt;code&gt;libuv&lt;/code&gt; still does the same pipe trick, probably for the sake of consistency and simplicity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As far as I'm aware, support for io_uring has landed to &lt;code&gt;libuv&lt;/code&gt; and in theory it is capable of doing proper async file io, at least on Linux. I'm don't know if Node is using it yet. &lt;/p&gt;

&lt;h2&gt;
  
  
  Honesty
&lt;/h2&gt;

&lt;p&gt;Recently it became fashionable to speak about green threads, co-routines and etc. as primitives of async programming.&lt;/p&gt;

&lt;p&gt;I loath those. Because they are fake.&lt;/p&gt;

&lt;p&gt;I’m a firm believer that everything should be implemented in the right way and the only way to implement those in the right way is to have support from operating system.&lt;/p&gt;

&lt;p&gt;Unfortunately not Linux nor MacOS and nor any kind of BSD I know provide you with facility to interrupt your own running code. So every time someone’s speaking about “green threads” or “coroutines” or anything pretending to be doing preemptive multitasking in user space - they are lying. What they are offering is one of two hacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you write your own complier and runtime and you sprinkle the generated code with “yield”s. This is what Go used to do. The least “bad” way but &lt;a href="http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#psched" rel="noopener noreferrer"&gt;still produced some interesting quirks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;you setup a timer and a signal so the Kernel will interrupt your process normal execution and will call the function that you specified as signal handler. This comes with several “gotchas” and people have written about those extensively. Recently Go has switched to this mechanism and avoids the issue with “for” loop from the first bullet point. Unfortunately for this to work you have to hack your stack and rewrite instruction pointer registers so when your signal handler is done running and kernel resumes you process it end up in scheduler instead of previously running function. This is really a nasty nasty nasty hacking. Stack and instruction pointer belongs to Kernel, not us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s one operating system that allows you to do that: Windows&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/windows/win32/procthread/fibers" rel="noopener noreferrer"&gt;there’s support for “fibers” which are user-space threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/windows/win32/procthread/user-mode-scheduling" rel="noopener noreferrer"&gt;there’s actually a framework to replace Kernel scheduler with your own for your process&lt;/a&gt; which unfortunately is dead in Windows 11. Or may it’ll live in Server versions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this is why I love Node so much - it is the only platform that doesn’t seem to hide and pile up hacks and gotchas when it comes to async io and just lets you use OS primitives through a somewhat simple programming language and comfortable interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code references
&lt;/h2&gt;

&lt;p&gt;I have checked out commit 331088f4a450e29f3ea8a28a9f98ccc9f8951386 so if there are any changes in the files after that then line numbers in code references might’ve drifted. &lt;/p&gt;

</description>
      <category>eventloop</category>
      <category>javascript</category>
      <category>libuv</category>
      <category>node</category>
    </item>
    <item>
      <title>TIL: get strongly typed HTTP headers with TypeScript</title>
      <dc:creator>Tony Miller</dc:creator>
      <pubDate>Mon, 20 Dec 2021 07:12:52 +0000</pubDate>
      <link>https://dev.to/tmlr/til-get-strongly-typed-http-headers-with-typescript-3e33</link>
      <guid>https://dev.to/tmlr/til-get-strongly-typed-http-headers-with-typescript-3e33</guid>
      <description>&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;I’m a developer of a backend framework. It is written in TypeScript. I want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hide the actual request object (&lt;code&gt;http.IncomingMessage&lt;/code&gt;) from my users&lt;/li&gt;
&lt;li&gt;Yet provide my users with access to HTTP headers on the request (&lt;code&gt;http.IncomingHttpHeaders&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Provide IntelliSense (auto-completion) so it is easier to find headers people want to use.&lt;/li&gt;
&lt;li&gt;Provide &lt;strong&gt;compile-time&lt;/strong&gt; checking that there’s no type in a header.&lt;/li&gt;
&lt;li&gt;Do not limit my users as to which headers they can use, so the list of headers must be extensible from their services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turns out all of that is possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Consider &lt;code&gt;http.IncomingHttpHeaders&lt;/code&gt; interface:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IncomingHttpHeaders&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept-patch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept-ranges&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www-authenticate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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 problem with it that while it does have header names hardcoded it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does not provide a way to extend that list.&lt;/li&gt;
&lt;li&gt;provides index signature, which means all type safety goes out the window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in order to conceal the actual request from my users, I’ve got a class called &lt;code&gt;Context&lt;/code&gt; and I hand out instances of that to handlers for each request:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IncomingMessage&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="err"&gt;…&lt;/span&gt;
    &lt;span class="nf"&gt;getHeader&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="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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;What we want to do is to introduce some kind of type instead of &lt;code&gt;?&lt;/code&gt; so that it allows &lt;strong&gt;only&lt;/strong&gt; those headers from &lt;code&gt;http.IncomingHttpHeaders&lt;/code&gt; that are hard-coded, we will call them “known keys”.&lt;/p&gt;

&lt;p&gt;We also want our users to be able to extend this list easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1
&lt;/h3&gt;

&lt;p&gt;Can’t use simple &lt;code&gt;type StandardHeaders = keyof http.IncomingHtppHeaders&lt;/code&gt; because the interface has index signature, that resolves into &lt;code&gt;StandardHeaders&lt;/code&gt; accepting anything so auto-completion and compile-time checking doesn’t work.&lt;/p&gt;

&lt;p&gt;Solution - remove index signature from the interface. TypeScript 4.1 and newer allows key re-mapping and TypeScript 2.8 and newer has Conditional Types. We only provide 4.1 version here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;


&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StandardHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// copy every declared property from http.IncomingHttpHeaders&lt;/span&gt;
    &lt;span class="c1"&gt;// but remove index signatures&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IncomingHttpHeaders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IncomingHttpHeaders&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;



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

&lt;/div&gt;

&lt;p&gt;That gives us copy of &lt;code&gt;http.IncomingHttpHeaders&lt;/code&gt; with index signatures removed.&lt;/p&gt;

&lt;p&gt;It is based on the fact that &lt;code&gt;‘a’ extends string&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; but &lt;code&gt;string extends ’a’&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;. Same for &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can just:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StandardHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;StandardHeaders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;That’s what VSCode thinks about &lt;code&gt;StandardHeader&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjiu295j4g61tjdf3f0c9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjiu295j4g61tjdf3f0c9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice type literal with only known headers. Let’s plug it into &lt;code&gt;getHeader(name: StandardHeader)&lt;/code&gt; and try to use it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ft0a23k3p8tg3mto03p0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0a23k3p8tg3mto03p0o.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Auto-completion works and compilation breaks if we type something wrong there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fh3r5rpvh95lymtdvj78b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3r5rpvh95lymtdvj78b.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2.
&lt;/h3&gt;

&lt;p&gt;We’re a framework, this set of headers is pretty narrow, so we need to give people ability to extend it.&lt;/p&gt;

&lt;p&gt;This one is easier to solve that the previous one. Let’s make our &lt;code&gt;Context&lt;/code&gt; generic and add several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;limit generic to &lt;strong&gt;string&lt;/strong&gt; type literals&lt;/li&gt;
&lt;li&gt;provide a sensible default&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TCustomHeader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StandardHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IncomingMessage&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="err"&gt;…&lt;/span&gt;
    &lt;span class="nf"&gt;getHeader&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;StandardHeader&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;TCustomHeader&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Ok, now our users can write something like this:&lt;/p&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;ctx&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;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Bar&lt;/span&gt;&lt;span class="dl"&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;const&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Foo&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;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Bar&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;And it will auto-complete those headers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F5v5woyngc0vlpj34wv8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v5woyngc0vlpj34wv8d.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And also it includes them into compile-time check:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdhut8jcwrfnltk374di5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhut8jcwrfnltk374di5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further improvements
&lt;/h2&gt;

&lt;p&gt;Because we’re a framework, users won’t be creating instances of &lt;code&gt;Context&lt;/code&gt; class themselves, we’re handing those out. So instead we should introduce a class &lt;code&gt;ContextHeaders&lt;/code&gt; and replace &lt;code&gt;getHeader(header: StandardHeader)&lt;/code&gt; with generic method &lt;code&gt;headers&amp;lt; TCustomHeader extends string = StandardHeader&amp;gt;: ContextHeaders&amp;lt;StandardHeader | TCustomHeader&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is left as exercise for reader =).&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>typescript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
