AI Will Kill Your Project and You Won't Even Notice. Part One: Deploy A developer recounts the deployment challenges of an AI-powered astrology Telegram bot that never made a cent and was shut down. The project used two microservices and a free database, leading to separate bills and infrastructure complexity. The developer recommends Infrastructure as Code as a lifeline for beginners and praises Fly.io for its built-in IaC and simple deployment. I've got an idea for a series of posts about our vibe-coding problems. Today I'll talk about the lumps I took in the part that was hardest for me: deploy. My favorite pet project was about astrology: you'd send your birth data to a Telegram bot, the service would calculate your natal chart, and an AI would answer questions about it. In six months the project didn't make a single cent, and one fine day, right in the middle of eclipse season , I shut it down. But along the way it walked me into a decent number of gotchas, and those I want to share. So. You have a brilliant idea for a brilliant service. You talked it over with an AI, the AI produced code and promises of money raining down. Just one small thing left: that code currently lives — and probably works — only on your laptop. And it needs to be on the internet. I see three options, and two of them rule themselves out. Locally, from your own computer. Forward the ports, set up a domain, make sure the machine doesn't fall asleep, figure out security, never turn the laptop off. I can't do that. I'm just a cat. On a rented bare VPS. They hand you a blank box — install the updates yourself, the firewall, the web server, the certificates, the monitoring, patch it all yourself until the end of time. I can't do that either. I've got tiny paws. Which leaves specialized hosting — it takes some of this off your hands. Then the question becomes: which one. I had three. I had two microservices: one calculated natal charts, the other talked to the human via AI. You might tell me this is overkill, but I had a serious, important reason that I may write about in a separate post spoiler: an AI can quietly drag in a dependency whose license forces you to open-source your closed code — and, well, oops . Whatever the reasons, I needed to roll out two microservices, with one facing the internet and the other listening only to the first. Not every host makes that easy enough to set up. By the way, an important note for your wallet: every microservice is a separate bill. Two microservices inside a single project of mine = I pay twice. Keep that in mind when an AI cheerfully suggests "let's split this out into its own service." If you don't have a DB, skip this section. I did have one: I needed to store natal charts, plus subscriptions and payment dates as if there were any . My database was hosted separately, and for free. I'm going to tiptoe past that "for free" for now, because a free DB can become a problem, and that deserves its own post. For now remember one thing: when you're choosing a host, look separately at how it hosts databases. Many charge for it, and free tiers can have nasty surprises. There are hosts that position themselves as "for complete beginners": every setting through a beautiful UI, just click the checkboxes. Sounds great, but I wouldn't. Here's why: you're tied to that host forever — your configuration lives in their UI, not with you; checkboxes have to be watched and ticked by hand, and you have two hands and a memory that leaks; and most importantly — you can't ask an AI to check your checkboxes for you. Code, you can. Which leads to an unexpected conclusion: Infrastructure as Code is the ideal vibe-coder option. Not an advanced topic for DevOps graybeards — it's a lifeline for beginners. Your infrastructure is described in a text file, the file sits next to your code, and you can hand it to an AI and ask, "take a look, did I screw anything up?" You can't check checkboxes that way — first you have to find them all in that beautiful, trendy UI. I tried several hosts. I won't describe the ones that didn't work out — in case of lawyers. But I liked Fly.io http://Fly.io , and here's why: that same Infrastructure as Code, out of the box; deploy with a single command straight from the folder — fly deploy , no git required, no pushes, just from the directory with your code; controls for who talks to whom over the network — which service accepts requests from the internet, and which one only answers another service. This is where their ad would go, if anyone had paid me. Nobody did, so let me tell you about the rough edges too. At first it seemed to me that Fly was magic: it takes my code and figures out how to run it. It didn't invent a thing. It dutifully executed four files that I had written myself beforehand kidding, of course — "myself," the AI wrote them for me : Dockerfile two of them, one per service — I had these from the start, because I never managed to run the astrology calculation library without one. But in general, I sincerely and wholeheartedly recommend doing all local development in containers — it will save your sanity. But alas, you're finally going to have to learn Docker in 2026. fly.toml also two — a description of how the service lives on the platform: name, region, machine size, which ports are exposed, health checks. I'll write about these in detail below. The fly deploy command reads fly.toml from the folder, builds an image from the Dockerfile , uploads it and rolls out a machine with the described parameters. No magic whatsoever. : A reminder of the setup: I have two apps. dearstarsbot — the chatterbox, it faces outward. sweservice — the natal chart calculator, it should listen only to the bot and to nobody else. The chatterbox goes to the calculator for the math. That's the whole topology. The chatterbox app = 'dearstarsbot' primary region = 'fra' build build-target = "bot" env METRICS PORT = "8081" CHART API URL = "http://sweservice.flycast/chart" CITY = "London" OPENAI MODEL = "gpt-4o" SUPPORT CHAT ID = "12345666" http service internal port = 8081 force https = true auto stop machines = 'off' auto start machines = true min machines running = 1 max machines running = 1 processes = 'app' http service.checks interval = "10s" timeout = "2s" grace period = "5s" method = "GET" path = "/health" vm memory = '1gb' cpu kind = 'shared' cpus = 1 memory mb = 1024 Going line by line through what isn't obvious: build-target = "bot" — needed if your Dockerfile has several stages FROM ... AS bot ; then Fly builds not the whole Dockerfile but the specific stage named bot . If there are no stages, you don't need the line. env — this is the non-secret config: the metrics port, the address of the second service, the default city, the OpenAI model, and my personal chat id so I get notifications. Note: no keys here. CHART API URL = "http://sweservice.flycast/chart" — the first wire between the services. This is the address the chatterbox goes to for a chart calculation. Let's break the address down: sweservice is the name of the second app, .flycast is its private address inside my network on Fly, and http:// not https — because flycast only ever speaks http. Remember that http , it'll come up again. internal port = 8081 — the port the chatterbox listens on inside the container. Functionally the chatterbox only has outgoing traffic requests to the Telegram API , but I also had metrics, and this is the port they came out of. force https = true — public service, everyone gets pushed to https. Makes sense. auto stop machines = 'off' + min = 1 + max = 1 — the chatterbox lives in exactly one instance and never falls asleep. Why — I'll explain in the gotchas section. http service.checks — every 10 seconds Fly pokes /health and decides whether the service is alive. This will also come up in the gotchas. vm — the parameters of the machine Fly will run the app on. cpu kind = 'shared' — a shared processor; I'll talk about it a bit further down, but not in the gotchas. The calculator: app = 'sweservice' primary region = 'fra' build build-target = "final" env METRICS PORT = "8080" CGO ENABLED = 1 CITY = "London" http service internal port = 8080 force https = false auto stop machines = 'stop' auto start machines = true min machines running = 1 max machines running = 1 processes = 'app' vm memory = '1gb' cpu kind = 'shared' cpus = 1 Here we mostly look at the differences: CGO ENABLED = 1 — a fairly exotic line, needed only if, like me, you have Go on top of C: inside this service lives a C library for the calculations. If you don't have Go on top of C, forget you ever saw this line. internal port = 8080 — the second wire between the services. Remember CHART API URL = ".../chart" on the chatterbox? Well, /chart lives on exactly this port. The port the calculator listens on is exactly the port the chatterbox knocks on. force https = false — and this isn't an oversight, it's a requirement. flycast only works over http, and if you put true here, the private connection between the services simply breaks. That same http:// from the chatterbox's address, just seen from the other side. auto stop machines = 'stop' — supposedly "let it sleep when nobody's asking." But right next to it sits min machines running = 1 , and that 1 won't let the machine fall asleep. Which means 'stop' here quietly does nothing. Oops.And one more oops: look at both files again and find the line that makes sweservice private. It isn't there. Both services declare http service . Both, if you go by fly.toml alone, are public. Privacy isn't described in the config at all. It comes from somewhere else: sweservice simply has no public IP address — only a private flycast one. Unfortunately, there's no way to do this through fly.toml , only through the beautiful UI or the terminal: fly ips allocate-v6 --private assign a private flycast address fly ips list check that there are no public addresses fly ips release