[{"data":1,"prerenderedAt":758},["ShallowReactive",2],{"post-\u002Fhow-i-built-a-modern-infrastructure-using-open-source-tools-and-the-power-of-cloudflare":3},{"page":4,"translation":712,"nav":714,"related":744,"random":751},{"id":5,"title":6,"body":7,"categories":689,"category":691,"date":692,"description":693,"draft":694,"extension":695,"image":696,"kind":697,"lang":698,"meta":699,"navigation":700,"path":701,"readingTime":702,"seo":703,"slug":704,"stem":704,"tags":705,"translationKey":708,"type":709,"updated":710,"__hash__":711},"posts\u002Fhow-i-built-a-modern-infrastructure-using-open-source-tools-and-the-power-of-cloudflare.md","How I Built a Modern Infrastructure Using Open Source Tools and the Power of Cloudflare",{"type":8,"value":9,"toc":670},"minimark",[10,43,48,66,69,75,82,93,96,99,104,232,234,238,241,244,247,313,316,320,323,326,329,343,346,350,356,363,366,391,394,409,412,416,422,431,434,448,455,459,464,467,470,501,504,511,515,518,524,527,531,537,543,550,557,564,568,574,577,588,591,598,602,605,613,618,623,626,630,633,647,650,653,667],[11,12,13,21],"blockquote",{},[14,15,16,17],"p",{},"💡 ",[18,19,20],"strong",{},"Quick Summary (TL;DR):",[22,23,24,31,37],"ul",{},[25,26,27,30],"li",{},[18,28,29],{},"The Goal:"," Run camiler.org on a single, low-cost VPS ($5\u002Fmonth) while handling high traffic with near-zero latency and high scalability.",[25,32,33,36],{},[18,34,35],{},"The Stack:"," A modular, Dockerized architecture using Traefik, Nuxt 3 (SSR), Fastify (API), Redis (HTML & database cache), and imgproxy.",[25,38,39,42],{},[18,40,41],{},"Key Optimization:"," Compressed Redis caching middleware, tag-based cache invalidation, and offloading all static\u002Fmedia traffic to Cloudflare CDN and R2 storage.",[44,45,47],"h2",{"id":46},"behind-the-scenes-of-camilerorg-a-modern-dockerized-infrastructure","Behind the Scenes of Camiler.org: A Modern, Dockerized Infrastructure",[14,49,50,51,65],{},"Most people outside of Turkey probably haven’t heard of ",[18,52,53],{},[54,55,64],"a",{"href":56,"className":57,"rel":59,"target":63},"https:\u002F\u002Fcamiler.org",[58],"dofollow",[60,61,62],"nofollow","noopener","noreferrer","_blank","camiler.org",", so let me give a bit of context.",[14,67,68],{},"In Turkey, where the majority of the population is Muslim, mosques—called “cami” in Turkish—are a big part of everyday life. They're not just places of worship, but also central to community life, especially around funeral services. As you’d expect, people often search online for nearby mosques, prayer times, funeral announcements, and directions.",[14,70,71,72,74],{},"The interesting part is this: although these kinds of searches happen a lot, the results on Google are surprisingly poor. You mostly get outdated pages, duplicated content, or generic results that don’t really help. That’s what pushed me to build ",[18,73,64],{},"—a site that’s fast, searchable, and actually useful.",[14,76,77,78,81],{},"Now, to be fair, ranking in these search results isn’t particularly hard. It’s what you might call a “low-competition SERP.” But it’s not all upside either. ",[18,79,80],{},"Google tends to show its own boxes—like map results or local snippets—right at the top",", which means fewer people click through to actual websites. Still, the lack of good content in this space leaves a clear opening, and that’s where camiler.org fits in.",[14,83,84,85,88,89],{},"If you're curious about the SEO thinking and the broader experiment, I wrote about that here:",[86,87],"br",{},"\n👉 ",[54,90,92],{"href":91},"\u002Fan-seo-experiment-in-a-low-competition-serp-with-google-maps-and-openai","An SEO Experiment in a Low-Competition SERP with Google Maps and OpenAI",[14,94,95],{},"This post isn’t about keywords or rankings though. It’s about how I put the site together—what tools I used, how I approached the architecture, and why I made certain decisions. Everything from Docker and Redis to Cloudflare and GitHub Actions. I built it all on my own, and in this post, I’ll walk through what that looked like.",[97,98],"hr",{},[100,101,103],"h3",{"id":102},"architecture-components-and-roles","Architecture Components and Roles",[105,106,107,124],"table",{},[108,109,110],"thead",{},[111,112,113,118,121],"tr",{},[114,115,117],"th",{"align":116},"left","Component",[114,119,120],{"align":116},"Role \u002F Purpose",[114,122,123],{"align":116},"Why It Was Chosen",[125,126,127,141,154,167,180,193,206,219],"tbody",{},[111,128,129,135,138],{},[130,131,132],"td",{"align":116},[18,133,134],{},"Traefik",[130,136,137],{"align":116},"Reverse Proxy & SSL Management",[130,139,140],{"align":116},"Native Docker integration, auto-routing via container labels (no manual config files)",[111,142,143,148,151],{},[130,144,145],{"align":116},[18,146,147],{},"Nuxt 3",[130,149,150],{"align":116},"Frontend (SSR Layout)",[130,152,153],{"align":116},"Modern developer experience, SEO-friendly server-side rendering (SSR)",[111,155,156,161,164],{},[130,157,158],{"align":116},[18,159,160],{},"Fastify",[130,162,163],{"align":116},"Backend API",[130,165,166],{"align":116},"Extremely fast, low-overhead Node.js framework to minimize CPU usage",[111,168,169,174,177],{},[130,170,171],{"align":116},[18,172,173],{},"Prisma & MariaDB",[130,175,176],{"align":116},"Database & ORM",[130,178,179],{"align":116},"Type safety, predictable query behavior, and reliable relational data structures",[111,181,182,187,190],{},[130,183,184],{"align":116},[18,185,186],{},"Redis",[130,188,189],{"align":116},"Compressed HTML Cache & Rate Limiting",[130,191,192],{"align":116},"Fast in-memory key-value store with compressed payload support and tag-based invalidation",[111,194,195,200,203],{},[130,196,197],{"align":116},[18,198,199],{},"imgproxy & Cloudflare R2",[130,201,202],{"align":116},"Image Optimization & Object Storage",[130,204,205],{"align":116},"On-the-fly image resizing and optimization backed by cost-efficient S3-compatible storage",[111,207,208,213,216],{},[130,209,210],{"align":116},[18,211,212],{},"Cloudflare CDN",[130,214,215],{"align":116},"Edge Caching & Security",[130,217,218],{"align":116},"Serves static assets and optimized images directly from edge nodes, keeping traffic off the origin VPS",[111,220,221,226,229],{},[130,222,223],{"align":116},[18,224,225],{},"GitHub Actions",[130,227,228],{"align":116},"Lightweight CI\u002FCD Deployment",[130,230,231],{"align":116},"Simple, webhook-triggered automated container deployment pipeline",[97,233],{},[44,235,237],{"id":236},"overview-of-the-stack","Overview of the Stack",[14,239,240],{},"When I started building camiler.org, my goal was clear: I wanted a fast, reliable system that I could run and maintain solo—without relying on heavyweight services or getting locked into any particular vendor. I wanted to understand every part of the stack and be able to swap out anything if needed.",[14,242,243],{},"So I went with a clean, Docker-based setup built entirely on open source tools. Everything runs on a small, single VPS, and each component does one thing well.",[14,245,246],{},"Here’s the rough breakdown:",[22,248,249,254,259,272,277,283,297,302],{},[25,250,251,253],{},[18,252,134],{}," acts as the reverse proxy, handling SSL, routing, and IP-level restrictions.",[25,255,256,258],{},[18,257,147],{}," runs the frontend in SSR mode. I built a custom middleware that caches full HTML responses in Redis for better performance.",[25,260,261,263,264,267,268,271],{},[18,262,160],{}," handles the backend API, talking to a ",[18,265,266],{},"MariaDB"," database via ",[18,269,270],{},"Prisma",".",[25,273,274,276],{},[18,275,186],{}," is primarily used for caching, but I also use it for things like tag-based invalidation and rate limiting logic.",[25,278,279,282],{},[18,280,281],{},"imgproxy"," serves mosque images, resizing and optimizing them on the fly.",[25,284,285,288,289,292,293,296],{},[18,286,287],{},"Cloudflare"," handles edge caching and CDN duties. I also use ",[18,290,291],{},"Cloudflare R2"," for object storage. Technically yes, that’s a vendor dependency—but since R2 is S3-compatible, I could switch to any other S3 provider or even a self-hosted ",[18,294,295],{},"MinIO"," setup if needed.",[25,298,299,301],{},[18,300,225],{}," automates deployments—just enough CI\u002FCD for a one-person project.",[25,303,304,305,308,309,312],{},"Finally, ",[18,306,307],{},"Portainer"," and ",[18,310,311],{},"phpMyAdmin"," are in the stack for quick container and database inspection when necessary.",[14,314,315],{},"It’s simple, modular, and flexible. I know exactly what runs where, and I’m not locked into anything I can’t replace.",[100,317,319],{"id":318},"traefik-simple-smart-and-docker-friendly","Traefik: Simple, Smart, and Docker-Friendly",[14,321,322],{},"Traefik is the first thing that touches any request coming into the server. It’s the reverse proxy that routes traffic to the right container, handles SSL certificates, and takes care of a few basic security rules.",[14,324,325],{},"One of the main reasons I picked Traefik is how well it integrates with Docker. You just add a few labels to your containers, and Traefik picks them up automatically—no extra config files, no reloads. For someone managing things solo, that kind of automation is gold.",[14,327,328],{},"I use it for:",[22,330,331,334,337,340],{},[25,332,333],{},"SSL via Let’s Encrypt",[25,335,336],{},"Route management based on container labels",[25,338,339],{},"IP-based access control (some internal tools aren’t meant to be public)",[25,341,342],{},"Gzip compression and some default security headers",[14,344,345],{},"There are more complex reverse proxies out there, but I didn’t need complexity—I needed something that \"just works\" in a containerized setup. And that’s exactly what Traefik does.",[100,347,349],{"id":348},"frontend-nuxt-3-custom-cache-middleware","Frontend: Nuxt 3 + Custom Cache Middleware",[14,351,352,353,355],{},"For the frontend, I went with ",[18,354,147],{}," running in SSR mode. It gives me the flexibility of server-side rendering while still keeping the developer experience clean. But Nuxt’s built-in caching isn’t really enough if you care about performance at scale—even on a modest project.",[14,357,358,359,362],{},"So I wrote a ",[18,360,361],{},"custom cache middleware"," to control how pages are cached and served.",[14,364,365],{},"Here’s what it does:",[22,367,368,374,377,384],{},[25,369,370,371,373],{},"It stores full HTML responses in ",[18,372,186],{},", so the server doesn’t have to re-render every page on each request.",[25,375,376],{},"To save memory on a small VPS, I compress the data before writing it to Redis. It costs almost nothing in terms of CPU, but significantly reduces memory usage.",[25,378,379,380,383],{},"It supports ",[18,381,382],{},"ETag"," headers, which helps with freshness checks and reduces unnecessary bandwidth.",[25,385,386,387,390],{},"Most importantly, it includes ",[18,388,389],{},"tag-based invalidation","—which means I can purge related pages when something changes. For example, if a mosque's info is updated, I can also invalidate the cache for its “nearby mosques” page.",[14,392,393],{},"This gave me much finer control over how caching works compared to what Nuxt offers out of the box.",[14,395,396,397,400,401,404,405,408],{},"It's also worth mentioning that there's a separate caching layer on the ",[18,398,399],{},"client side",", built with a mix of ",[18,402,403],{},"Local Storage"," and in-memory ",[18,406,407],{},"LRU caching",". It helps with things like reducing API calls and keeping recently viewed data instantly accessible. But since it's purely frontend logic and not part of the infrastructure itself, I won’t go into detail here.",[14,410,411],{},"This setup isn’t just about speed on the client side—it’s mainly about reducing server load, improving response times, and making more efficient use of limited system resources.",[100,413,415],{"id":414},"backend-fastify-prisma-custom-rate-limiter","Backend: Fastify + Prisma + Custom Rate Limiter",[14,417,418,419,421],{},"For the backend, I’m using ",[18,420,160],{},"—a lightweight, high-performance Node.js framework that’s perfect for projects where you care about every millisecond. It’s easy to extend, has a good plugin system, and doesn't get in your way.",[14,423,424,425,427,428,430],{},"The API is fairly straightforward: it handles search queries, mosque data, metadata endpoints, and a few internal tools. Under the hood, I use ",[18,426,270],{}," as the ORM, connecting to a ",[18,429,266],{}," database. Prisma gives me type safety, a nice dev experience, and predictable query behavior—which matters a lot when you're flying solo.",[14,432,433],{},"But one thing I couldn’t quite find off-the-shelf was a rate limiter that gave me the kind of control I needed. The existing solutions were either too generic or not flexible enough, especially when you want:",[22,435,436,439,442,445],{},[25,437,438],{},"Different rate limits per route",[25,440,441],{},"Group-based or user-specific limits",[25,443,444],{},"Global fallback thresholds",[25,446,447],{},"Redis-backed counters that survive restarts",[14,449,450,451,454],{},"So I ended up writing my own ",[18,452,453],{},"custom rate limiter"," middleware. It plugs into Redis, tracks usage with expiry logic, and gives me full visibility into how traffic is behaving. It's lightweight, doesn’t introduce much overhead, and I can fine-tune it however I like.",[100,456,458],{"id":457},"redis-the-glue-layer","Redis: The Glue Layer",[14,460,461,462,271],{},"If there’s one component holding everything together, it’s ",[18,463,186],{},[14,465,466],{},"I originally added it just for caching SSR responses from the frontend. But as the project evolved, Redis naturally became the place where small, fast-access data lives—things that don’t belong in a database but still need to be shared across services.",[14,468,469],{},"Right now I use Redis for:",[22,471,472,478,484,489,495],{},[25,473,474,477],{},[18,475,476],{},"Caching"," full HTML responses with optional compression",[25,479,480,483],{},[18,481,482],{},"Tag-based invalidation",", so I can expire multiple related keys at once",[25,485,486],{},[18,487,488],{},"ETag and SWR support",[25,490,491,494],{},[18,492,493],{},"Rate limiting",", with expiring counters for different scopes (IP, route, global)",[25,496,497,500],{},[18,498,499],{},"Lightweight state tracking",", like whether a purge or update happened recently",[14,502,503],{},"And since I’m on a small VPS with limited memory, I compress most Redis payloads before writing. It’s a negligible cost CPU-wise but saves a good amount of RAM in the long run.",[14,505,506,507,510],{},"I also made sure all logic around Redis is ",[18,508,509],{},"non-blocking and isolated",". If Redis goes down, the rest of the stack keeps working. It’s not a single point of failure—it’s an optimization layer.",[100,512,514],{"id":513},"imgproxy-for-on-the-fly-image-optimization","imgproxy for On-the-Fly Image Optimization",[14,516,517],{},"I didn’t want to serve raw images directly from the app. It adds complexity, uses bandwidth unnecessarily, and doesn’t scale well when you need multiple sizes or formats.",[14,519,520,521,523],{},"Instead, I use ",[18,522,281],{},"—a lightweight image processing server that resizes and optimizes images on demand. It pulls the originals from object storage and returns just the version needed for the client: the right size, format, and quality.",[14,525,526],{},"This way, I only need to store a single version of each image, and I let imgproxy do the rest dynamically.",[100,528,530],{"id":529},"cloudflare-edge-caching-and-object-storage","Cloudflare: Edge Caching and Object Storage",[14,532,533,534,536],{},"For both static assets and image delivery, ",[18,535,287],{}," plays a big role in performance and cost-efficiency.",[14,538,539,540,542],{},"I store all mosque images in ",[18,541,291],{},", which is their S3-compatible object storage service. It works seamlessly with tools like imgproxy.",[14,544,545,546,549],{},"What makes R2 even more attractive is its ",[18,547,548],{},"generous free tier","—especially for a personal project like this. I’ve been using it from day one, and haven’t had to worry about hitting limits. It just works.",[14,551,552,553,556],{},"On top of that, Cloudflare’s ",[18,554,555],{},"free CDN"," handles edge caching automatically. Once an optimized image is generated by imgproxy, it’s cached at Cloudflare’s edge nodes around the world. That means the next user gets it instantly, without the request even reaching my server or R2.",[14,558,559,560,563],{},"This setup keeps traffic off my origin, cuts latency, and keeps the project affordable. And even though Cloudflare is technically a vendor dependency, R2’s ",[18,561,562],{},"S3 compatibility"," means I can move elsewhere with minimal effort if I ever need to.",[100,565,567],{"id":566},"cicd-with-github-actions","CI\u002FCD with GitHub Actions",[14,569,570,571,573],{},"For deployments, I kept things simple and predictable. I use ",[18,572,225],{}," to automate everything from building Docker images to restarting containers.",[14,575,576],{},"Whenever I push changes to the main branch, GitHub Actions kicks in:",[22,578,579,582,585],{},[25,580,581],{},"It builds the Docker image for the relevant service (frontend or backend)",[25,583,584],{},"Tags and pushes the image to my private Docker registry",[25,586,587],{},"Then hits a lightweight webhook endpoint on the server, which triggers a container restart with the new image",[14,589,590],{},"That’s it. No fancy pipelines, no third-party CI\u002FCD platforms, and no SSHing into the server to pull code manually. It’s fast, quiet, and works every time.",[14,592,593,594,597],{},"For a solo project, I’ve found this to be the perfect balance: ",[18,595,596],{},"fully automated, but still transparent",". I know exactly what’s happening, and if something breaks, I’m just one log file away from understanding why.",[100,599,601],{"id":600},"admin-tools-portainer-and-phpmyadmin","Admin Tools: Portainer and phpMyAdmin",[14,603,604],{},"Even though most of the stack runs smoothly on its own, I still need some basic visibility into what’s happening under the hood—especially during development or debugging.",[14,606,607,608,308,610,612],{},"That’s where ",[18,609,307],{},[18,611,311],{}," come in.",[14,614,615,617],{},[18,616,307],{}," gives me a web-based UI to monitor and manage Docker containers. I don’t use it for deployments or orchestration, but it’s great for quickly checking logs, restarting a misbehaving container, or keeping an eye on resource usage.",[14,619,620,622],{},[18,621,311],{},", on the other hand, is strictly for local or IP-restricted use. Sometimes I just want to inspect a table or run a manual SQL query without firing up a terminal or writing scripts. It’s not fancy, but it gets the job done when I need it.",[14,624,625],{},"Neither tool is exposed publicly, and I treat them as optional helpers—not core infrastructure. But when you’re working solo, having a visual layer like this can save time and prevent mistakes.",[100,627,629],{"id":628},"final-thoughts","Final Thoughts",[14,631,632],{},"I didn’t set out to build a complex infrastructure. I just wanted a fast, useful, and maintainable site that could fill a real gap in the search landscape—without relying on big platforms or managed everything-as-a-service offerings.",[14,634,635,636,639,640,643,644,646],{},"That mindset shaped every part of this stack. I leaned on ",[18,637,638],{},"open source tools",", stayed close to the metal with ",[18,641,642],{},"Docker",", and used ",[18,645,287],{}," where it made sense—especially for edge performance and low-cost storage. Every piece serves a clear purpose, and nothing feels like overkill.",[14,648,649],{},"Yes, it’s all running on a single VPS. Yes, I wrote and maintain everything myself. But that’s kind of the point: with the right tools, you don’t need a team of engineers to build something reliable and scalable.",[14,651,652],{},"If you’re thinking about launching a project in a niche space—or replacing something bloated with something smarter—this kind of setup might be all you need.",[14,654,655,656,659,660,663,664,666],{},"And if you're curious about how I handle things like ",[18,657,658],{},"tag-based cache invalidation",", ",[18,661,662],{},"compressed Redis payloads",", or my ",[18,665,453],{},", those will be the focus of upcoming posts.",[14,668,669],{},"Thanks for reading.",{"title":671,"searchDepth":672,"depth":672,"links":673},"",2,[674,678],{"id":46,"depth":672,"text":47,"children":675},[676],{"id":102,"depth":677,"text":103},3,{"id":236,"depth":672,"text":237,"children":679},[680,681,682,683,684,685,686,687,688],{"id":318,"depth":677,"text":319},{"id":348,"depth":677,"text":349},{"id":414,"depth":677,"text":415},{"id":457,"depth":677,"text":458},{"id":513,"depth":677,"text":514},{"id":529,"depth":677,"text":530},{"id":566,"depth":677,"text":567},{"id":600,"depth":677,"text":601},{"id":628,"depth":677,"text":629},[690],"engineering",null,"2025-07-02","Behind the scenes of camiler.org: A modern, Dockerized infrastructure built on open source tools and Cloudflare, optimized to run on a single small VPS.",false,"md","\u002Fimages\u002Fhero\u002Fcamiler-infra.avif","Building","en",{},true,"\u002Fhow-i-built-a-modern-infrastructure-using-open-source-tools-and-the-power-of-cloudflare",12,{"title":6,"description":693},"how-i-built-a-modern-infrastructure-using-open-source-tools-and-the-power-of-cloudflare",[706,707],"projects","infrastructure","camiler-infra","post","2026-06-21","eFrWh253XADHTGagOR4mGazoKMNtZi7e5DrCmzud2a8",{"path":713},"\u002Ftr\u002Fcloudflare-docker-ve-acik-kaynak-araclarla-bir-altyapi-kurmak-camiler-orgun-teknik-hikayesi",{"prev":715,"next":718,"others":721,"lucky":743,"readingTime":702},{"path":716,"title":717},"\u002Fbeyond-the-bot-lessons-from-building-a-chat-system-for-global-patients","Beyond the bot: lessons from building a chat system for global patients",{"path":719,"title":720},"\u002Ffrom-rules-to-decisions-the-real-time-sales-intelligence-platform-we-built-at-vanity","From Rules to Decisions: The Real-Time Sales Intelligence Platform We Built at Vanity",[722,725,728,731,734,737,738,739,740],{"path":723,"title":724},"\u002F1m-impressions-per-month-0-revenue-a-programmatic-seo-post-mortem","1M Impressions per Month, $0 Revenue: A Programmatic SEO Post-Mortem",{"path":726,"title":727},"\u002Fthe-end-of-coding-or-a-new-renaissance-the-invisible-crisis-of-ai","The End of Coding or a New Renaissance? The Invisible Crisis of AI",{"path":729,"title":730},"\u002Fraising-children-in-the-age-of-artificial-intelligence","Raising Children in the Age of Artificial Intelligence",{"path":732,"title":733},"\u002Fpesintaksit-cash-vs-installments-a-turkish-inflation-aware-payment-comparison-tool","Cash or Installments? – The Story Behind PeşinTaksit",{"path":735,"title":736},"\u002Fredar-ai-powered-summaries-for-kap-disclosures-and-open-sources","Redar: AI-Powered Summaries for KAP Disclosures and Open Sources",{"path":716,"title":717},{"path":719,"title":720},{"path":91,"title":92},{"path":741,"title":742},"\u002Fwhy-im-building-rankextension-making-google-search-console-actually-make-sense","Why I’m Building RankExtension: Making Google Search Console Actually Make Sense",{"path":735,"title":736},[745,746,748,749],{"path":723,"title":724,"date":710},{"path":732,"title":733,"date":747},"2025-10-30",{"path":735,"title":736,"date":747},{"path":91,"title":92,"date":750},"2025-06-05",[752,754,756],{"path":726,"title":727,"date":753},"2025-11-20",{"path":716,"title":717,"date":755},"2025-07-13",{"path":719,"title":720,"date":757},"2025-06-26",1782141950468]