<?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: Saimul Islam</title>
    <description>The latest articles on DEV Community by Saimul Islam (@saimulislamdl).</description>
    <link>https://dev.to/saimulislamdl</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2102508%2F731a7a40-a68a-4ee1-8318-0d1d14988552.png</url>
      <title>DEV Community: Saimul Islam</title>
      <link>https://dev.to/saimulislamdl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saimulislamdl"/>
    <language>en</language>
    <item>
      <title>Stop Paying for Vercel: How to Self-Host Your Deployments on a VPS and Keep the Same Workflow</title>
      <dc:creator>Saimul Islam</dc:creator>
      <pubDate>Fri, 04 Sep 2026 05:19:58 +0000</pubDate>
      <link>https://dev.to/saimulislamdl/stop-paying-for-vercel-how-to-self-host-your-deployments-on-a-vps-and-keep-the-same-workflow-4cce</link>
      <guid>https://dev.to/saimulislamdl/stop-paying-for-vercel-how-to-self-host-your-deployments-on-a-vps-and-keep-the-same-workflow-4cce</guid>
      <description>&lt;p&gt;Push-to-deploy, automatic HTTPS, and preview environments on a $24 droplet with Dokploy, plus the four mistakes to skip on the way.&lt;/p&gt;

&lt;p&gt;For two years, deploying was the easiest part of my week. I pushed to main, closed my laptop, and the site updated itself. I never thought about servers, which was the entire point of paying for it.&lt;/p&gt;

&lt;p&gt;Then I opened the invoice.&lt;/p&gt;

&lt;p&gt;$198 for the month. I read it twice, because the number did not match what I was running: a Next.js frontend with a few thousand monthly visitors, a small API moving JSON around, one Postgres database holding a couple hundred megabytes, and a nightly cron job. Four small things, and I knew that launching a fifth would push the bill up again.&lt;/p&gt;

&lt;p&gt;Here is what is worth understanding before you decide whether any of this applies to you. You are not paying managed platforms for compute. Compute is cheap. You are paying because somebody else already set up the reverse proxy, the TLS certificates that renew themselves, and the pipeline that turns a git push into a running container. That is real work, and it was worth every cent for two years. So the question got narrower: is it still worth it, now that I know which of those features I actually use?&lt;/p&gt;

&lt;p&gt;What follows is the whole setup, end to end: installing Dokploy, connecting your Git provider, getting deploys on every push, domains with automatic HTTPS, object storage for user files, scheduled backups, and monitoring. The workflow you land on is the one you already have. You push to main, you close your laptop, and the site updates itself.&lt;/p&gt;

&lt;p&gt;It took me a weekend, and I have left all four of my mistakes in, because those are the parts no documentation warns you about. If you can ship an application but have never really run a server, this is written for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why My First Attempt at This Failed
&lt;/h2&gt;

&lt;p&gt;My first instinct was to do it entirely by hand, and that failure is the reason Dokploy made sense to me later. I rented a VPS, installed Node, set up PM2 to keep the process alive, hand-wrote an Nginx config, and ran Certbot for a certificate. I felt clever for about four days.&lt;/p&gt;

&lt;p&gt;Then deploying turned out to mean SSH-ing in to run &lt;code&gt;git pull&lt;/code&gt;, which is fine right up until you are doing it from your phone because something has broken. No build logs, no deployment history, and a rollback plan that was really just hope. Adding a second app meant another Nginx block, another port, another process to babysit.&lt;/p&gt;

&lt;p&gt;The problem was never that servers are hard. It was that I had hand-built a worse deployment platform and was now responsible for maintaining it. Once I wrote down what I actually wanted, it became shoppable: something that handled the reverse proxy, the certificates, the builds, the restarts, and the rollbacks, on hardware I paid for directly. That is what Dokploy is. So I went and got a server to put it on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the Server, and the Mistake I Made in the First Hour
&lt;/h2&gt;

&lt;p&gt;I went with DigitalOcean. Hetzner, Vultr, and OVH are all fine too, and cheaper if you are optimising purely on price, but I already had projects there and wanted one dashboard rather than four.&lt;/p&gt;

