AI 资讯
Your Free AI Tier Is Shared. Build the Gate.
This week, DEV is arguing about who reviews AI output ( discussion ). The community keeps asking the same question. My answer is different. Review the boundary first, not the output. The output is visible. The boundary is not. That is where the risk hides. Agents get the memory debates. The gateway gets none. A free AI tier is a shared service. It has a budget, a concurrency ceiling, and no SLA. Treat it that way. Put a gateway between your app and the model. The gateway owns the budget, the queue, and the breaker. MonkeyCode is an open source project. It offers free model access and a free server option. The free tier gives you a 10M token monthly budget. That number is a constraint, not a feature. Design around it before you build on it. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Think of the free tier as a water pipe. The pipe has a fixed diameter and a monthly meter. Your app is a set of open taps. Without a valve, the meter empties fast and the pipe floods. The gateway is the valve. Direct calls look simpler. They are simpler for one request. They fail at the tenth. The gateway absorbs the variance. Your app never sees a 429. Your app never sees an empty budget. Constraints Three constraints define the design. First, the 10M token budget is monthly. It does not reset daily. It does not roll over. Second, the free server serializes work. Concurrency of one is a safe assumption. Third, there is no SLA. The endpoint can stall, throttle, or return 429 at any moment. These constraints are not bugs. They are the contract. A good architecture reads the contract. Then it shapes the data flow around it. Data flow The flow has six stages. The client sends a prompt to the gateway. The gateway checks the token budget. It enqueues the request. A single worker drains the queue. The worker calls the model endpoint. The response returns to the client. Add two escape paths. When the budget is empty, the gateway returns a fallback answer. Whe
AI 资讯
Best Enterprise AI Gateway for LLM Cost Optimization in 2026
The first time you deploy an LLM application, cost usually isn't the biggest concern. You have one...
AI 资讯
drainscan vs gitleaks vs trufflehog: Why Web3 Needs Its Own Secret Scanner (2026 Benchmark)
drainscan vs gitleaks vs trufflehog: Why Web3 Needs Its Own Secret Scanner Benchmarked on 500+ web3 repositories. Generic scanners miss 73% of web3-specific key leaks. The Problem: Generic Scanners Don't Speak Web3 You run gitleaks detect or trufflehog filesystem on your Solana/Ethereum repo. Green checkmark. You ship. Three months later: $2.3M drained from a private key committed in docker-compose.yml that neither tool flagged as high-confidence. Why? Generic scanners match patterns (regex/entropy). They don't understand web3 key semantics : Blind Spot gitleaks trufflehog drainscan BIP-39 checksum validation ❌ ❌ ✅ Offline address derivation ❌ ❌ ✅ Live balance checks ❌ ❌ ✅ Phantom JSON export detection ❌ ❌ ✅ Solana base58 seed (64-byte) Partial Partial ✅ Token-2022 extension context ❌ ❌ ✅ Entropy + context dedup Generic Generic Web3-aware SARIF 2.1.0 ✅ ✅ ✅ Benchmark: 500+ Web3 Repos Scanned Methodology : Cloned top 500 repos by stars from solana , ethereum , defi , web3 topics. Ran each scanner with default + aggressive configs. Manual verification of findings. Results Summary Metric gitleaks trufflehog drainscan Free Total findings 1,847 3,291 2,156 High-confidence true positives 312 401 687 Web3-specific true positives 89 112 487 False positive rate (high) 34% 41% 3% False negative rate (web3 keys) 73% 68% 4% Avg scan time (500 repos) 12m 47m 8m Key Finding: The 73% Gap Generic scanners missed 73% of web3-specific key types : Phantom/Solflare JSON exports (64-byte arrays) — gitleaks: 0, trufflehog: 12, drainscan: 234 BIP-39 mnemonics with valid checksum — gitleaks: 45 (many false), trufflehog: 67, drainscan: 156 (all validated) Solana base58 seeds — gitleaks: 23, trufflehog: 31, drainscan: 189 EVM keys in .env / .yaml / .toml context — gitleaks: 189, trufflehog: 223, drainscan: 298 Entropy-detected foreign-chain keys (Cosmos, Sui, Near, ed25519 hex) — gitleaks: 0, trufflehog: 0, drainscan: 87 Why drainscan Wins on Web3 1. BIP-39 Checksum Validation = Near-Zero Fal
AI 资讯
The agent posted successfully. To the wrong account.
Back in July I wrote about what it takes to wire an AI agent into social platforms: six OAuth flows,...
AI 资讯
Stop rewriting your API responses in Laravel (Use this Trait instead)
If you are building API-driven applications, nothing clutters up your controllers faster than manually typing out response()->json(...) arrays every single time you need to return data or throw an error. When you have inconsistent response structures, your frontend (and the developers consuming your API) will constantly have to guess whether the data is nested under ['data'] , ['payload'] , or just at the root of the object. The cleanest way I've found to standardize this across an entire application is by creating a dedicated ApiResponse trait. Instead of rewriting your JSON structure in every controller method, create this trait in your app/Traits directory: namespace App\Traits ; use Illuminate\Http\JsonResponse ; trait ApiResponse { protected function success ( mixed $data , ?string $message = null , int $code = 200 ): JsonResponse { return response () -> json ([ 'status' => 'success' , 'message' => $message , 'data' => $data ], $code ); } protected function error ( string $message , int $code = 400 , array | string $errors = []): JsonResponse { // Force errors into an array format for consistent frontend parsing $formattedErrors = is_string ( $errors ) ? [ $errors ] : $errors ; return response () -> json ([ 'status' => 'error' , 'message' => $message , 'errors' => $formattedErrors ], $code ); } } Next, simply use this trait inside your base Controller.php . Now, your actual endpoints become incredibly readable and strictly standardized: namespace App\Http\Controllers ; use App\Models\Task ; use Illuminate\Http\Request ; use Illuminate\Http\JsonResponse ; use Throwable ; class TaskController extends Controller { public function store ( Request $request ): JsonResponse { $validated = $request -> validate ([ 'title' => 'required|string|max:255' , 'description' => 'nullable|string' ]); try { $task = Task :: create ( $validated ); return $this -> success ( $task , 'Task successfully generated' , 201 ); } catch ( Throwable $e ) { // Note: Exposing raw exception messa
AI 资讯
Azure ExpressRoute vs VPN Gateway: the honest comparison
Your datacenter needs to talk to Azure. You can send that traffic through an encrypted tunnel over the public internet, or over a private circuit that never touches it. That single choice — shared road or private rail — decides cost, speed, and reliability. Almost every organization moving to Azure keeps something on-premises, and those two worlds have to connect privately. Azure gives you two hybrid-connectivity options, and they take opposite routes to the same destination: VPN Gateway and ExpressRoute . Understanding them is really understanding one question — does your traffic ride the public internet, protected by encryption, or a dedicated line that bypasses it entirely? VPN Gateway: an encrypted tunnel over the internet Microsoft's description is exact: Azure VPN Gateway "can be used to send encrypted traffic between an Azure virtual network and on-premises locations over the public Internet." Your traffic still travels the ordinary internet, but inside an IPsec/IKE tunnel, so it is private even though the road is shared. It comes in a few shapes: site-to-site (your datacenter's VPN device to Azure), point-to-site (an individual remote worker to the VNet), and VNet-to-VNet . It is quick to stand up, needs no third party, and is inexpensive — the pragmatic default for dev/test and small-to-medium production links. ExpressRoute: a private, dedicated circuit ExpressRoute takes the other road entirely. It "lets you extend your on-premises networks into the Microsoft cloud over a private connection with the help of a connectivity provider." The defining fact: because ExpressRoute connections do not go over the public internet , they offer "more reliability, faster speeds, consistent latencies, and higher security than typical connections over the internet." You are not tunnelling through shared roads; you have a private rail line into Microsoft's network, arranged through a connectivity provider. That extra reliability and consistency costs more and takes longer t
AI 资讯
AI Dev Weekly #23: Qwen4 Architecture Preview, GPT-5.6 Lands in Kiro, Gemini Takes On Whisper
AI Dev Weekly is a Thursday series where I cover the week's most important AI developer news, with my...
AI 资讯
RFLCT: Bringing Runtime Type Metadata to TypeScript 7
If you've built large-scale applications in TypeScript, chances are you've used a Dependency Injection (DI) container. As the creator of InversifyJS, I've spent years thinking deeply about inversion of control, decoupling, and how to make enterprise patterns feel natural in TypeScript. But for all those years, there has been a glaring elephant in the room: our heavy reliance on experimentalDecorators and emitDecoratorMetadata . These compiler flags have served us well, but they are exactly that— experimental . They tie us to legacy decorator implementations, require specific compiler configurations, and often feel like a magic black box that doesn't perfectly align with modern build pipelines. I've spent a lot of time recently thinking about how we could finally drop these flags entirely while keeping the developer experience pristine. With the release of TypeScript 7, I'm thrilled to introduce the solution: 🪞 RFLCT . What is RFLCT? RFLCT is an ahead-of-time (AOT) reflect metadata injector for TypeScript 7. It injects design:symbols and design:arguments directly at build time. Zero decorators. Zero emitDecoratorMetadata . It integrates seamlessly with virtually any build tool (Vite, Rollup, webpack, esbuild) via unplugin , or you can use the built-in CLI using the TypeScript 7 API for standalone tsgo projects. Let's look at how it actually feels to write code with RFLCT. The Magic: Before and After With RFLCT, you annotate the types you want to expose to your runtime metadata using a special Reflect<T> wrapper type. What you write: import { Reflect , resolve } from " rflct " ; interface Shape { sides : number ; } class Polygon { constructor ( public shape : Reflect < Shape > , public label : Reflect < string , { optional : true } > ) {} } // resolve<T>() → the runtime identity of T (Symbol for interfaces, class for classes) container . bind ( resolve < Shape > ()). to ( Polygon ); What RFLCT compiles it to: Notice how the interfaces are safely converted into global
开发者
Blue-green deployment that left the old environment running for weeks, doubling infrastructure cost
The deploy worked. The bill doubled. The blue-green cutover went perfectly. Traffic shifted to green, health checks passed, the team signed off, and moved on. It was one of those rare deployments that goes exactly as planned. Six weeks later, a cost anomaly surfaced in the monthly AWS review. Infrastructure spend had been running at roughly double what it should have been since the deployment date. Every EC2 instance, every RDS node, every load balancer from the blue environment was still running. Serving zero traffic. Billed at full price. For six weeks. Nobody had decommissioned it because nobody owned it after cutover. The team that ran the deployment assumed operations would clean it up. Operations assumed the team that deployed it would tear it down. The blue environment sat in a perfect ownership gap, healthy and idle and expensive, while both teams closed their tickets and moved on. This is the part blue-green deployment guides don't emphasize enough. The strategy is excellent for zero downtime releases and instant rollback capability. The rollback window is the dangerous part. It's open-ended by default, which means the old environment stays alive until someone makes a deliberate decision to shut it down. That decision requires ownership, and ownership requires someone to be responsible for it after the deployment is considered done. The fix is treating decommissioning as part of the deployment itself, not cleanup that happens afterward. Tag every blue environment resource at launch with a TTL: aws ec2 create-tags \ --resources i-1234567890abcdef0 \ --tags Key = DeploymentColor,Value = blue \ Key = CutoverDate,Value = 2026-01-14 \ Key = TTL,Value = 2026-01-21 Then wire Cost Anomaly Detection to alert when a specific environment tag is still generating spend past its TTL. The old environment doesn't get to become invisible just because traffic moved away from it. The deeper issue is that blue-green deployments create a window of parallel infrastructure that m
AI 资讯
Building Local-First Web Apps: Parsing HTML and PDFs to Markdown in the Browser
Local-first and privacy-focused web utilities are having a massive comeback. With browser engines becoming faster and WebAssembly/Web Workers maturing, there is rarely a reason to push sensitive user documents to an external backend for simple conversions. While building MD-Convert (a zero-upload document to Markdown converter), I explored how to parse real-world documents into clean Markdown entirely on the client side. Here is a breakdown of the core architecture and libraries that make purely in-browser document processing possible. 1. Converting Web Articles with Readability + Turndown Converting messy web markup into clean Markdown involves two distinct steps: Content Extraction: Stripping ads, navbars, sidebars, and trackers. HTML-to-Markdown Transformation: Translating semantic DOM nodes into markdown tokens. Mozilla’s @mozilla/readability paired with turndown is an incredible combination for this: import { Readability } from ' @mozilla/readability ' ; import TurndownService from ' turndown ' ; function htmlToCleanMarkdown ( rawHtmlDocument , sourceUrl ) { // 1. Extract pure article content const reader = new Readability ( rawHtmlDocument ); const article = reader . parse (); if ( ! article || ! article . content ) { throw new Error ( ' Unable to extract main content ' ); } // 2. Initialize Turndown const turndownService = new TurndownService ({ headingStyle : ' atx ' , codeBlockStyle : ' fenced ' }); // Ensure image URLs remain absolute turndownService . addRule ( ' absoluteImages ' , { filter : ' img ' , replacement : ( content , node ) => { const src = node . getAttribute ( ' src ' ); const alt = node . getAttribute ( ' alt ' ) || '' ; if ( ! src ) return '' ; try { const absoluteUrl = new URL ( src , sourceUrl ). href ; return `\n\n` ; } catch { return `\n\n` ; } } }); return turndownService . turndown ( article . content ); } Offloading Heavy PDF Parsing to Web Workers Parsing large PDFs using pdf
AI 资讯
Day 32: Rebase Replays Your Commits, and a Restore Inherits Everything You Don't Override
Today's two tasks are both about a new base. A feature branch that needs to sit on top of a master that has moved. A database instance that needs to come back from a snapshot taken when things were fine. In each case, the interesting question is the same: what carries over, and what do you have to say out loud? One Git task, one AWS task. Rebase a feature branch onto master without creating a merge commit, then snapshot an RDS instance and restore it into a new one. The tasks come from the KodeKloud Engineer platform. Rebase: not moving commits, replaying them The requirement was specific, and the specificity is the lesson. A developer's feature branch was behind master. Bring it up to date without losing any feature work, and without a merge commit. That second clause rules out git merge master . Merge joins two histories and records the join, which is the merge commit. Rebase does something else entirely. cd /usr/src/kodekloudrepos/media git branch git log --oneline --graph --all --decorate git checkout feature git rebase master git log --oneline --graph --decorate Git's own documentation describes what happens under git rebase master : it lists the commits on your branch that are not on master, checks out master, and then replays each of your commits on top of it, one at a time, in a way it compares to running git cherry-pick for each one. Replays. Not moves. Every commit that comes out the other side has a new hash, because a commit's identity includes its parent, and the parent is different now. Your work is preserved, the commits carrying it are not the same objects they were. That is exactly why there is no merge commit. Rebase does not join two histories, it rewrites yours so it looks like it was always based on master's current tip. You get a straight line, at the cost of a history that is no longer a record of what actually happened. Two things I had to be deliberate about. Direction. Rebase applies to the branch you are standing on and takes the branch yo
开发者
I Built a Discord Server Discovery Platform
I Started Building a Discord Server Directory I’ve spent a lot of time around Discord communities, and one thing has always bothered me. Finding a good Discord server is harder than it should be. There are thousands of communities for gaming, anime, roleplay, technology, social groups and pretty much every niche you can think of. But finding the right one usually means jumping between invite links, old posts, server lists and search results. At some point I thought, why not build a better way to discover them? That’s how I started working on Dizord. The first version was pretty simple. I wanted a place where a server could be listed, people could discover it, and everything could be organized around interests instead of just one giant list of servers. Then the project started getting bigger. More servers meant more categories and tags. More tags meant better search and filtering. Server information changes constantly, so keeping listings updated became another problem to solve. I’m building the backend with Laravel and working with the Discord API to handle server information and synchronization. There are also a lot of small things behind the scenes that aren't obvious when you simply open a server listing page. One of the things I'm currently working on is making discovery better for smaller communities. A server shouldn't need tens of thousands of members just to be discoverable. The goal is pretty simple: Make it easier to find a Discord community you'll actually want to stay in. The project is still evolving, but the current version is live: https://dizord.com I'm still experimenting with search, categorization, server activity and ways to make a large directory useful instead of overwhelming. If you're building a directory, marketplace, or any project with thousands of constantly changing pages, I'd also be interested in hearing how you handle discovery and indexing at scale.
AI 资讯
An API that returns 200 and does nothing is worse than one that returns an error
I cross-post my articles to dev.to. Looking at the numbers, the posts tagged agents were getting traffic and the one without it had a single view in twenty hours. Obvious fix: add agents to that post. I sent a PUT updating the tags. The response was 200. I opened the post. The tags were unchanged. Three requests, three 200s, three identical responses Assuming I'd malformed the request, I ran the smallest test I could: three PUTs to the same article, sending agents , then python,agents , then the original tags. All three returned 200. All three returned byte-identical bodies — the tags the post was created with. The truth: dev.to tags are immutable after publish, and the API silently ignores the field. Not a 403 saying you can't do that. Not a 422 saying the field is read-only. A 200, and then nothing happens. That one field made me wrong twice The first time was the day before. I'd sent 4 tags and gotten 3 back. My conclusion: dev.to caps tags at 3. That conclusion is entirely reasonable. You send four, you get three, what else would it be? I was confident enough to write MAX_TAGS = 3 into a script comment as an established fact. What actually happened: the tags field was never applied at all. What came back were the three tags from creation time. It had nothing to do with a cap. I could have sent one tag or ten and gotten the same three. One silently ignored field, two wrong conclusions in two days, and I committed one of them to source control as documentation for my future self. That's the real cost. Not the failed request — the false fact I wrote down as knowledge. Why 200 is more dangerous than an error An error interrupts you . It forces a stop, and it usually tells you something true. Even when the message is imprecise, "this did not work" is accurate information. A 200 doesn't interrupt you. You tick the step off and move on. You proceed on a false premise, believing you verified it. Going back through my ops log, this failure mode shows up more than once. A
AI 资讯
The Bug Class AI Coding Agents Keep Introducing (and How We Started Catching It in CI)
The pattern AI coding agents are good at producing a diff that works in the narrowest sense — the function still returns what the test expects. What they're not reliably good at is preserving properties nobody wrote a test for in the first place. The two we kept running into: an authorization check quietly dropped during an agent-driven refactor (nothing failed, because no test covered who was allowed to call the route — only that the route worked), and a rewritten query that behaved fine against a small dev dataset and full-table-scanned the moment it hit production data. Neither shows up in CI as it exists today. Both show up in code review only if the reviewer happens to look at exactly the right five lines out of a few hundred. What we built Agent Code Merge Gate is a free GitHub Action, now live on the GitHub Marketplace , that runs on every pull request and scans the diff specifically for those two regression classes. It runs an offline heuristic pass (fast, no external call) plus one AI-backed pass for a short Executive Summary, and posts a single comment back to the PR that updates on every push rather than piling up duplicates. Deliberately narrow scope — it's not trying to be a general linter. It covers the two failure modes we found ourselves manually re-checking for once AI-generated PRs became the majority of our merge volume. Wiring it into CI Three lines in a workflow file: - name : Agent Code Merge Gate uses : avalonlabs-platform/agent-code-merge-gate@v1.0.0 ``` { % endraw % } No signup and no config needed for the default behavior. Two inputs worth knowing about : { % raw % } `fail-on-critical : true ` turns a CRITICAL finding into an actual failed check instead of just a comment, and `comment-on-pr : false ` if you'd rather build your own notification from the raw `status` output. ## What's next Right now it's diff-scoped — it sees what changed in this PR, not the whole repo's history of how that code got there, which limits how much context it
开发者
I Spawned 1000000 Goroutines. Here's Where 13 GB of RAM Went.
Ask any Go developer what a goroutine costs and you'll get the same answer with the exact byte count:...
创业投融资
Medical device maker Boston Scientific says a cyberattack is causing a ‘global disruption’ to its operations
The company won't say if medical devices are affected or if any customer data was exfiltrated.
AI 资讯
How I Diagnosed and Fixed Keyword Cannibalization Between Two Nearly-Identical Blog Posts
I thought publishing more useful content would automatically give my website more opportunities to rank. Then I noticed something uncomfortable. Two articles on my site were covering almost the same subject. Both were useful. Both were properly indexed. Both answered similar questions. And both were competing for overlapping search intent. I had accidentally created a small SEO architecture problem inside my own blog. I'm building "SabrTime.in" ( https://sabrtime.in/ ), a small Islamic companion app focused on practical digital tools for everyday worship. As a solo developer, I also manage the website, content, SEO, and product development myself. While working on the site's content, I published two articles around Tasbeeh: How to Do Tasbeeh — A Complete Guide Digital Tasbeeh Counter: How It Works & Why Muslims Are Switching The first article is about the practice itself. The second is supposed to be about the technology and use case of digital Tasbeeh counters. Sounds different enough, right? At first, I thought so too. But when I looked at the actual content and search intent, the overlap became obvious. The Problem Wasn't Duplicate Content This is where SEO gets misunderstood. Keyword cannibalization doesn't necessarily mean you have two pages containing identical paragraphs. The more interesting problem is intent overlap. If two URLs are trying to satisfy essentially the same searcher's question, a search engine has less information about which page should be the primary result. For example, imagine these two pages: /page-a "How to Do Tasbeeh" /page-b "Digital Tasbeeh Counter" Their titles are different. But if both pages explain: what Tasbeeh means how many times to recite it common Tasbeeh counts the same hadith how to count Tasbeeh why Muslims use a counter FAQs about Tasbeeh ...then the distinction between the pages starts becoming blurry. That was happening on my site. The two articles were not technically duplicates. But parts of their search intent were d
开发者
I did Golden Images
Golden Images How I Stopped Manually Logging Into Every New Server The problem Every time I spun up a new server for a service, it worked but it wasn't actually ready . There was always one manual step left: log in, run through some interactive setup, get the application into a working state. Only after that could the server actually do its job. For one server, that's a minor annoyance. For a fleet that's supposed to scale up and down on demand, it's a dealbreaker. You can't call something "automated provisioning" if a human still has to remote in and click through a setup wizard before it's usable. The fix: capture the setup once, replay it everywhere The pattern here is usually called a golden image and the idea is simple: instead of repeating a manual setup step on every new machine, do it once, capture the result of that setup, and have every future machine apply that captured state automatically during provisioning. Concretely, I built a small tool that: Connects to a machine that's already been through the manual setup and is in a known-good state. Packages up just the state that setup actually produced not the whole machine, just the specific files/config that resulted from the manual steps. Uploads that package to storage, versioned. Then the provisioning script for every new machine downloads that package and applies it automatically as part of boot no human, no remote session, no wizard. The mistake worth mentioning My first version of this captured too much. Instead of packaging just the setup-derived state, it grabbed an entire application data folder which included the application's own installed binaries, not just the configuration that setup had produced. That meant every new machine, when it applied the "golden" package, got its fresh application install silently overwritten with whatever binary version happened to be running on the machine I captured from. New servers ended up running an older version of the software than the one they'd just install
AI 资讯
Observability Stack: Prometheus, Node Exporter & Grafana
A solid observability setup usually comes down to three pieces working together: something that collects metrics, something that exposes system-level metrics, and something that visualizes it all. Here's what each one does and how to install them. The Theory: How This All Fits Together Before installing anything, it helps to understand the model, because it's a bit different from how logging or alerting tools usually work. Pull, not push. Most people's first instinct is "the app should send its metrics somewhere." Prometheus flips that around — it pulls metrics on a timer instead. Every target (a machine, a service, an app) exposes a simple HTTP endpoint, usually /metrics , that just returns plain text numbers. Prometheus visits that endpoint every N seconds (the "scrape interval") and saves whatever it finds, with a timestamp attached. Nothing gets pushed to Prometheus — Prometheus goes and asks. This means for anything to show up in Prometheus, it has to satisfy one requirement: something has to expose a /metrics endpoint Prometheus can reach. That's the whole game. Everything else in this stack exists to satisfy that one requirement or to make the data useful afterward. Why Node Exporter exists. Your operating system doesn't naturally speak Prometheus's language — it doesn't expose CPU/memory/disk stats as a /metrics endpoint by default. Node Exporter's only job is to read stats the OS already tracks (via /proc and /sys on Linux) and republish them in the text format Prometheus expects, on port 9100. It's a translator, not a monitoring tool by itself — it collects nothing, decides nothing, alerts on nothing. It just answers "what does this machine look like right now?" whenever asked. Why Prometheus itself is separate. Prometheus doesn't know anything about CPUs or memory — it has no idea what it's scraping. It just knows: "go hit this list of URLs on a schedule, and remember what comes back." The intelligence is in the config (which targets to scrape, how often)
AI 资讯
DigitalOcean App Platform vs Peon: Managed PaaS or Your Own Droplet?
DigitalOcean App Platform is a metered system charged by app; Peon provisions limitless services to your existing Droplet. A practical pricing and feature comparison. The same cloud, but two very distinct approaches. There are two methods of deploying your app with DigitalOcean, and the pricing disparity between the two may be much greater than you expected. App Platform is the managed PaaS service: you integrate with the code repository, and DigitalOcean provisions, deploys and maintains your app. The costs include monthly rates per component starting at $5 for web services plus separate payments for workers plus $7+ for a development database and $15+ for a production database. The alternative way is just a regular Droplet: either a $6 VPS (1 CPU, 1 GB) or a $12 VPS (1 CPU, 2 GB) with ability to run as many containerized apps as it has available resources. Traditionally, the droplet approach required self-managing your infrastructure, exactly what a platform like Peon fixes. Cost at small scale, with real numbers For example, take a regular indie/agency load of three small apps, shared Postgres, and Redis. In App Platform, this would cost about $37 a month, where three web services ($15), a managed dev database ($7), and Redis ($15) are the cheapest tier offerings (share CPU, limited to 512 MB memory). On one $12 Droplet using Peon, $12 for the Droplet, $6 for three projects running, all with access to 2 GB of memory plus. About $18 per month total, and the ability to use as much memory as the application needs (without being limited to 512 MB slices). And this ratio grows with every additional service, as the costs for the additional Droplet resources are already included. The fourth app on App Platform will add somewhere between $5 and $12 of the bill; on your own Droplet, $2. Comparison of features Push Git deployment: both, with build log Automatic HTTPS for custom domains: both Roll out and roll back with zero downtime: both Database support: App Platform nee