{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Vineeth N K, Blog",
  "home_page_url": "https://vineethnk.in/",
  "feed_url": "https://vineethnk.in/feed.json",
  "description": "Writing code, building tools, and sharing what I learn along the way.",
  "language": "en",
  "authors": [
    {
      "name": "Vineeth N K",
      "url": "https://vineethnk.in/"
    }
  ],
  "items": [
    {
      "id": "https://vineethnk.in/blog/i-blocked-tor-then-opened-tor-browser/",
      "url": "https://vineethnk.in/blog/i-blocked-tor-then-opened-tor-browser/",
      "title": "I blocked Tor exit nodes, then I opened Tor Browser",
      "summary": "I deployed a hardened Tor exit node firewall on a SaaS production box, opened Tor Browser to confirm, and the site loaded. The IPv4 fortress was perfect. The IPv6 side door was wide open. This is the script, the punchline, and the rewrite that became TorShield.",
      "content_text": "I deployed a hardened Tor exit node firewall on a SaaS production box, opened Tor Browser to confirm, and the site loaded. The IPv4 fortress was perfect. The IPv6 side door was wide open. This is the script, the punchline, and the rewrite that became TorShield.",
      "date_published": "2026-04-30T00:00:00.000Z",
      "tags": [
        "tor",
        "iptables",
        "ipset",
        "ipv6",
        "linux",
        "security",
        "ops"
      ],
      "image": "https://vineethnk.in/blog/tor-shield-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/the-node-modules-that-wouldnt-die/",
      "url": "https://vineethnk.in/blog/the-node-modules-that-wouldnt-die/",
      "title": "The node_modules That Wouldn't Die",
      "summary": "An internal app deploy kept importing an old Vite plugin export. The lockfile was right, the build was wrong. The culprit was older than the bug.",
      "content_text": "An internal app deploy kept importing an old Vite plugin export. The lockfile was right, the build was wrong. The culprit was older than the bug.",
      "date_published": "2026-04-29T00:00:00.000Z",
      "tags": [
        "docker",
        "deployment",
        "git",
        "ci-cd",
        "ops"
      ],
      "image": "https://vineethnk.in/blog/stale-node-modules-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/the-sentry-invite-that-never-arrived/",
      "url": "https://vineethnk.in/blog/the-sentry-invite-that-never-arrived/",
      "title": "The Sentry signup nobody could finish",
      "summary": "A colleague signed up on our self-hosted Sentry and never got the email. I had been getting Sentry mail forever, so I assumed he was missing something. He was not. DMARC was silently dropping every message to our Workspace inboxes, and the 'workaround' I shared with him broke too. Here is the bug, the lie my inbox had been telling me, and the shell command that finally got him in.",
      "content_text": "A colleague signed up on our self-hosted Sentry and never got the email. I had been getting Sentry mail forever, so I assumed he was missing something. He was not. DMARC was silently dropping every message to our Workspace inboxes, and the 'workaround' I shared with him broke too. Here is the bug, the lie my inbox had been telling me, and the shell command that finally got him in.",
      "date_published": "2026-04-28T00:00:00.000Z",
      "tags": [
        "sentry",
        "self-hosted",
        "dmarc",
        "smtp",
        "ops"
      ],
      "image": "https://vineethnk.in/blog/sentry-invite-skipped-gmail-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/the-sed-that-didnt-stick/",
      "url": "https://vineethnk.in/blog/the-sed-that-didnt-stick/",
      "title": "The sed that didn't stick",
      "summary": "A failing nightly backup, a sed hotfix that worked once, and the next morning's cron that failed anyway. Node's require cache had eaten my patch.",
      "content_text": "A failing nightly backup, a sed hotfix that worked once, and the next morning's cron that failed anyway. Node's require cache had eaten my patch.",
      "date_published": "2026-04-27T00:00:00.000Z",
      "tags": [
        "docker",
        "node",
        "backup",
        "ops",
        "open-source"
      ],
      "image": "https://vineethnk.in/blog/sed-hotfix-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/cross-posting-blog-to-devto-and-hashnode/",
      "url": "https://vineethnk.in/blog/cross-posting-blog-to-devto-and-hashnode/",
      "title": "Cross-Posting My Blog to dev.to and Hashnode: What I Got Wrong",
      "summary": "I figured cross-posting my Astro blog to dev.to and Hashnode would take an afternoon. It turned into four PRs, three failure modes, and a few API surprises.",
      "content_text": "I figured cross-posting my Astro blog to dev.to and Hashnode would take an afternoon. It turned into four PRs, three failure modes, and a few API surprises.",
      "date_published": "2026-04-25T00:00:00.000Z",
      "tags": [
        "blog",
        "automation",
        "github-actions",
        "devops",
        "self-hosting"
      ],
      "image": "https://vineethnk.in/blog/cross-posting-blog-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/i-mistook-gpt-oss-for-an-image-generator/",
      "url": "https://vineethnk.in/blog/i-mistook-gpt-oss-for-an-image-generator/",
      "title": "I Mistook gpt-oss for an Image Generator. Now My Mac Runs FLUX Offline.",
      "summary": "I asked gpt-oss for an image, realised it cannot do that, and ended up with FLUX running on my Mac through Draw Things and a tiny curl pipeline. A full walkthrough so you do not repeat my mistakes.",
      "content_text": "I asked gpt-oss for an image, realised it cannot do that, and ended up with FLUX running on my Mac through Draw Things and a tiny curl pipeline. A full walkthrough so you do not repeat my mistakes.",
      "date_published": "2026-04-25T00:00:00.000Z",
      "tags": [
        "mac",
        "ai",
        "flux",
        "draw-things",
        "local-llm",
        "image-generation"
      ],
      "image": "https://vineethnk.in/blog/local-flux-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/per-repo-wiki-that-gets-read/",
      "url": "https://vineethnk.in/blog/per-repo-wiki-that-gets-read/",
      "title": "Building a per-repo wiki that actually gets read",
      "summary": "The docs existed. The CI/CD was already automated. And yet I was still getting pinged before every deployment. Honest field notes on why a per-repo wiki finally broke the loop, and the hidden .wiki.git repo nobody talks about.",
      "content_text": "The docs existed. The CI/CD was already automated. And yet I was still getting pinged before every deployment. Honest field notes on why a per-repo wiki finally broke the loop, and the hidden .wiki.git repo nobody talks about.",
      "date_published": "2026-04-24T00:00:00.000Z",
      "tags": [
        "docs",
        "github-actions",
        "devops",
        "team-process",
        "onboarding"
      ],
      "image": "https://vineethnk.in/blog/per-repo-wiki-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/docker-port-convention-suffix-vs-prefix/",
      "url": "https://vineethnk.in/blog/docker-port-convention-suffix-vs-prefix/",
      "title": "Why I Stopped Arguing About Docker Port Conventions",
      "summary": "A colleague raised port conflicts in the daily. A suffix vote lost to the 16-bit limit. A prefix rule worked but still felt wrong. Then docker-compose.override.yml.example made the whole debate go away.",
      "content_text": "A colleague raised port conflicts in the daily. A suffix vote lost to the 16-bit limit. A prefix rule worked but still felt wrong. Then docker-compose.override.yml.example made the whole debate go away.",
      "date_published": "2026-04-23T00:00:00.000Z",
      "tags": [
        "docker",
        "devops",
        "compose",
        "workflow"
      ],
      "image": "https://vineethnk.in/blog/docker-port-convention-hero-v2.png"
    },
    {
      "id": "https://vineethnk.in/blog/setting-up-minio-cdn-nginx-docker/",
      "url": "https://vineethnk.in/blog/setting-up-minio-cdn-nginx-docker/",
      "title": "Setting Up a MinIO CDN with Nginx Reverse Proxy on Docker",
      "summary": "A practical walkthrough for self-hosting an S3-compatible CDN with MinIO, Docker Compose, and Nginx - including the small config bits everyone seems to miss until presigned URLs start failing.",
      "content_text": "A practical walkthrough for self-hosting an S3-compatible CDN with MinIO, Docker Compose, and Nginx - including the small config bits everyone seems to miss until presigned URLs start failing.",
      "date_published": "2026-04-22T00:00:00.000Z",
      "tags": [
        "minio",
        "nginx",
        "docker",
        "self-hosting",
        "devops"
      ],
      "image": "https://vineethnk.in/blog/minio-cdn-nginx-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/the-day-i-tested-a-production-restore/",
      "url": "https://vineethnk.in/blog/the-day-i-tested-a-production-restore/",
      "title": "The day I realised I had never tested a production backup",
      "summary": "I had been running test-backup drills for months and felt covered. But I had never pulled a real production snapshot into a lab and restored it. The afternoon I finally did is the story behind this post.",
      "content_text": "I had been running test-backup drills for months and felt covered. But I had never pulled a real production snapshot into a lab and restored it. The afternoon I finally did is the story behind this post.",
      "date_published": "2026-04-21T00:00:00.000Z",
      "tags": [
        "backup",
        "docker-compose",
        "databases",
        "ops",
        "open-source"
      ],
      "image": "https://vineethnk.in/blog/backup-verify-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/shipping-ipwhoami-to-brew-and-scoop/",
      "url": "https://vineethnk.in/blog/shipping-ipwhoami-to-brew-and-scoop/",
      "title": "The second half of shipping a CLI: Homebrew tap, Scoop bucket, and the SHA dance",
      "summary": "What happens after you publish to npm? For Mac and Windows users, mostly nothing. Here is the packaging side-quest of getting ipwhoami onto brew and scoop.",
      "content_text": "What happens after you publish to npm? For Mac and Windows users, mostly nothing. Here is the packaging side-quest of getting ipwhoami onto brew and scoop.",
      "date_published": "2026-04-20T00:00:00.000Z",
      "tags": [
        "cli",
        "homebrew",
        "scoop",
        "packaging",
        "github-actions",
        "release-please"
      ],
      "image": "https://vineethnk.in/blog/ipwhoami-packaging-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/building-docling-server/",
      "url": "https://vineethnk.in/blog/building-docling-server/",
      "title": "Building docling-server: a one-command document API for our AI pipeline",
      "summary": "Why I wrapped docling into a full Docker Compose setup with FastAPI, Celery, and nginx — so our AI project could stop worrying about messy PDFs, Word files, and scanned junk, and just get clean markdown back.",
      "content_text": "Why I wrapped docling into a full Docker Compose setup with FastAPI, Celery, and nginx — so our AI project could stop worrying about messy PDFs, Word files, and scanned junk, and just get clean markdown back.",
      "date_published": "2026-04-18T00:00:00.000Z",
      "tags": [
        "docling",
        "fastapi",
        "celery",
        "docker",
        "ai-pipeline",
        "open-source"
      ],
      "image": "https://vineethnk.in/blog/docling-server-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/building-mcp-pool/",
      "url": "https://vineethnk.in/blog/building-mcp-pool/",
      "title": "Building mcp-pool: one week, eleven MCP servers, one shared OAuth library",
      "summary": "How a single Stripe MCP server turned into a monorepo of eleven, and why half the engineering effort went into not writing the same OAuth flow six times.",
      "content_text": "How a single Stripe MCP server turned into a monorepo of eleven, and why half the engineering effort went into not writing the same OAuth flow six times.",
      "date_published": "2026-04-17T00:00:00.000Z",
      "tags": [
        "typescript",
        "mcp",
        "oauth",
        "monorepo",
        "ai-tooling",
        "open-source"
      ],
      "image": "https://vineethnk.in/blog/mcp-pool-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/upgrading-self-hosted-sentry/",
      "url": "https://vineethnk.in/blog/upgrading-self-hosted-sentry/",
      "title": "I upgraded our 2.5-year-old self-hosted Sentry without losing a single byte",
      "summary": "A self-hosted Sentry instance, 2.5 years behind, 0 bytes of swap free, 4 mandatory version hops, 78 containers, and one afternoon where I learned that enabling a feature and using a feature are not the same thing.",
      "content_text": "A self-hosted Sentry instance, 2.5 years behind, 0 bytes of swap free, 4 mandatory version hops, 78 containers, and one afternoon where I learned that enabling a feature and using a feature are not the same thing.",
      "date_published": "2026-04-16T00:00:00.000Z",
      "tags": [
        "sentry",
        "self-hosted",
        "devops",
        "upgrade",
        "docker",
        "journey"
      ],
      "image": "https://vineethnk.in/blog/sentry-upgrade-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/a-short-history-of-ipwhoami/",
      "url": "https://vineethnk.in/blog/a-short-history-of-ipwhoami/",
      "title": "A short history of the CLI I built to stop curling IP APIs",
      "summary": "It was supposed to be a weekend CLI. Then the name got stolen, the providers started disagreeing with each other, the rate limits hit back, and somewhere along the way the simple tool grew its own self-hosted backend.",
      "content_text": "It was supposed to be a weekend CLI. Then the name got stolen, the providers started disagreeing with each other, the rate limits hit back, and somewhere along the way the simple tool grew its own self-hosted backend.",
      "date_published": "2026-04-15T00:00:00.000Z",
      "tags": [
        "cli",
        "node",
        "ip",
        "geolocation",
        "open-source",
        "journey"
      ],
      "image": "https://vineethnk.in/blog/ipwhoami-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/my-family-whatsapp-made-me-build-medix/",
      "url": "https://vineethnk.in/blog/my-family-whatsapp-made-me-build-medix/",
      "title": "My family thinks WhatsApp can send anything. So I wrote a Python CLI.",
      "summary": "A 2005 wedding video, an 8 GB .VOB file, a family WhatsApp group with unreasonable faith, a small privacy grudge against Google, and the weekend Python CLI that came out of it.",
      "content_text": "A 2005 wedding video, an 8 GB .VOB file, a family WhatsApp group with unreasonable faith, a small privacy grudge against Google, and the weekend Python CLI that came out of it.",
      "date_published": "2026-04-15T00:00:00.000Z",
      "tags": [
        "python",
        "ffmpeg",
        "cli",
        "media",
        "open-source",
        "journey"
      ],
      "image": "https://vineethnk.in/blog/medix-hero.png"
    },
    {
      "id": "https://vineethnk.in/blog/jquery-vertical-scroll/",
      "url": "https://vineethnk.in/blog/jquery-vertical-scroll/",
      "title": "jquery.verticalScroll.js: a love letter to jQuery, written ten years later",
      "summary": "I saw Apple's iPhone page in 2016, thought 'I can build that in jQuery,' and somehow I'm still maintaining it in 2026. Zero stars. Zero regrets.",
      "content_text": "I saw Apple's iPhone page in 2016, thought 'I can build that in jQuery,' and somehow I'm still maintaining it in 2026. Zero stars. Zero regrets.",
      "date_published": "2026-04-14T00:00:00.000Z",
      "tags": [
        "jquery",
        "plugin",
        "javascript",
        "scss",
        "open-source",
        "journey"
      ]
    },
    {
      "id": "https://vineethnk.in/blog/my-zshrc-archaeology/",
      "url": "https://vineethnk.in/blog/my-zshrc-archaeology/",
      "title": "My .zshrc is 350 lines and I mass-replaced every core Unix command",
      "summary": "A 350-line .zshrc, a 68-line .bash_aliases time capsule from the Linux era, a secret gist, and the story of a developer who can't let go of a shell config.",
      "content_text": "A 350-line .zshrc, a 68-line .bash_aliases time capsule from the Linux era, a secret gist, and the story of a developer who can't let go of a shell config.",
      "date_published": "2026-04-14T00:00:00.000Z",
      "tags": [
        "shell",
        "zsh",
        "dotfiles",
        "devtools",
        "personal",
        "journey"
      ]
    },
    {
      "id": "https://vineethnk.in/blog/dfree-disk-cleanup/",
      "url": "https://vineethnk.in/blog/dfree-disk-cleanup/",
      "title": "dfree: stop digging with your hands, you've got an axe now",
      "summary": "A short story about running out of server space because of academy videos, and the shell tool I wrote so I'd never hear 'Vineeth, we can't upload anymore' again.",
      "content_text": "A short story about running out of server space because of academy videos, and the shell tool I wrote so I'd never hear 'Vineeth, we can't upload anymore' again.",
      "date_published": "2026-04-13T00:00:00.000Z",
      "tags": [
        "shell",
        "cli",
        "devops",
        "disk-cleanup",
        "open-source"
      ]
    },
    {
      "id": "https://vineethnk.in/blog/building-agent-sessions/",
      "url": "https://vineethnk.in/blog/building-agent-sessions/",
      "title": "Building agent-sessions: a universal session manager for the AI CLI era",
      "summary": "Five AI coding agents, five incompatible session formats, one terminal UI to browse them all — and the reverse-engineering it took to get there.",
      "content_text": "Five AI coding agents, five incompatible session formats, one terminal UI to browse them all — and the reverse-engineering it took to get there.",
      "date_published": "2026-04-12T00:00:00.000Z",
      "tags": [
        "typescript",
        "cli",
        "ai-tooling",
        "hexagonal-architecture",
        "react",
        "open-source"
      ]
    },
    {
      "id": "https://vineethnk.in/blog/diskdoc-and-dockit/",
      "url": "https://vineethnk.in/blog/diskdoc-and-dockit/",
      "title": "diskdoc and dockit: same problem, two languages, different answers",
      "summary": "I built two disk cleanup CLIs — one in Rust with a TUI, one in Go with risk scoring. Here's what each language and design taught me that the other couldn't.",
      "content_text": "I built two disk cleanup CLIs — one in Rust with a TUI, one in Go with risk scoring. Here's what each language and design taught me that the other couldn't.",
      "date_published": "2026-04-12T00:00:00.000Z",
      "tags": [
        "rust",
        "go",
        "cli",
        "docker",
        "tui",
        "open-source"
      ]
    },
    {
      "id": "https://vineethnk.in/blog/building-backupctl/",
      "url": "https://vineethnk.in/blog/building-backupctl/",
      "title": "Building backupctl: what it took to replace a cron job with a backup service",
      "summary": "How a cron-and-restic setup outgrew itself, and the NestJS backup service I built to replace it — with two war stories I didn't see coming.",
      "content_text": "How a cron-and-restic setup outgrew itself, and the NestJS backup service I built to replace it — with two war stories I didn't see coming.",
      "date_published": "2026-04-11T00:00:00.000Z",
      "tags": [
        "nestjs",
        "hexagonal-architecture",
        "backup",
        "restic",
        "open-source",
        "journey"
      ]
    },
    {
      "id": "https://vineethnk.in/blog/hello-world/",
      "url": "https://vineethnk.in/blog/hello-world/",
      "title": "Hello World — Welcome to My Blog",
      "summary": "First post on my new portfolio blog. A quick intro about what to expect and why I decided to start writing.",
      "content_text": "First post on my new portfolio blog. A quick intro about what to expect and why I decided to start writing.",
      "date_published": "2024-04-03T00:00:00.000Z",
      "tags": [
        "intro",
        "blog",
        "personal"
      ]
    }
  ]
}