&lt;p&gt;Dokploy says the minimum is 2GB of RAM. That is technically true and practically a trap, because you build your application on the same box you deploy to.&lt;/p&gt;

&lt;p&gt;I found out the hard way. My first build died on the $12 droplet. Not failed, died. The log stopped part way through with no error, and I spent twenty minutes blaming my Dockerfile before finding the kernel had killed it for using too much memory. Skip the entry tiers and start on the 4GB droplet at $24, which fits the app, the database, and the build at once. Ubuntu 24.04, because boring is correct here.&lt;/p&gt;

&lt;p&gt;One more thing before you install anything, and I nearly skipped it. Ten minutes on the basics: SSH key instead of password login, password login disabled, firewall with ports 22, 80, 443, and 3000 open. When I checked my auth log a day later there were thousands of failed login attempts. Nobody knew my server existed. That is simply what the internet does to a public IP.&lt;/p&gt;

&lt;p&gt;With the box locked down, the install itself is almost anticlimactic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing Dokploy Takes One Command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://dokploy.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few minutes of output, and it is done. It is worth seeing what that script did for you, because you now own a real stack rather than a magic box. It installed Docker, set up Swarm mode so containers restart on their own when they die, deployed Traefik as your reverse proxy, and started a small database for Dokploy itself. Every one of those I configured by hand the first time, and got wrong at least once.&lt;/p&gt;

&lt;p&gt;Open your server IP on port 3000 and create an admin account. Then, before deploying anything, point a subdomain like &lt;code&gt;panel.yourdomain.com&lt;/code&gt; at the server, give the dashboard that domain, turn on HTTPS, and close port 3000. Your control panel should not sit on an open IP with only a password in front of it.&lt;/p&gt;

&lt;p&gt;You now have a platform. What it does not have yet is your code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting GitHub Is the Decision That Pays Off Later
&lt;/h2&gt;

&lt;p&gt;Dokploy pulls your code two ways, and the choice matters more than it looks at the time. The first is a generic Git connection over SSH, which works with GitLab, Bitbucket, Gitea, or anything else that speaks SSH. Generate a key in the SSH Keys section, add the public half to your provider, and use the &lt;code&gt;git@&lt;/code&gt; URL rather than &lt;code&gt;https&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second, if you are on GitHub, is the Dokploy GitHub App. Two extra minutes, and it is the best decision in this story. That single step gave me automatic deploys on push, watch paths, and preview environments for pull requests, with no further configuration. Those are exactly the three things people assume they give up when they leave managed hosting, and this is where you keep them. Click the right button early, because switching later is far more annoying.&lt;/p&gt;

&lt;p&gt;Then create a project, pick your repository and branch, and paste your environment variables into a box that works like a &lt;code&gt;.env&lt;/code&gt; file. One detail that confused me: Dokploy clones the repository onto your own server and builds it there, which is why server memory mattered so much a moment ago.&lt;/p&gt;

&lt;p&gt;Then I hit deploy. And then I hit it three more times.&lt;/p&gt;




&lt;h2&gt;
  
  
  My First Three Deploys Failed, and That Was the Useful Part
&lt;/h2&gt;

&lt;p&gt;Every tutorial I read showed a green checkmark on the first attempt, so when mine went red I assumed something was structurally wrong. It was not. Three ordinary mistakes, each of which explained a piece of how the system fits together.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Port
&lt;/h3&gt;

&lt;p&gt;The build worked, the container started, and the site returned a gateway error, because I had never told Dokploy which port my app listens on. That field lives in the domain settings rather than the build settings, which is why I missed it. The lesson is worth keeping: your app is never exposed to the internet directly. Traefik forwards public traffic inward, which is why you never open your app port in the firewall.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2l0e33lsxkpeh60swimy.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2l0e33lsxkpeh60swimy.png" alt="The path a request takes, and why the container port lives in the domain settings" width="799" height="316"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The path a request takes, and why the container port lives in the domain settings.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Missing Environment Variable
&lt;/h3&gt;

&lt;p&gt;The second was a missing environment variable. It built fine and crashed on startup, and the container log said so in its first line while I was staring at the build log. Those answer different questions: why it would not compile, versus why the compiled thing will not run.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The Build Type
&lt;/h3&gt;

&lt;p&gt;The third was the build type, which is how Dokploy turns your repository into a running app at all. There are five options, and the default guesses for you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If your project is&lt;/th&gt;
&lt;th&gt;Choose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A standard Next.js, Django, or Laravel app&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Railpack, the modern default, or Nixpacks if it gives you trouble&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Already carrying a Dockerfile you trust&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dockerfile, for full control including build secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A static site from Vite, Astro, or Hugo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nixpacks with a Publish Directory, or Static if pre-built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Being moved directly off Heroku&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Buildpack, using your existing Heroku or Paketo config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I switched to a Dockerfile because my build needed a step the automatic builders were skipping. Fourth deploy, green checkmark. The site loaded, at an IP address, which did not feel like it counted yet.&lt;/p&gt;


&lt;h2&gt;
  
  
  Giving It a Real Domain and Free HTTPS
&lt;/h2&gt;

&lt;p&gt;This happens in two steps, and doing them in the wrong order caused my next mistake. At your registrar, create an A record pointing your domain at the server IP. Then in Dokploy, open your app, go to Domains, and add one. Set the Host, Path, and Container Port, flip the HTTPS toggle, choose Let’s Encrypt, and the certificate and its renewal are handled from then on.&lt;/p&gt;

&lt;p&gt;My mistake was enabling HTTPS fifteen seconds after creating the DNS record. Let’s Encrypt tried to verify I owned the domain, found nothing, and failed with an error that never mentioned timing. Run &lt;code&gt;dig +short yourdomain.com&lt;/code&gt; first, and only enable HTTPS once it prints your server IP back.&lt;/p&gt;

&lt;p&gt;One more trap catches a lot of people. If your DNS is on Cloudflare, the orange cloud means Cloudflare is proxying your traffic, and verification fails with an error that never mentions Cloudflare either. Set the record to DNS only until the certificate is issued, then switch proxying back on if you want it.&lt;/p&gt;

&lt;p&gt;While you are in the dashboard, add Postgres too. Resist the urge to expose its port, because open database ports are among the most scanned things on the internet. Your app reaches it by service name over the internal Docker network, as in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://user:pass@my-app-db:5432/mydb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that was the setup finished. Custom domain, valid certificate, deploys on push, a database, all for $24 a month. It ran perfectly for two weeks, which is when the real education started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Weeks In: The Monorepo Problem
&lt;/h2&gt;

&lt;p&gt;The first crack was small. My repo is a monorepo, and every push rebuilt everything in it. I would fix a typo in a markdown file and watch three services redeploy and send me notifications for things that had not changed.&lt;/p&gt;

&lt;p&gt;Watch paths fix this. In your app’s Git settings you list the files and folders that app actually cares about, and it only rebuilds when something inside them changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/web/**
packages/ui/**
!apps/web/**/*.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two asterisks match across directories, and a leading exclamation mark excludes. One caveat before you go debugging: this works out of the box on GitHub, but on GitLab, Bitbucket, or Gitea you need automatic deployment working first, because watch paths filter automatic deploys. With nothing to filter, they look ignored.&lt;/p&gt;

&lt;p&gt;That one was an annoyance. The next one was not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Weeks In: The Day All My User Uploads Disappeared
&lt;/h2&gt;

&lt;p&gt;I shipped a routine change, nothing dramatic, and somebody told me their profile picture was gone. Not just theirs. Every file any user had uploaded since launch.&lt;/p&gt;

&lt;p&gt;The cause is obvious in hindsight and catches almost everyone new to containers. I had been saving uploads to a folder inside the application, so when I deployed, Dokploy built a fresh container from my repository and replaced the old one. My repository does not contain user uploads. Containers are meant to be disposable, and that is exactly what makes deploys and rollbacks safe. My mistake was putting data that has to survive inside a thing designed not to.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz5orpw65d5n252vvinpd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz5orpw65d5n252vvinpd.png" alt="Why the uploads vanished, and where they should have been living instead" width="799" height="373"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Why the uploads vanished, and where they should have been living instead.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For user files the answer is object storage, and I picked Cloudflare R2 for one reason: it charges nothing for egress. Everyone else charges you to read your own files back out, and that cost scales with traffic rather than storage. R2 is $0.015 per GB per month with zero egress, against roughly $0.023 per GB plus $0.09 per GB egress on S3, and the free tier covers 10GB and 10 million reads a month.&lt;/p&gt;

&lt;p&gt;Best of all, R2 speaks the S3 API. Create a bucket, generate an API token with Object Read and Write, note your account ID, and point your existing S3 client at it:&lt;br&gt;
&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-s3&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;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.r2.cloudflarestorage.com`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretAccessKey&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;Setting that up turned out to be lucky, because a few days later I needed somewhere to put backups.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Night I Realised I Had No Backups at All
&lt;/h2&gt;

&lt;p&gt;I was running a migration late one night, and for about ninety seconds I believed I had destroyed my users table. I had not, in the end. But in those ninety seconds I went looking for my backups and found something worse than a bad migration. There were none. Three weeks of production data, not a single copy anywhere.&lt;/p&gt;

&lt;p&gt;The embarrassing part is that Dokploy had this built in the whole time, in a tab I had scrolled past a dozen times. On the schedule you set, it runs a proper database dump, the same &lt;code&gt;pg_dump&lt;/code&gt; you would run by hand, and ships it to S3-compatible storage. Which is why the R2 bucket from the last section quietly became my backup destination too, on the same free tier.&lt;/p&gt;

&lt;p&gt;Add the destination once under Settings, then Destinations, using your R2 access key, secret key, bucket, the region set to &lt;code&gt;auto&lt;/code&gt;, and the same endpoint as above. Hit the test button before you trust it, because a destination that fails silently is worse than none. Then open your database, go to Backups, and set a schedule. I use &lt;code&gt;0 3 * * *&lt;/code&gt; for daily at 3am.&lt;/p&gt;

&lt;p&gt;Now the part that matters more than everything above it. A backup you have never restored is not a backup, it is a file you feel good about. The week after setting this up I spun up a throwaway Postgres service and restored into it on purpose. Ten minutes, and it worked. Do that once while nothing is on fire. Then be as pleased with yourself as I was, right up until you try to back up the next thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Do When There Is No Database to Back Up
&lt;/h2&gt;

&lt;p&gt;My other service had no database. It kept its state in a folder, which is the shape of anything using SQLite or any self-hosted tool with a working directory, so the backup feature had nothing to point at.&lt;/p&gt;

&lt;p&gt;Dokploy handles this with volume backups, which meant finally understanding a distinction I had been ignoring. A bind mount points at a folder on the host machine. A named volume is storage Docker itself creates and manages, referred to by name. I assumed that was stylistic. It is not, because volume backups only work with named volumes, so a bind mount means migrating first.&lt;/p&gt;

&lt;p&gt;Creating one is easy. Go to Advanced, then Mounts, and add a Volume Mount with a name like &lt;code&gt;app_data&lt;/code&gt; and the path where it appears inside the container. One detail that will confuse you at restore time: for Compose services, Dokploy prefixes the volume name with the app name, so &lt;code&gt;app_data&lt;/code&gt; exists as &lt;code&gt;myapp-abc123_app_data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You also choose whether to stop the container while the backup runs. Stopping is safer, since nothing is writing while files are copied. I stop mine, because forty seconds of downtime at 3am costs nothing and a corrupted backup would cost a great deal.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2w0tup5h2xh1nnx0kch1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2w0tup5h2xh1nnx0kch1.png" alt="The finished storage setup: uploads written live, database and volumes backed up on a schedule, all into one bucket" width="799" height="318"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The finished storage setup: uploads written live, database and volumes backed up on a schedule, all into one bucket.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With that, my data was safe. So naturally the server got slow.&lt;/p&gt;


&lt;h2&gt;
  
  
  A Month Later: Why Is Everything Suddenly Slow?
&lt;/h2&gt;

&lt;p&gt;Everything got sluggish at once, across all my apps, which is the least helpful symptom possible because it tells you the problem is shared and nothing more.&lt;/p&gt;

&lt;p&gt;Under my old setup I would have SSH-ed in, run &lt;code&gt;top&lt;/code&gt;, squinted at container-generated process names, and guessed. Instead I opened the dashboard, where every application has its own view of CPU, memory, disk, and network usage. I found the culprit in two minutes: a background job in my API had a runaway loop and was eating almost all the memory on the box. Live logs and deployment history did the rest. A surprising number of mysteries resolve the moment you notice the slowdown began at the exact minute of a deploy.&lt;/p&gt;

&lt;p&gt;I fixed the loop, obviously. More usefully, I set a memory limit on that app under Advanced, then Resources, so it can never take the whole server down again. If it exceeds the limit it restarts rather than consuming everything around it. I now set limits on everything at double normal usage, plus alert thresholds pointed at Discord so the server tells me before a user does.&lt;/p&gt;

&lt;p&gt;Worth being straight about the limits. Self-hosted gives you per-container metrics, logs, deployment history, resource limits, and threshold alerts. The advanced dashboard is part of Dokploy’s paid cloud tier. For a handful of apps the built-in view is plenty, and if you outgrow it, Dokploy ships a Prometheus extension as a template that gives you Grafana on the same box.&lt;/p&gt;

&lt;p&gt;That is the last of my four mistakes. One more warning, though, and it is the thing that quietly kills self-hosted setups around the three month mark.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Thing That Will Break in Three Months
&lt;/h2&gt;

&lt;p&gt;Your disk will fill up. Every deploy builds a new Docker image, and old images and build caches do not delete themselves. You go from comfortable to out of space with no warning at all, and your deploys start failing with errors that never say "disk full".&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="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;                    &lt;span class="c"&gt;# how much space is left&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-af&lt;/span&gt;  &lt;span class="c"&gt;# remove unused images and caches&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that occasionally, and update Dokploy itself from the dashboard every month or so, since it is your software now.&lt;/p&gt;




&lt;h2&gt;
  
  
  So, Was It Worth It?
&lt;/h2&gt;

&lt;p&gt;$198 a month became $24, and R2 is still inside the free tier. It cost me a weekend including every mistake above, and about thirty minutes a month since. Nothing about my day-to-day changed: deploy on push, automatic HTTPS, preview environments, and one-click rollbacks all survived the move. That droplet now runs four applications instead of one, and adding a fifth would not change the bill.&lt;/p&gt;

&lt;p&gt;What I gave up is real, and I would rather say it plainly than sell you something. One server is one point of failure, and if the machine dies, everything on it is down until I bring it back. My answer is backups I have actually tested, and knowing a fresh server plus a restore is a couple of hours rather than a catastrophe.&lt;/p&gt;

&lt;p&gt;So do not do this if you are pre-launch and your only real job is shipping features, because every hour on infrastructure then is an hour not spent finding out whether anyone wants the thing. Do not do this if nobody can respond when the server has a problem, because a managed platform is partly an on-call rotation you are renting. And if your traffic is global and latency-sensitive, remember that one server is in one place.&lt;/p&gt;

&lt;p&gt;What I got beyond the money mattered more than I expected. Deployment used to be a black box I posted code into and hoped about. Now I know what a reverse proxy does, why containers are disposable, and where my data actually lives. The anxiety about next month’s bill is gone too, not because $24 is nothing, but because it does not move when I have a good week.&lt;/p&gt;

&lt;p&gt;If you want to try this, rent a box, spend ten minutes on SSH keys and a firewall, run one install command, and deploy something small you would not mind breaking. Give yourself an afternoon. Worst case, you destroy a server costing a few dollars and start again, which is a cheap way to learn how your own infrastructure actually works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.dokploy.com" rel="noopener noreferrer"&gt;Dokploy Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.dokploy.com/docs/core/applications/build-type" rel="noopener noreferrer"&gt;Dokploy Build Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.dokploy.com/docs/core/watch-paths" rel="noopener noreferrer"&gt;Dokploy Watch Paths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.dokploy.com/docs/core/databases/backups" rel="noopener noreferrer"&gt;Dokploy Backups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/r2/pricing/" rel="noopener noreferrer"&gt;Cloudflare R2 Pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>nextjs</category>
      <category>docker</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
