# EthioDeploy: full documentation > Full text of the EthioDeploy docs, deploy guides, comparisons and pricing FAQ. The short index is at https://ethiodeploy.com/llms.txt. # Pricing URL: https://ethiodeploy.com/pricing ### Questions **Is there a free plan?** Yes. The Free plan costs $0 and runs one project on a .ethiodeploy.app subdomain with a 512 MB managed Postgres, MySQL or Redis addon. Free projects sleep after 30 minutes without traffic and wake on the next request. **What does usage-based mean on Pro and Business?** Paid plans have no fixed project cap. Each includes a monthly compute quota: about 6 small sites or 2 mid-sized apps on Pro ($10/month), and about 30 small sites or 11 mid-sized apps on Business ($30/month). Usage past the quota is taken from your prepaid wallet. **Can I pay in Ethiopian Birr?** Yes. Every plan can be paid by local bank transfer in ETB: Pro is 1,399 ETB and Business is 4,999 ETB per month. The transfer is verified against the bank, usually within seconds, and each payment covers 30 days. Outside Ethiopia, pay by card in USD. **Can I cancel at any time?** Yes. Card plans renew monthly until you cancel from Settings → Billing, and the plan stays active until the paid period ends. Bank-transfer plans don't renew on their own; if you don't pay again, the account moves back to Free after a 2-day grace period. **Is the AI coding agent included in hosting plans?** No, it's a separate add-on. Every account gets a one-time free trial of 100,000 tokens. After that, agent plans are Starter ($5/month), Pro ($20/month) and Max ($50/month), with access to Claude, GPT and 100+ other models. **Do I need a paid plan for a custom domain?** Yes. Custom domains come with Pro and Business. You can buy a domain from the dashboard on any plan, but attaching your own domain to a project needs a paid plan. # Docs ## Getting started URL: https://ethiodeploy.com/docs/getting-started Updated: 2026-09-29 Sign in with GitHub, install the EthioDeploy app, and deploy your first project in minutes — no server, Dockerfile, or YAML required. Go from a GitHub repo to a live URL in a few minutes. There's no server to set up, no Dockerfile to write, and no YAML to maintain: we detect your stack, build it, and deploy it on every push. ### Before you start - A GitHub account, and a repo containing the app you want to deploy. - An app that starts an HTTP server (or, for a bot or queue consumer, a long-running process; see [Web services & background workers](https://ethiodeploy.com/docs/service-types)). - For web apps, listen on `0.0.0.0` and read the port from the `PORT` environment variable. Most frameworks already do this; the [framework guides](https://ethiodeploy.com/docs/frameworks) cover the exceptions. ### Deploy your first project - **Sign in with GitHub.** Click “Sign in with GitHub” and authorize the app. - **Install the GitHub App.** Install EthioDeploy on the account or org that owns your repo, and choose the specific repos it can access. It only sees the repos you select. - **Create a project.** From the dashboard, click “New Project” and pick the repo. - **Watch it build.** We detect your stack, build it, and run a health check. Build logs are available in the dashboard, so you can see exactly what's happening. - **Open your URL.** Once the health check passes, your project is live at `.ethiodeploy.app`. ### What happens on every push Every push to your project's default branch triggers a new build and deploy automatically. The previous version keeps serving traffic until the new one passes its health check, so a broken build never takes your site down. Details are in [How deploys work](https://ethiodeploy.com/docs/deploys). ### If the first deploy fails Open the deployment's build log in the dashboard. It shows the exact step and error. The usual causes are a missing dependency, a wrong start command, or an env var the build expects that hasn't been set yet. More on these in [Troubleshooting](https://ethiodeploy.com/docs/troubleshooting). ### Next steps - [Add environment variables](https://ethiodeploy.com/docs/env-vars) for API keys and config. - [Add a Postgres, MySQL or Redis addon](https://ethiodeploy.com/docs/addons) if your app needs a database. - [Connect a custom domain](https://ethiodeploy.com/docs/custom-domains) instead of the default subdomain. - [Invite teammates](https://ethiodeploy.com/docs/team-access) with Viewer, Developer, or Admin roles. ## How deploys work URL: https://ethiodeploy.com/docs/deploys Updated: 2026-09-28 How EthioDeploy builds and deploys your app on every push: auto-detected stacks, build logs, and zero-downtime releases. Every push to your project's default branch triggers a new build automatically. We detect your stack and build it. No Dockerfile needed. If your repo already has one, we use that instead. Auto-detected stacks include Next.js, Node.js, Python, and most common frameworks. Once the build passes, the new version goes live and the old one is removed. Build logs and deployment history are available per-project in the dashboard, so you can see exactly what happened on every push. ## Framework guides URL: https://ethiodeploy.com/docs/frameworks Updated: 2026-09-29 Deploy Next.js, Node.js, Python, and SvelteKit apps on EthioDeploy — framework-specific setup notes and links to full guides. Auto-detection covers most stacks out of the box. Pick your framework for a full guide, including the one gotcha worth knowing about before you push. [Next.jsNext.js is auto-detected on Node 22, with standalone output handled for you. Push to GitHub and it builds with no adapter or config step.NEXT_PUBLIC_ values are baked in at build time](https://ethiodeploy.com/deploy/nextjs)[Node.jsExpress, Fastify, Hono, NestJS and plain Node apps all deploy the same way: we run your start script. The only requirements are the port and the bind address.Bind 0.0.0.0 and read PORT](https://ethiodeploy.com/deploy/nodejs)[PythonFastAPI, Flask and Django deploy from requirements.txt or pyproject.toml. Set a start command that binds 0.0.0.0:$PORT, and for Django, add the domain to your settings.Set a start command that binds 0.0.0.0:$PORT](https://ethiodeploy.com/deploy/python)[SvelteKitSvelteKit needs one change before it runs on a container host: swap adapter-auto for adapter-node and add a start script. Then it's a normal git-push deploy.Fix this first: adapter-auto has no runnable server here](https://ethiodeploy.com/deploy/sveltekit)[TanStack StartTanStack Start is detected automatically, for React and Solid. We build it with Nitro's Node preset and run its server, so server functions and API routes work. Push to GitHub and it deploys.Keep the nitro() plugin in vite.config](https://ethiodeploy.com/deploy/tanstack-start)[LLovableSync your Lovable project to GitHub and EthioDeploy builds it on every push. Both older Vite apps and newer TanStack Start apps are detected automatically.Add your Supabase values before the first deploy](https://ethiodeploy.com/deploy/lovable)[BoltConnect your Bolt project to GitHub and EthioDeploy builds it on every push. Bolt apps are JavaScript, so they deploy like any Vite or Node app.Don't edit the same files in Bolt and GitHub](https://ethiodeploy.com/deploy/bolt)[Claude Code & CursorYour coding agent already runs terminal commands. Give it the ethiodeploy CLI and it can create the project, deploy, read the build log when something breaks, and fix it.Push before you deploy](https://ethiodeploy.com/deploy/claude-code-cursor)[DjangoDjango is detected from manage.py and started with migrate plus gunicorn, no Procfile needed. Update settings.py for production, add Postgres, and push.Set ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS before the first deploy](https://ethiodeploy.com/deploy/django)[FastAPIFastAPI deploys from requirements.txt or pyproject.toml. Set a start command with fastapi run, add a health route, and push. Postgres is one click away.Set a start command: the default only runs python main.py](https://ethiodeploy.com/deploy/fastapi)[FlaskFlask needs one thing before it deploys: a gunicorn start command. Add ProxyFix so URLs and cookies know they're on HTTPS, then push.Flask has no default start command](https://ethiodeploy.com/deploy/flask)[LaravelLaravel is detected from artisan and built on the official PHP image with nginx and PHP-FPM. Vite assets are built for you and config, route and view caches are warmed on start.Laravel 11+ defaults to SQLite, which resets on every deploy](https://ethiodeploy.com/deploy/laravel)[RailsRails is detected from the Gemfile and built with nixpacks, not the Dockerfile Rails generates. Set RAILS_ENV and your master key, point the health check at /up, add Postgres, and push.Set RAILS_ENV and RAILS_MASTER_KEY before the first deploy](https://ethiodeploy.com/deploy/rails)[ExpressExpress apps deploy with npm start. Listen on PORT, turn on trust proxy for HTTPS cookies, and keep sessions out of memory so logins survive a deploy.Listen on process.env.PORT](https://ethiodeploy.com/deploy/express)[NestJSNestJS apps build with nest build and need one setting: start the compiled dist/main instead of nest start. Add a Postgres addon, run migrations at start, and push.Set the Start command to node dist/main](https://ethiodeploy.com/deploy/nestjs)[GoGo apps are detected from go.mod and compiled into a single binary. Read PORT, add a health route, and push.Listen on the PORT variable](https://ethiodeploy.com/deploy/go)[Telegram botRun a Telegram bot as a web service that receives webhooks, or as a Background Worker that long-polls. Both work on the Free plan. A worker never sleeps; a webhook bot sleeps and wakes on the next message.A polling bot must be a Background Worker](https://ethiodeploy.com/deploy/telegram-bot)[Firebase StudioFirebase Studio shuts down on March 22, 2027. Push your app to GitHub, add your Gemini key and Firebase config, and it runs as a normal Next.js app.Export before March 22, 2027](https://ethiodeploy.com/deploy/firebase-studio)[ReplitPush your Replit app to GitHub and EthioDeploy builds it on every push. Most apps need a port change and new env vars; apps that use Replit Auth or App Storage need more work.Read PORT instead of a fixed port](https://ethiodeploy.com/deploy/replit)[v0v0 apps are Next.js apps. Sync to GitHub and they build with next build and next start. Vercel services need a key or a replacement.v0 changes reach main through a pull request](https://ethiodeploy.com/deploy/v0)[Google AI StudioSync your AI Studio app to GitHub and EthioDeploy builds it on every push. Set GEMINI_API_KEY in Settings and make sure it never ends up in the browser.Keep your Gemini key out of the browser](https://ethiodeploy.com/deploy/google-ai-studio)[BBase44A Base44 export is a React and Vite frontend that still calls Base44 for data, sign-in and functions. You can host the frontend here today; leaving Base44 completely means replacing its backend.Your app still runs on Base44's backend](https://ethiodeploy.com/deploy/base44) Not listed? If your app builds and starts an HTTP server on `0.0.0.0` and the `PORT` environment variable, it will usually deploy as-is. See [Getting started](https://ethiodeploy.com/docs/getting-started) and [Troubleshooting](https://ethiodeploy.com/docs/troubleshooting). ## Web services & background workers URL: https://ethiodeploy.com/docs/service-types Updated: 2026-09-28 The difference between a Web Service and a Background Worker on EthioDeploy, and how to configure each. Every project is a **Web Service** by default: we expect it to serve HTTP on the port Nixpacks starts it on, we route `.ethiodeploy.app` (and any custom domain) to it, and every deploy waits for a health check to pass before switching traffic over — the old version keeps serving until the new one proves it's ready. Some projects don't serve HTTP at all — a Telegram bot polling `getUpdates`, a queue consumer, any long-running background process. For those, set the project's **Service Type** to **Background Worker** — either when creating the project, or later from Settings → Build. A worker changes three things: - No domain routing and no HTTP health check — there's no port to check. A deploy is considered healthy once the new process stays running past a short startup grace period. - Deploys stop the previous instance before starting the new one, instead of running both briefly side by side. For most apps that overlap is harmless; for something holding a single long-poll connection open (like a Telegram bot), running two instances at once causes the provider to kill one of them with a conflict error. Sequential deploys mean a few seconds of downtime instead. - On the Free plan, a worker never sleeps when idle — there's no traffic to idle on in the first place, and the process needs to stay running to keep polling. It's still bound by the same monthly usage quota as everything else, so a worker that uses meaningfully more memory than a lightweight bot can trip that quota before the month is out. ## Environment variables URL: https://ethiodeploy.com/docs/env-vars Updated: 2026-09-28 Add encrypted environment variables to your EthioDeploy project from the settings panel. Add environment variables from your project's settings panel. They're encrypted at rest and injected into your app at deploy time. Nothing is ever written into your repo or build logs. By default a variable reaches both the build and the running app; mark secrets your app only reads at startup (session keys, passwords, API secrets) as runtime only and they're kept out of the build, so they never end up in the image. Leave it off for anything the build itself needs, like `NEXT_PUBLIC_*` or `VITE_*`. ## Addons URL: https://ethiodeploy.com/docs/addons Updated: 2026-09-28 Add a managed Postgres, MySQL or Redis instance to your EthioDeploy project, with connection strings injected automatically. Add a managed Postgres, MySQL or Redis instance from your project's Addons tab. Both provision on the same box as your app, no separate service to wire up. - **Postgres**: a dedicated database with scheduled, restorable backups (Backups tab). Reachable only from inside EthioDeploy, not from your own computer. - **MySQL**: a dedicated MySQL 8 database, exposed as `DATABASE_URL`. A project can have Postgres or MySQL, not both. - **Redis**: an in-memory store for caching, queues, and sessions. Once an addon finishes provisioning, its connection string is injected into your app as an env var automatically. Nothing to copy or paste. Adding or removing addons requires the Admin role on the project. **What's a connection string?** It's a single line of text, like a web address, that bundles your database or Redis instance's location, username, and password into one string, so a tool can connect without you typing each piece separately. Treat it like a password: anyone who has it can read and write your data. **Do I need to copy it?** Usually no. Your deployed app already has it. You only need to copy it when connecting something outside ethiodeploy, e.g.: - Running the app on your own laptop for local development. - Opening the database in a GUI client like TablePlus, pgAdmin, or RedisInsight. - Connecting from the command line with `psql` (Postgres) or `redis-cli` (Redis). To get it: open the project's Addons tab, click **Reveal** next to the addon to show the value, then click the copy icon to copy it to your clipboard. Only Admins can reveal or copy connection strings. ### Questions **What's a connection string?** It's a single line of text, like a web address, that bundles your database or Redis instance's location, username, and password into one string, so a tool can connect without you typing each piece separately. Treat it like a password: anyone who has it can read and write your data. **Do I need to copy it?** Usually no. Your deployed app already has it. You only need to copy it when connecting something outside ethiodeploy: running the app on your own laptop for local development, opening the database in a GUI client like TablePlus, pgAdmin, or RedisInsight, or connecting from the command line with psql (Postgres) or redis-cli (Redis). ## Database & Redis tools URL: https://ethiodeploy.com/docs/database-tools Updated: 2026-09-28 Built-in schema browser, ERD view, SQL editor, and Redis key browser for every EthioDeploy project addon. Once a Postgres addon is **Ready**, a Database tab appears on the project with a schema browser, an ERD view, a row browser, a SQL editor, and a migrations runner. No separate GUI client needed. Once a Redis addon is **Ready**, a Redis tab appears with connection info, live stats, and a key browser. ## Branch deployments URL: https://ethiodeploy.com/docs/branch-deployments Updated: 2026-09-28 Give any branch its own always-on preview URL on EthioDeploy, separate from production. Give a non-default branch its own always-on preview URL by adding it as a branch deployment from the project dashboard. Each one is its own container, so it gets the same resources as production, but unlike production, it sleeps automatically when idle to save cost. How many branch deployments a project can have at once depends on plan: Free doesn't include any, Pro includes 1, and Business includes 2. ## Custom domains URL: https://ethiodeploy.com/docs/custom-domains Updated: 2026-10-19 Point your own domain at an EthioDeploy project with a CNAME or A record, get a free TLS certificate automatically, and forward your root domain to www. Every project ships with a free `.ethiodeploy.app` subdomain. To use your own domain, add it in the project's settings and point your DNS at us: - **Subdomain** (e.g. `app.yoursite.com`): add a CNAME record pointing to your project's `ethiodeploy.com` subdomain. - **Root domain** (e.g. `yoursite.com`): add an A record to the IP shown in your settings panel (root domains can't use CNAME). Saving triggers a DNS check and issues a TLS certificate once it resolves. DNS propagation can take a few minutes to a few hours. Hit Save again once it has. ### Forward root to www If your site lives at `www.yoursite.com`, EthioDeploy can send visitors who type the bare `yoursite.com` straight to it. This uses an A record rather than a CNAME, so it works alongside the MX and TXT records your email needs. - Set your custom domain to `www.yoursite.com` and wait for it to verify. - Tick **Forward yoursite.com to www.yoursite.com** below the DNS records and save. - Add the A record for `@` shown in your settings panel, replacing any existing A record for the root. If your DNS is on Cloudflare, set it to **DNS only** (grey cloud), not proxied. We check the record automatically every few minutes, so there's no need to save again or redeploy. Once it resolves, the root domain gets its own TLS certificate and every request is permanently redirected (301) to www, keeping the path and query string. This IP is different from the one shown for a root custom domain, and it stays the same even if your project moves between servers, so you only ever set it once. ## Analytics URL: https://ethiodeploy.com/docs/analytics Updated: 2026-09-28 Built-in page views, request volume, bandwidth, and error rate tracking for every EthioDeploy project. Every project ships with a built-in Analytics tab: page views, request volume, bandwidth, and error rate over 24h, 7d, or 30d, measured at the edge with nothing to install. Visitor geography and top pages need a lightweight tracking snippet added to your app; once it's in place, those charts populate automatically. ## Buying a domain URL: https://ethiodeploy.com/docs/domain-purchasing Updated: 2026-09-29 Search, buy, and attach a domain to your project without a separate registrar account. Pay by card or bank transfer, then manage DNS from the dashboard. Buy a domain from the dashboard and attach it to a project in one flow. You don't need a separate registrar account, and you don't need a project to buy one. Head to [Domains](https://ethiodeploy.com/domains) after signing in. Every plan, including Free, can buy domains. ### Searching and pricing Type a name into the search box. We check the common extensions (`.com`, `.io`, `.co`, `.net`, `.org`) and, if your first choice is taken, suggest alternatives. Prices are shown per year. If the renewal price is higher than the first-year price, both are shown so there are no surprises later. `.dev` and `.app` can't be registered through EthioDeploy right now and always show as unavailable. ### Checking out - Click Buy on a result. Optionally pick a project to attach it to. - Enter the registrant contact details (name, email, address, and phone). These are required by the registry. - Pay. You can switch the checkout between USD and ETB. - Check your inbox for an email from the registrar asking you to confirm your contact details. This is an ICANN requirement, and you need to click it once to keep the domain active. ### Payment options - **Card (USD).** You're sent to a hosted checkout. The domain is set up as a yearly subscription that **renews automatically** at the price shown at checkout. - **Bank transfer (ETB).** Transfer the amount, then enter the bank and your reference number. We verify it against the bank, usually within seconds. If it takes longer, the page keeps checking and updates on its own. This is a one-off purchase for one year and **does not renew automatically**. If a bank transfer is for less than the price, it's rejected. A reference number can only be used once. ### Attaching to a project Attach a domain at checkout, or later from **My domains**. You need the Admin role on the project. - **Root domain** (`yoursite.com`): we create the A record for you. - **Subdomain** (`app.yoursite.com`): we create a CNAME to your project's `ethiodeploy.com` address. Different subdomains can point at different projects. No DNS verification is needed for domains you bought here. Traffic starts routing on the project's next deploy; we trigger one automatically. A hostname can only be attached to one project at a time. To move it, reassign it from My domains. ### Managing DNS Open **DNS** next to a domain in My domains to edit records directly. Supported types are `A`, `AAAA`, `CNAME`, `TXT`, and `MX` (with priority). TTL can be 60 to 604800 seconds. Each record shows whether it's live or still propagating. A CNAME can't share a name with other records, so detach an attached subdomain before adding your own records for it. ### Renewal and expiry - **Card purchases** renew every year automatically. - **Bank-transfer purchases** have no in-app renewal yet. The expiry date is shown in My domains, so note it, and [contact us](https://ethiodeploy.com/docs/help) before it passes. If you want automatic renewal, buy with a card. If a payment goes through but the registration fails on our side, it's not retried automatically. Contact us and we'll sort it out. ## Agent mode URL: https://ethiodeploy.com/docs/agent-mode Updated: 2026-09-29 Chat with an AI coding agent that reads, edits, and runs your project's code in a live sandbox. Review the diff, then apply it as a pull request. Agent mode gives each project an AI coding agent with its own live sandbox. Ask it for a change in plain language. It reads your code, edits files, and runs commands like installs, builds, and tests. You review the real diff, and nothing touches your repo until you apply it. ### Open the workspace Open your project's Home tab and click **Open workspace** on the Agent mode card. If you already have a running session you're taken straight back to it. Otherwise click **Start coding**. Viewers can open the page, but starting a session, chatting, and applying need the Developer role or higher. ### The sandbox Each session clones your repo into a private sandbox on its own `agent/` branch. The sandbox comes with Git, Node 22 (npm, pnpm, yarn), Bun, Python 3, Ruby, Go, PHP with Composer, and common build tools. Resources follow the project owner's hosting plan: | Plan | Memory | CPU | | --- | --- | --- | | Free | 768 MB | 0.5 | | Pro | 1 GB | 1 | | Business | 2 GB | 2 | ### What the agent can do The agent can run shell commands, read files, write files, and list directories inside the sandbox. It follows your project's install, build, and start settings. It tries to verify its own work by running your build, tests, or linter. A single message can use up to 30 tool steps. Pick a model from the picker in the workspace: Claude, GPT, Qwen, and many more. The default is Qwen3 Coder 30B, which is also the only model available on the free trial. Larger models tend to handle long, multi-step tasks better. ### Review and apply changes - **Diff.** The Diff tab shows every uncommitted change in the sandbox. - **Preview.** For supported stacks the Preview tab runs your dev server inside the sandbox. You can override the launch command if auto-detection picks the wrong one. - **Apply.** Click Apply and choose _Just open a PR_ or _Merge into_ your default branch. We commit and push to the session's branch and open a pull request. The first Apply also creates a branch preview deployment and comments its URL on the PR. Later Applies redeploy the same preview. Branch previews follow your plan's [branch deployment limit](https://ethiodeploy.com/docs/branch-deployments). On plans with none, Apply still pushes the branch, but no preview deployment is created. If an automatic merge is blocked by branch protection or a conflict, the branch stays pushed and you merge it from the PR. ### Sessions - A session goes idle after 30 minutes without tool activity. Your files and uncommitted edits are kept, and sending a message or clicking Resume brings it back quickly. - Idle sessions are removed after 7 days. Apply anything you want to keep before then. - Delete destroys the sandbox and any uncommitted changes, and can't be undone. - Only one message runs at a time per session. ### Good to know - Agent usage is charged to the project owner's account. See [Agent plans & credit](https://ethiodeploy.com/docs/agent-pricing). - The agent can't see anything outside the repo and its sandbox, other than what it can reach over the internet. - The default model is small. For bigger refactors, switch to a larger model if your plan allows it. ## Agent plans & credit URL: https://ethiodeploy.com/docs/agent-pricing Updated: 2026-09-29 How agent mode is billed: the free trial, Starter, Pro, and Max plans, included credit, the wallet, and what happens when you run out. Agent mode is an add-on, separate from your hosting plan. Every account gets a one-time free trial. After that you subscribe to an agent plan, which includes credit for model usage each month. Full prices are on the [Pricing](https://ethiodeploy.com/pricing) page. ### Free trial Any account, including Free, gets a one-time budget of **100,000 tokens** (input and output combined) the first time it uses the agent. That's enough for a handful of turns or one small feature. The trial is locked to the default model, Qwen3 Coder 30B. When it's used up, subscribe to keep going. ### Plans | Plan | Price / month | Included credit | | --- | --- | --- | | Starter | $5 or 950 ETB | 1,030 ETB | | Pro | $20 or 2,999 ETB | 3,250 ETB | | Max | $50 or 8,999 ETB | 9,750 ETB | Starter suits light use, Pro suits daily use across projects, and Max suits heavy multi-project use. Subscribe from your dashboard Settings. You can pay with a card in USD, or by bank transfer in ETB. ### Credit and wallet Each message is billed by the tokens it uses, at the model's live per-token price. Your included credit is spent first, and then your wallet covers anything extra. The credit resets to the plan amount every 30-day cycle and does not roll over. - **Card subscriptions** renew automatically. - **Bank-transfer plans** are a single 30-day period. Renew before it ends. There's a 2-day grace period after that, and then the plan and its credit are removed. ### When credit runs out When both your included credit and your wallet balance are empty, the next message is blocked with a prompt to top up the wallet. A message already in progress finishes, and your wallet never goes negative. The wallet only works as overflow for subscribers, so a wallet balance alone doesn't unlock the agent without an active plan or trial tokens. Usage is charged to the project owner, so teammates using the agent on a shared project spend the owner's credit. ## Command-line tool URL: https://ethiodeploy.com/docs/cli Updated: 2026-10-21 Install the ethiodeploy CLI to create projects, deploy, tail logs, and manage environment variables from your terminal or CI, with JSON output for scripts and AI agents. The `ethiodeploy` CLI does from your terminal what the dashboard does in the browser: create projects, trigger deploys, follow build and runtime logs, roll back, and manage environment variables. Every command has a `--json` mode, so scripts and AI coding agents can drive it too. ### Install and log in Requires Node.js 18.3 or newer. ``` npm install -g ethiodeploy ethiodeploy login ``` `login` prints a short code and opens your browser. Check the code matches, click Authorize, and the CLI is signed in. No browser on the machine? Create a token under Settings → API tokens and run `ethiodeploy login --token edp_…`. ### Create or link a project Inside a repo that's pushed to GitHub, create a project and watch its first deploy: ``` ethiodeploy new # uses this repo's origin ethiodeploy new acme/shop -b main # or name the repo ``` The EthioDeploy GitHub app must be installed on the repo, and your GitHub account needs write access to it. If the app can't see the repo, the CLI prints the link to install it. Running `new` again for the same repo just shows the existing project. Build settings can be set up front with `--install`, `--build`, `--start` and `--root-dir`, and `--worker` creates a Background Worker. If your build needs env vars (like `VITE_` or `NEXT_PUBLIC_` values), skip the first deploy, set them, then deploy: ``` ethiodeploy new --no-deploy ethiodeploy env push .env ethiodeploy deploy ``` `new` links the directory for you. For a project that already exists, link it with: ``` ethiodeploy init ``` This matches your git `origin` to a project and writes `.ethiodeploy/project.json`. After that every command works from any subfolder. Or pass `-p ` each time. ### Deploy and watch logs ``` ethiodeploy deploy # latest pushed commit, streams the build ethiodeploy deploy -b staging # a branch deployment ethiodeploy logs -f # follow runtime logs ethiodeploy redeploy # re-run the last deployment ethiodeploy redeploy # roll back to an older one ``` `deploy` builds what's on GitHub, not local files, so push first. It exits 0 when the deployment is live, 1 if it failed, and 2 if it was cancelled. ### Environment variables ``` ethiodeploy env set API_URL=https://example.com ethiodeploy env set STRIPE_KEY=sk_live_… --sensitive ethiodeploy env set SESSION_KEY=… --runtime-only ethiodeploy env push .env ethiodeploy env pull .env.local ethiodeploy env rm OLD_KEY ``` Changes apply on the next deploy. Sensitive variables can never be read back, so `env pull` skips them. Runtime-only variables are kept out of the build and only given to the running app, so they never end up in the image. `--no-runtime-only` turns that back off. Changing variables needs the Admin role on the project. ### CI and scripts Create a token in Settings → API tokens and expose it as `ETHIODEPLOY_TOKEN`. A token acts as you, with the same access to every project you can reach, so keep it in your CI provider's secret store and revoke it when it's no longer needed. ``` export ETHIODEPLOY_TOKEN=edp_… ethiodeploy deploy -p my-app --json ``` With `--json`, build logs go to stderr and stdout is a single JSON object. ### Command reference - `login`, `logout`, `whoami` - `new [owner/name]`, `projects`, `init [slug]`, `status`, `open`, `projects delete ` - `deploy`, `deployments`, `redeploy`, `cancel` - `logs` (`-f` to follow, `--build` for build logs, `-b` for a branch) - `env ls|set|rm|pull|push` Run `ethiodeploy --help` for every flag. ## Team access URL: https://ethiodeploy.com/docs/team-access Updated: 2026-09-28 Invite teammates to an EthioDeploy project with Viewer, Developer, or Admin roles. Invite a GitHub user to a specific project from its Settings tab and assign them one of three roles: - **Viewer**: read-only access to the project. - **Developer**: can deploy and manage the project day to day. - **Admin**: full control, including env vars, addons, and team access. The project owner always has full access and doesn't need a role assigned. ## Billing URL: https://ethiodeploy.com/docs/billing Updated: 2026-09-29 Plans, pricing, and how to pay: USD by card from anywhere or ETB by bank transfer, renewals, the usage wallet, and billing help. Pay in USD by card from anywhere in the world, or in Ethiopian Birr by bank transfer. Both work for every paid plan, so you don't need an international card to use EthioDeploy. Full feature lists are on the [Pricing](https://ethiodeploy.com/pricing) page. ### Plans | Plan | Price / month | Projects | Per container | Branch previews | | --- | --- | --- | --- | --- | | Free | $0 | 1 project, sleeps after 30 min idle | 256 MB / 0.5 CPU | None | | Pro | $10 or 1,399 ETB | No fixed project cap, always-on | 1 GB / 1 CPU | 1 per project | | Business | $30 or 4,999 ETB | No fixed project cap, always-on, 5 team seats | 2 GB / 2 CPU | 2 per project | | Enterprise | Custom | Dedicated infrastructure, SSO, SLA | Custom | Custom | Custom domains, always-on hosting, larger Postgres, MySQL and Redis storage, and a priority build queue come with the paid plans. Free projects sleep after 30 minutes without traffic and wake on the next request. See [Branch deployments](https://ethiodeploy.com/docs/branch-deployments) and [Team access](https://ethiodeploy.com/docs/team-access). ### How to pay - **Card (USD).** Choose USD on the pricing page or in Settings → Billing. You're sent to a secure hosted checkout, and the plan renews automatically each month. Use the **Manage** link in Settings → Billing to update your card or cancel. - **Bank transfer (ETB).** Transfer the plan price, then enter the bank and your reference number. We verify it against the bank, usually within seconds. Each payment covers one 30-day period. ### Renewal and changing plans - **Card plans** renew on their own. If you cancel, the plan is removed when the paid period ends. - **Bank-transfer plans** don't renew on their own. Pay again before the date shown in Settings → Billing. There's a 2-day grace period after it passes, then the account moves back to Free. - If a price changes, the new price applies on your next renewal, not mid-period. ### Usage and the wallet Paid plans include a monthly compute quota. If you go past it, the extra usage is taken from your **wallet**, a prepaid balance in ETB that you top up by bank transfer from Settings → Billing. Free accounts have no wallet overage, so a Free project that uses up its quota is stopped until the next month. ### Domains and agent mode These are billed separately from your hosting plan, each with its own card and bank-transfer options. See [Buying a domain](https://ethiodeploy.com/docs/domain-purchasing) and [Agent plans & credit](https://ethiodeploy.com/docs/agent-pricing). ### Billing problems If a payment went through but your plan didn't change, give it a few minutes. Bank verification can take a little longer than usual. If it still hasn't updated, [contact us](https://ethiodeploy.com/docs/help) with your reference number. ## Troubleshooting URL: https://ethiodeploy.com/docs/troubleshooting Updated: 2026-09-28 Fixes for the most common EthioDeploy issues: failed builds, domains not verifying, and stale deploys. **Why did my build fail?** Check the deployment's build log in the dashboard. It shows the exact step and error. Most failures are a missing dependency, a wrong start command, or a build that expects an env var that hasn't been set yet. **Why isn't my custom domain verifying?** Double check the DNS record matches exactly what's shown in settings, then wait for propagation. We automatically re-check unverified domains in the background and issue the certificate as soon as DNS resolves, no need to click Save again. **Why is my site showing an old version after I deployed?** If you have the dashboard open in a tab from before a redeploy, do a hard refresh. A stale tab can reference a build that no longer exists. ### Questions **Why did my build fail?** Check the deployment's build log in the dashboard. It shows the exact step and error. Most failures are a missing dependency, a wrong start command, or a build that expects an env var that hasn't been set yet. **Why isn't my custom domain verifying?** Double check the DNS record matches exactly what's shown in settings, then wait for propagation. We automatically re-check unverified domains in the background and issue the certificate as soon as DNS resolves, no need to click Save again. **Why is my site showing an old version after I deployed?** If you have the dashboard open in a tab from before a redeploy, do a hard refresh. A stale tab can reference a build that no longer exists. ## Still stuck? URL: https://ethiodeploy.com/docs/help Updated: 2026-09-28 Get direct help from the EthioDeploy team for anything the docs don't cover. Reach us at [officialbrooksolo@gmail.com](mailto:officialbrooksolo@gmail.com). # Deploy guides ## Deploy a Next.js app URL: https://ethiodeploy.com/deploy/nextjs Updated: 2026-09-29 Next.js is auto-detected on Node 22, with standalone output handled for you. Push to GitHub and it builds with no adapter or config step. - Detected by: next in package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build - Start: next start (or the standalone server) **NEXT_PUBLIC_ values are baked in at build time.** `NEXT_PUBLIC_*` variables are inlined into the JavaScript bundle when `next build` runs. On EthioDeploy your env vars are available during the build, so add them in Settings _before_ the deploy. If you add or change one afterwards, redeploy, because the running app won't pick it up. ### Prepare your app Make sure `package.json` has the standard scripts: ``` "scripts": { "build": "next build", "start": "next start" } ``` That's it for most apps. We build on Node 22 by default. If your app needs a specific version, set `"engines": { "node": "20.x" }` in `package.json` and that wins. **Standalone output is supported.** If your `next.config` sets `output: "standalone"`, we run the standalone server, copy `public/` and `.next/static` next to it (so CSS, JS and images don't 404), and install `sharp` for image optimization. You don't need to do any of that yourself. ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). - `NEXT_PUBLIC_*` values reach the browser, so never put secrets in them. - Other variables are read on the server. If a page prints a stale value, it was prerendered at build time. Make that route dynamic (for example by reading cookies or headers) so it reads the value per request. - If you use Server Actions across redeploys, set `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` (a base64 key of 16, 24 or 32 bytes) so the key stays the same between builds. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. With Prisma, generate the client during the build and apply migrations with `migrate deploy` (never `migrate dev` in production): ``` "scripts": { "build": "prisma generate && next build" } ``` Keep `prisma` in `dependencies` so it's available at build time. The database isn't reachable while the app builds, so run migrations when it starts: set the Start command in Settings → Build to `npx prisma migrate deploy && npm start`. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **A NEXT_PUBLIC_ value is empty or undefined in the browser.** Cause: The variable wasn't set when the app was built, so nothing was inlined. Fix: Add it in Settings, then trigger a new deploy. - **Build passes but the health check fails.** Cause: The server isn't reachable on the port we expect. Fix: Use the default `next start` or standalone output. If you set a custom start command, keep it listening on `0.0.0.0` and `$PORT`, for example `next start -H 0.0.0.0 -p $PORT`. - **Page shows an old env var value.** Cause: The route was prerendered at build time. Fix: Make the route dynamic so it reads the value on each request. - **Unstyled page or broken images with a custom start command.** Cause: Standalone output needs public/ and .next/static copied beside server.js. Fix: Leave the start command blank so we handle it, or copy both folders yourself. - **Build fails with a Node version error.** Cause: Your app needs a different Node version than 22. Fix: Set `engines.node` in `package.json`. - **Failed to find Server Action after a redeploy.** Cause: The Server Action encryption key changed between builds. Fix: Set `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` as a fixed env var. ### Questions **Can I host a Next.js app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory. Pro ($10/month) keeps it always on with 1 GB. **Do I need output: "standalone" or a Dockerfile?** No. Both work without one. A plain next start app runs as is. If next.config sets output: "standalone", we run the standalone server, copy public/ and .next/static next to it, and install sharp for image optimization. **Can I move a Next.js app from Vercel?** Yes. Connect the same GitHub repo and add your env vars before the first deploy, since NEXT_PUBLIC_ values are baked in at build time. The app runs as one long-running Node server rather than serverless functions, so there are no function timeouts. **Which Node.js version is used?** Node 22 by default. Set engines.node in package.json, for example "20.x", to use a different version. ## Deploy a Node.js app URL: https://ethiodeploy.com/deploy/nodejs Updated: 2026-09-29 Express, Fastify, Hono, NestJS and plain Node apps all deploy the same way: we run your start script. The only requirements are the port and the bind address. - Detected by: package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build (if you have one) - Start: npm start, then main, then index.js **Bind 0.0.0.0 and read PORT.** Listen on `0.0.0.0`, not `localhost`, and take the port from the `PORT` environment variable. Otherwise the platform can't reach your app and the health check fails, even though it runs fine on your laptop. ### Prepare your app Listen on the port we give you, on all interfaces: Express ``` const port = process.env.PORT || 3000; app.listen(port, '0.0.0.0'); ``` Fastify ``` await fastify.listen({ port: Number(process.env.PORT) || 3000, host: '0.0.0.0', }); ``` Hono ``` serve({ fetch: app.fetch, port: Number(process.env.PORT) || 3000, hostname: '0.0.0.0', }); ``` NestJS ``` await app.listen(process.env.PORT ?? 3000, '0.0.0.0'); ``` Then make sure `package.json` has a `start` script. For TypeScript, compile in `build` and run the output: ``` "scripts": { "build": "tsc", "start": "node dist/index.js" } ``` For NestJS use `"build": "nest build"` and `"start": "node dist/main"`. We build on Node 22 by default, and `engines.node` in `package.json` overrides it. Dev dependencies are installed during the build, so build tools like `tsc` or the Nest CLI can live there. ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). Read them with `process.env.NAME`. Never commit a `.env` file. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) (or Redis) and `DATABASE_URL` (or `REDIS_URL`) is injected automatically. Read it with `process.env.DATABASE_URL`. There's no separate pre-deploy step yet, and the database isn't reachable while the app builds, so run migrations before the server starts: set the Start command in Settings → Build to, for example, `npx prisma migrate deploy && npm start`. Make migrations safe to run more than once. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Health check times out.** Cause: The app listens on localhost or a hardcoded port. Fix: Listen on `0.0.0.0` and read `PORT`. - **Cannot find module '…/index.js' on start.** Cause: There's no start script, so we fell back to index.js. Fix: Add `"start"` to `package.json`, or set a Start command in Settings → Build. - **Cannot find module '…/dist/…'.** Cause: TypeScript was never compiled. Fix: Add a `build` script (`tsc` or `nest build`) and start from `dist/`. - **Unsupported engine or syntax errors.** Cause: The app needs a different Node version than 22. Fix: Set `engines.node` in `package.json`. - **App crashes at boot with missing config.** Cause: An env var your code needs isn't set. Fix: Add it in Settings and redeploy. The build log shows the crash. ### Questions **Can I host a Node.js app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory. Pro ($10/month) keeps it always on with 1 GB. **Does it work with TypeScript?** Yes. Compile in your build script (tsc or nest build) and point start at the output in dist/. Dev dependencies are installed during the build, so TypeScript can stay a dev dependency. **Can I run a Telegram bot, queue consumer or other process with no web server?** Yes. Set the project's Service Type to Background Worker when you create it or in Settings → Build. Workers get no HTTP health check and never sleep when idle, even on the Free plan. **Which Node.js version is used?** Node 22 by default. Set engines.node in package.json to use a different version. ## Deploy a Python app URL: https://ethiodeploy.com/deploy/python Updated: 2026-09-29 FastAPI, Flask and Django deploy from requirements.txt or pyproject.toml. Set a start command that binds 0.0.0.0:$PORT, and for Django, add the domain to your settings. - Detected by: requirements.txt, pyproject.toml, Pipfile or main.py - Install: pip install -r requirements.txt (or poetry / uv / pdm) - Build: none by default - Start: Django: migrate + gunicorn. Others: python main.py **Set a start command that binds 0.0.0.0:$PORT.** Only Django gets a start command automatically. For FastAPI and Flask, set the Start command in Settings → Build so the server binds `0.0.0.0` on `$PORT`. A server on `127.0.0.1` or a fixed port fails the health check. ### Prepare your app List your dependencies in `requirements.txt` or `pyproject.toml` (Poetry, uv and PDM work too). We install them into an isolated environment. To pin the Python version, add a `.python-version` file or set the env var `NIXPACKS_PYTHON_VERSION`. FastAPI ``` uvicorn app.main:app --host 0.0.0.0 --port $PORT --proxy-headers ``` Flask ``` gunicorn -b 0.0.0.0:$PORT app:app ``` Django Django is detected via `manage.py` and starts with `python manage.py migrate && gunicorn .wsgi` automatically. Add `gunicorn` to your requirements. Then update `settings.py` for production: ``` import os SECRET_KEY = os.environ["SECRET_KEY"] DEBUG = False ALLOWED_HOSTS = [".ethiodeploy.app", "yourdomain.com"] CSRF_TRUSTED_ORIGINS = ["https://.ethiodeploy.app"] SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") STATIC_ROOT = BASE_DIR / "staticfiles" ``` `CSRF_TRUSTED_ORIGINS` entries must include the `https://` scheme. Nothing runs `collectstatic` for you: set the Build command to `python manage.py collectstatic --noinput` and serve files with WhiteNoise (add it to `MIDDLEWARE` right after `SecurityMiddleware`). ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). Read them with `os.environ["NAME"]`. Set `SECRET_KEY` for Django. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. ``` # Django (pip install dj-database-url) import dj_database_url DATABASES = {"default": dj_database_url.config(conn_max_age=600)} # SQLAlchemy 2.x rejects the postgres:// scheme, so rewrite it url = os.environ["DATABASE_URL"].replace("postgres://", "postgresql+psycopg://", 1) engine = create_engine(url) ``` Framework-specific guides go further: [Django](https://ethiodeploy.com/deploy/django), [FastAPI](https://ethiodeploy.com/deploy/fastapi) and [Flask](https://ethiodeploy.com/deploy/flask). Django runs `migrate` on every start, which is fine for a single instance. For Alembic, run `alembic upgrade head` before your server command, for example `alembic upgrade head && uvicorn …`. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **App exits immediately or the health check fails.** Cause: No start command, or the server binds 127.0.0.1 / a fixed port. Fix: Set the Start command to bind `0.0.0.0:$PORT`. - **Django: DisallowedHost (400).** Cause: Your domain isn't in ALLOWED_HOSTS. Fix: Add both the `ethiodeploy.com` subdomain and any custom domain. - **Django: CSRF verification failed (403).** Cause: CSRF_TRUSTED_ORIGINS is missing the https origin, or the proxy header isn't trusted. Fix: Add the full `https://…` origin and set `SECURE_PROXY_SSL_HEADER`. - **Django admin has no styling.** Cause: collectstatic never ran and nothing serves the files. Fix: Run `collectstatic` in the Build command and add WhiteNoise. - **Build fails installing a package (psycopg, Pillow…).** Cause: The package needs system libraries or a pinned Python version. Fix: Use `psycopg[binary]`, or set `NIXPACKS_APT_PKGS` for the libs you need. - **Wrong Python version.** Cause: The default Python doesn't match your app. Fix: Add `.python-version` or set `NIXPACKS_PYTHON_VERSION`. ### Questions **Can I host a Python app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory. Pro ($10/month) keeps it always on with 1 GB. **Do I need a Dockerfile?** No. Dependencies are installed from requirements.txt, pyproject.toml (Poetry, uv or PDM) or Pipfile. Django starts automatically; for FastAPI and Flask, set a start command that binds 0.0.0.0:$PORT. **Which Python version is used?** Add a .python-version file, or set the NIXPACKS_PYTHON_VERSION env var, to pick the version. A runtime.txt file from Heroku is not read. ## Deploy a SvelteKit app URL: https://ethiodeploy.com/deploy/sveltekit Updated: 2026-09-29 SvelteKit needs one change before it runs on a container host: swap adapter-auto for adapter-node and add a start script. Then it's a normal git-push deploy. - Detected by: package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build - Start: npm start (node build) **Fix this first: adapter-auto has no runnable server here.** A fresh SvelteKit project ships with `adapter-auto`, which picks an output by detecting known hosts (Vercel, Netlify, and so on). On a generic container host it finds none and produces no server; the build log says `Could not detect a supported production environment`. Use `adapter-node` instead. ### Prepare your app - Swap the adapter: ``` npm install -D @sveltejs/adapter-node npm uninstall @sveltejs/adapter-auto ``` - Use it in `svelte.config.js`: ``` import adapter from '@sveltejs/adapter-node'; export default { kit: { adapter: adapter() }, }; ``` - Add a start script to `package.json`: ``` "scripts": { "start": "node build" } ``` adapter-node listens on `0.0.0.0` and reads `PORT` by default, so no code change is needed for the health check. Never use `vite dev` as the production start command, since it only binds `localhost`. ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). - Set `ORIGIN` to your public URL (for example `https://.ethiodeploy.app`, or your custom domain). Without it, form actions are rejected as cross-site POSTs behind the proxy. - `$env/static/*` values and `PUBLIC_` variables are baked in at build time. Set them before deploying. `$env/dynamic/*` is read at runtime. - A `.env` file isn't loaded in production. Use project env vars instead. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. Read it from `$env/dynamic/private` so it's resolved at runtime and not baked into the build. The database isn't reachable while the app builds, so run migrations in front of the Start command in Settings → Build, for example `npx drizzle-kit migrate && node build`. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Could not detect a supported production environment.** Cause: The project still uses adapter-auto. Fix: Switch to `@sveltejs/adapter-node`. - **Cannot find module … index.js on start.** Cause: There's no start script. Fix: Add `"start": "node build"`. - **Cross-site POST form submissions are forbidden (403).** Cause: ORIGIN isn't set, so the request origin doesn't match. Fix: Set `ORIGIN` to your public URL. - **PUBLIC_ or static env values are empty.** Cause: They weren't set when the app was built. Fix: Add them in Settings and redeploy. - **Client IP shows the proxy address.** Cause: adapter-node isn't told which forwarded header to trust. Fix: Set `ADDRESS_HEADER` (for example `X-Forwarded-For`) and `XFF_DEPTH`. ### Questions **Why does SvelteKit need adapter-node?** adapter-auto only produces output for hosts it recognizes, such as Vercel or Netlify. On a container host it finds none and builds no server. adapter-node builds a Node server that listens on 0.0.0.0 and reads PORT, which is what EthioDeploy runs. **Can I host a SvelteKit app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request. Pro ($10/month) keeps it always on. **Why are my form actions rejected with a 403?** SvelteKit checks that a POST comes from the same origin. Behind a proxy it can't tell the public URL on its own, so set the ORIGIN env var to your public URL and redeploy. ## Deploy a TanStack Start app (React or Solid) URL: https://ethiodeploy.com/deploy/tanstack-start Updated: 2026-10-29 TanStack Start is detected automatically, for React and Solid. We build it with Nitro's Node preset and run its server, so server functions and API routes work. Push to GitHub and it deploys. - Detected by: @tanstack/react-start or @tanstack/solid-start in package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build (with NITRO_PRESET=node-server) - Start: npm start, or node .output/server/index.mjs if there's no start script **Keep the nitro() plugin in vite.config.** TanStack Start builds its Node server through Nitro. The official Node setup adds `nitro()` from `nitro/vite` to the Vite plugins. Without it there is no `.output/server/index.mjs` to run, and the deploy fails on start. ### Prepare your app A standard app needs nothing extra. This guide assumes the current TanStack Start (1.x on Vite 8, with Nitro), as in TanStack's own examples. Check two files: - `vite.config.ts` includes Nitro: ``` import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import viteReact from '@vitejs/plugin-react' import { nitro } from 'nitro/vite' export default defineConfig({ plugins: [tanstackStart(), viteReact(), nitro()], }) ``` - `package.json` has the scripts from TanStack's Node guide: ``` "scripts": { "build": "vite build", "start": "node .output/server/index.mjs" } ``` We detect `@tanstack/react-start` and `@tanstack/solid-start`, so Solid apps work the same way. We build with `NITRO_PRESET=node-server` and run `npm start`. If there is no start script, we run `node .output/server/index.mjs` instead. The server reads the `PORT` we set, so no code change is needed. Want something else? Set your own Start command in Settings → Build, or add a `NITRO_PRESET` env var, and we leave that part alone. Never use `vite dev` or `vite preview` as the start command. Current TanStack Start needs Node 22.12 or newer. We build on Node 22.19 by default. If you set `engines.node` in `package.json`, keep it at 22.12 or above. ### Server functions and API routes Your app runs as one Node server, not as static files. Server functions (`createServerFn`) and server routes (a `server.handlers` block on a route) run on that server, so they can read env vars and talk to a database. SPA mode works too: the page renders in the browser, and server functions still reach the same server. ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). - Only `VITE_` variables reach the browser, through `import.meta.env`. They are baked in when the app builds, so add them before deploying and redeploy after a change. Never put secrets in them. - Server functions can read any variable with `process.env`. Read it inside the handler, not at the top of a module, so a secret can't end up in the client bundle. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. Read it with `process.env.DATABASE_URL` in server functions or server routes only. The browser can't see it, and shouldn't. The database isn't reachable while the app builds, so run migrations in front of the Start command in Settings → Build. ### Health check Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. ### Deploy TanStack Start for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. A TanStack Start app runs on the Free plan with no changes. The server, server functions and API routes all fit in one project, and the Postgres addon covers the database. Keep an eye on memory if you render heavy pages: the Free container has 256 MB. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Deploy succeeds but the site returns 503, or only static files load.** Cause: The app was served as a static Vite site instead of running its server. That happens when TanStack Start isn't listed in package.json, or a custom start command points somewhere else. Fix: Keep `@tanstack/react-start` or `@tanstack/solid-start` in `package.json`, or set the Start command to `node .output/server/index.mjs`. - **Cannot find module .output/server/index.mjs.** Cause: The build produced no Nitro server, usually because nitro() is missing from vite.config. Fix: Install `nitro` and add `nitro()` from `nitro/vite` to your Vite plugins. - **Build fails with "Cannot find native binding" or EBADENGINE warnings.** Cause: engines.node pins a Node older than 22.12, which Vite 8 and TanStack Start need. Fix: Raise `engines.node` (for example to `"22"` or `"24"`), or remove it to use our default, and redeploy. - **An env var is undefined in the browser.** Cause: It has no VITE_ prefix, or it wasn't set when the app was built. Fix: Rename it with VITE_ (only if it's safe to expose), or read it in a server function. Then redeploy. - **The app runs but the deploy fails its health check.** Cause: Your health check path returns 404, a 5xx, or needs a login. Fix: Add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. ### Questions **Do I need a Dockerfile or adapter to deploy TanStack Start?** No. We detect @tanstack/react-start or @tanstack/solid-start, build with NITRO_PRESET=node-server and run node .output/server/index.mjs (or your npm start). Your vite.config only needs the nitro() plugin from TanStack's Node setup. **Do server functions work?** Yes. The app runs as a long-running Node server, so server functions and server routes run on it and can read env vars and a database. **Can I host a TanStack Start app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory. Pro ($10/month) keeps it always on with 1 GB. **Does Solid Start work too?** Yes. TanStack Start for Solid (@tanstack/solid-start) is detected and built the same way as the React version. ## Deploy or self-host a Lovable app outside Lovable URL: https://ethiodeploy.com/deploy/lovable Updated: 2026-10-29 Sync your Lovable project to GitHub and EthioDeploy builds it on every push. Both older Vite apps and newer TanStack Start apps are detected automatically. - Detected by: vite or @tanstack/react-start in package.json - Install: npm ci (from your lockfile) - Build: npm run build - Start: Static files with an index.html fallback (Vite apps), or the app's Node server (TanStack Start apps) **Add your Supabase values before the first deploy.** `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY` are baked into the app when it builds. If the first deploy runs without them, the page loads blank. Add them first, or redeploy after adding them. ### Check which kind of Lovable app you have Lovable has generated two kinds of apps. Open `package.json` in your repo: - **No `@tanstack/react-start`:** an older React + Vite app that runs entirely in the browser. We build it with `npm run build` and serve `dist/`, and every route falls back to `index.html`, so deep links and page refreshes work with no extra config. - **Has `@tanstack/react-start`:** a newer app that renders on a server (Lovable apps created since May 13, 2026). We detect it, build it with `NITRO_PRESET=node-server` and run its server, which reads the `PORT` we set. Nothing to configure. From the [CLI](https://ethiodeploy.com/docs/cli), you can create the project, add your env vars, then run the first deploy: ``` ethiodeploy new --no-deploy ethiodeploy env push .env ethiodeploy deploy ``` ### Export your Lovable code to GitHub - Add a GitHub connection once, in Workspace settings → Git → GitHub → Add connection. - In the project, open Project settings → Git → GitHub and click Connect. Lovable creates a new private repository; it can't connect to one you already have. The sync is two-way on one branch, the default branch (usually `main`) unless you pick another: changes you make in Lovable are pushed to GitHub, and pushes to that branch come back into Lovable. EthioDeploy redeploys on every push, so you can keep building in Lovable and the app stays up to date. Git sync is on every Lovable plan. Without GitHub sync, paid Lovable plans can download the code: Project settings → Git → Download codebase gives a one-time `.zip`. Push it to a new GitHub repo yourself. It won't sync, so later Lovable changes need another download. ### Environment variables Your app needs its Supabase values. Lovable keeps them in the `.env` file, which you can see in Lovable's code editor or in the synced repo. Add them in the project's settings on EthioDeploy: - `VITE_SUPABASE_URL`, `VITE_SUPABASE_PUBLISHABLE_KEY` and `VITE_SUPABASE_PROJECT_ID` (all apps). These are baked into the JavaScript at build time, so add them before the deploy and redeploy after any change. - `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` (TanStack Start apps, read by server code), plus `NITRO_PRESET=node-server`. - Any secrets your server functions read. Secrets stored in Lovable are not part of the repo, so copy them over by hand. A missing `VITE_` value is the most common reason a Lovable app works in Lovable and loads a blank page anywhere else. The browser console usually says `supabaseUrl is required`. Adding the variable isn't enough on its own: trigger a new deploy so the build picks it up. More causes in [Vite env variables undefined in production](https://ethiodeploy.com/blog/errors/vite-env-variables-undefined-in-production), and see [Environment variables](https://ethiodeploy.com/docs/env-vars). ### Self-hosting: what stays in Lovable EthioDeploy hosts the app itself. If your project uses Lovable Cloud or your own Supabase project, the database, auth and storage keep running there, and the deployed app talks to them with the values above. - Add your EthioDeploy URL (and your custom domain, if you add one) to your auth provider's allowed redirect URLs, or sign-in will fail. - Edge functions live in `supabase/functions/` and keep running on Supabase. Deploy changes to them with the Supabase CLI. - While the project uses Lovable Cloud, AI model calls and app connectors keep running through Lovable. Connector connections aren't exported, and managed OAuth with automatic token refresh only works on Lovable Cloud. - Scheduled jobs don't come with the code. Recreate them on your backend. - Lovable's Publish button only publishes to Lovable's own hosting. Your EthioDeploy app updates from GitHub pushes instead. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Build fails with "Cannot find native binding" or EBADENGINE warnings.** Cause: engines.node in package.json pins an older Node than your tools need (Vite 8 needs 20.19+ or 22.12+). Fix: Raise `engines.node` (for example to `"22"` or `"24"`), or remove it to use our default, and redeploy. - **Blank page, and the browser console mentions supabaseUrl.** Cause: The VITE_SUPABASE_ values weren't set when the app was built. Fix: Add them in Settings, then trigger a new deploy. - **Sign-in fails or redirects to the wrong site.** Cause: Your new URL isn't in the auth provider's allowed redirect URLs. Fix: Add the ethiodeploy.app URL and any custom domain to the allowed redirect URLs. - **A feature that calls a server function fails.** Cause: It reads a secret that lives in Lovable, not in the repo. Fix: Add the secret as an env var in Settings and redeploy. ### Questions **Can I host a Lovable app outside Lovable?** Yes. Connect your Lovable project to GitHub, then create an EthioDeploy project from that repo. Both older Vite apps and newer TanStack Start apps are detected and built with no config. **Can I export a Lovable project without GitHub?** On paid Lovable plans, yes: Project settings → Git → Download codebase gives a one-time zip of the code. Push it to a GitHub repo to deploy it. It doesn't sync, so for ongoing changes connect GitHub instead, which works on every plan. **Why is my Lovable app a blank page after I deploy it elsewhere?** Almost always a missing VITE_SUPABASE_ value. Vite bakes VITE_ variables into the JavaScript when the app builds, so add VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY and VITE_SUPABASE_PROJECT_ID in the project's settings, then trigger a new deploy. **Can I keep building in Lovable after I move hosting?** Yes. Lovable's GitHub sync is two-way on the default branch, and EthioDeploy redeploys on every push, so changes you make in Lovable go live automatically. **Does my Supabase or Lovable Cloud backend move too?** No. EthioDeploy hosts the app. The database, auth, storage and edge functions keep running on Supabase or Lovable Cloud, and the app reaches them through the VITE_SUPABASE_ env vars. **Can I use my own domain?** Yes, on the Pro plan ($10/month) and up. Add the domain to your auth provider's allowed redirect URLs as well, or sign-in will fail. ## Deploy a Bolt app outside Bolt URL: https://ethiodeploy.com/deploy/bolt Updated: 2026-10-29 Connect your Bolt project to GitHub and EthioDeploy builds it on every push. Bolt apps are JavaScript, so they deploy like any Vite or Node app. - Detected by: package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build - Start: Static files with an index.html fallback (frontend-only apps), or npm start (apps with a Node server) **Don't edit the same files in Bolt and GitHub.** Bolt commits every working change to GitHub and pulls GitHub changes every 30 seconds. If both sides change, Bolt keeps its version and overwrites the one on GitHub. Make changes in one place at a time. ### Get your code onto GitHub In Bolt, click the GitHub icon (top right) and create a repository from your project. You start on the `main` branch, and Bolt commits for you every time a change doesn't break the project. EthioDeploy redeploys on every push. Prefer not to connect? Click the project title, then Download, and push the unzipped folder to a GitHub repo yourself. ### Prepare your app Bolt builds JavaScript apps, with Node.js as the only backend option. How we run it depends on what your project has: - **Frontend only** (a Vite app with no server): we run `npm run build`, serve `dist/`, and fall back to `index.html` so client-side routes work. Nothing to change. - **A Node server** (Express, Hono and similar): add a `start` script and listen on `0.0.0.0` and `PORT`. See the [Node.js guide](https://ethiodeploy.com/deploy/nodejs). - **Next.js**: see the [Next.js guide](https://ethiodeploy.com/deploy/nextjs). ### Environment variables Copy the values from your project's `.env` file into the project's settings on EthioDeploy. Values the browser reads (for Vite, anything starting with `VITE_`) are baked in at build time, so add them before the deploy. See [Environment variables](https://ethiodeploy.com/docs/env-vars). If your app has sign-in, open its authentication settings in Bolt and set the Site URL to your new address, then add every domain users return to after sign-in to the redirect allow list. Bolt updates these automatically only when it publishes to its own hosting. ### Your database Your data doesn't have to move. If the app uses Bolt Database or a Supabase project, that keeps running where it is, and the deployed app reaches it with the same values from `.env`. Bolt's docs don't cover using Bolt Database from another host, so deploy to your `ethiodeploy.app` address first and test sign-in and data loading before you move your domain. To keep the data next to the app instead, add a Postgres or MySQL [addon](https://ethiodeploy.com/docs/addons). `DATABASE_URL` is injected into the app, and the database is private to the project: your server code can reach it, code running in the browser can't. A frontend-only Bolt app needs a Node server in front of it, as in the [Node.js guide](https://ethiodeploy.com/deploy/nodejs). ### Deploy a Bolt app for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. A frontend-only Bolt app is a set of static files, so 256 MB is plenty. It still sleeps like any free app, so the first visit after 30 quiet minutes waits for it to start. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Build fails with "Cannot find native binding" or EBADENGINE warnings.** Cause: engines.node in package.json pins an older Node than your tools need (Vite 8 needs 20.19+ or 22.12+). Fix: Raise `engines.node` (for example to `"22"` or `"24"`), or remove it to use our default, and redeploy. - **Blank page in production but fine in Bolt.** Cause: A VITE_ value from .env wasn't set when the app was built. Fix: Add it in Settings, then trigger a new deploy. - **Health check times out.** Cause: The Node server listens on localhost or a hardcoded port. Fix: Listen on `0.0.0.0` and read `PORT`. - **Sign-in sends users to localhost or the bolt.host site.** Cause: The auth Site URL and redirect list still point at the old address. Fix: Update both in Bolt's authentication settings. - **Your GitHub edits disappeared.** Cause: Bolt changed the same files and overwrote the GitHub version. Fix: Make changes in Bolt or on GitHub, not both at once. ### Questions **Can I host a Bolt.new app somewhere other than Bolt?** Yes. Connect the project to GitHub from Bolt, or download it as a zip and push it yourself, then create an EthioDeploy project from the repo. Frontend-only apps are served as static files and apps with a Node server run their start script. **Can I keep editing in Bolt after I deploy?** Yes. Bolt commits working changes to GitHub and EthioDeploy redeploys on every push. Avoid editing the same files on GitHub at the same time, because Bolt keeps its own version. **Can I host a Bolt app for free?** Yes. The Free plan runs one project at no cost, with no card, on a .ethiodeploy.app address. It sleeps after 30 minutes without traffic and wakes on the next request. A custom domain needs Pro ($10/month). **Why is my deployed Bolt app a blank page?** Usually a VITE_ value from the project's .env file wasn't set when the app was built. Add it in the project's settings and redeploy. ## Host a website made with Claude Code or Cursor URL: https://ethiodeploy.com/deploy/claude-code-cursor Updated: 2026-10-22 Your coding agent already runs terminal commands. Give it the ethiodeploy CLI and it can create the project, deploy, read the build log when something breaks, and fix it. - Detected by: Your app's framework (see the framework guides) - Install: Detected from your lockfile - Build: Detected from your framework - Start: Detected from your framework **Push before you deploy.** `ethiodeploy deploy` builds what's on GitHub, not the files on your machine. Tell your agent to commit and push first, or it will deploy the previous version and wonder why its fix didn't land. ### Set up once Install the [CLI](https://ethiodeploy.com/docs/cli) and log in yourself. The login opens your browser, and every agent session on this machine uses it afterwards: ``` npm install -g ethiodeploy ethiodeploy login ``` Push your repo to GitHub and install the EthioDeploy GitHub app on it. Then, inside the repo, create the project. This links the folder and streams the first deploy: ``` ethiodeploy new ``` If the build needs env vars (like `VITE_` or `NEXT_PUBLIC_` values), create it with `--no-deploy`, run `ethiodeploy env push .env`, then `ethiodeploy deploy`. ### Tell your agent how to deploy Add this to `CLAUDE.md` for Claude Code, or to `AGENTS.md` or a project rule for Cursor: ``` ## Deploying - Deploy with the ethiodeploy CLI. It builds what is pushed to GitHub, so commit and push first. - Run `ethiodeploy deploy --json`. Build logs go to stderr, stdout is one JSON object. Exit code 0 = live, 1 = failed, 2 = cancelled. - If it fails, read `ethiodeploy logs --build -n 200`, fix the cause, push, deploy again. - Runtime errors: `ethiodeploy logs -n 200`. - Env vars: `ethiodeploy env set KEY=VALUE` (add --sensitive for secrets), then deploy again. Never print secret values. - Roll back: `ethiodeploy deployments`, then `ethiodeploy redeploy `. ``` Then ask for what you want, for example “add a contact form and deploy it”. The agent pushes, deploys, and if the build fails it reads the log and tries again. ### Cloud agents and CI An agent running somewhere else (a cloud sandbox, a CI job) can't use your browser login. Create a token in Settings → API tokens and set it as `ETHIODEPLOY_TOKEN` in that environment. A token acts as you, so keep it in a secret store. ``` export ETHIODEPLOY_TOKEN=edp_… ethiodeploy deploy -p my-app --json ``` ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). ### Troubleshooting - **The deploy is live but doesn't include the agent's change.** Cause: The change wasn't pushed, and deploys build what's on GitHub. Fix: Commit and push, then deploy again. - **Not authenticated, inside the agent's environment.** Cause: The agent runs somewhere your login isn't stored. Fix: Set `ETHIODEPLOY_TOKEN` there, or run `ethiodeploy login` on that machine. - **ethiodeploy new says the GitHub app can't see the repo.** Cause: The EthioDeploy GitHub app isn't installed on that repo. Fix: Open the link it prints, install the app on the repo, and run new again. - **Health check times out.** Cause: The server listens on localhost or a hardcoded port. Fix: Listen on `0.0.0.0` and read `PORT`. See the [Node.js guide](https://ethiodeploy.com/deploy/nodejs). ### Questions **Can Claude Code or Cursor deploy my app by itself?** Yes. Install the ethiodeploy CLI and log in once. The agent can then push, run ethiodeploy deploy, read the build log when a deploy fails, fix the cause and deploy again. **How does the agent know whether a deploy worked?** ethiodeploy deploy --json prints one JSON object on stdout and exits with 0 when the app is live, 1 when it failed and 2 when it was cancelled. Build logs go to stderr. **How do I deploy from a cloud agent or CI job?** Create a token in Settings → API tokens and set it as ETHIODEPLOY_TOKEN in that environment. The token acts as you, so keep it in a secret store. ## Deploy a Django app: free Django hosting with Postgres URL: https://ethiodeploy.com/deploy/django Updated: 2026-10-29 Django is detected from manage.py and started with migrate plus gunicorn, no Procfile needed. Update settings.py for production, add Postgres, and push. - Detected by: manage.py with a WSGI_APPLICATION setting - Install: pip install -r requirements.txt (or poetry / uv / pdm) - Build: none by default (add collectstatic) - Start: python manage.py migrate && gunicorn .wsgi **Set ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS before the first deploy.** With `DEBUG = False`, Django answers every request for a host that isn't in `ALLOWED_HOSTS` with a 400, and rejects form posts from origins missing from `CSRF_TRUSTED_ORIGINS` with a 403. Add your `.ethiodeploy.app` address (and any custom domain) to both. CSRF entries need the `https://` scheme. ### Prepare your app Add the production dependencies to `requirements.txt`: ``` django gunicorn whitenoise dj-database-url psycopg[binary] ``` We detect Django from `manage.py` and read `WSGI_APPLICATION` from your settings to build the start command, so keep that setting (every `startproject` has it). Each deploy runs `python manage.py migrate` and then `gunicorn .wsgi`. Gunicorn reads the `PORT` variable we set and binds `0.0.0.0`, so there is nothing to configure for the port. Pin the Python version with a `.python-version` file (for example `3.13`). A Heroku-style `runtime.txt` is not read. ### Production settings Read everything secret or host-specific from the environment: ``` import os import dj_database_url SECRET_KEY = os.environ["SECRET_KEY"] DEBUG = os.environ.get("DEBUG") == "1" ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",") CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS if h] # The platform terminates HTTPS and forwards the original scheme SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True DATABASES = {"default": dj_database_url.config(conn_max_age=600)} ``` Then set `ALLOWED_HOSTS` in the project's env vars to a comma-separated list, for example `myapp.ethiodeploy.app,myapp.com,www.myapp.com`. ### Static files with WhiteNoise Nothing runs `collectstatic` by default. Serve static files from the app itself with WhiteNoise: ``` MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", # ... the rest ] STATIC_ROOT = BASE_DIR / "staticfiles" STORAGES = { "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"}, } ``` Set the Build command in Settings → Build to `python manage.py collectstatic --noinput`. Env vars are available during the build, so settings that read `SECRET_KEY` still load. User uploads (`MEDIA_ROOT`) are different: each deploy starts a fresh container, so files written to disk are lost on the next push. Store uploads in S3-compatible object storage with `django-storages`. ### Health check Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. The check is sent with `Host: .ethiodeploy.app`, so keep that address in `ALLOWED_HOSTS` even after you add a custom domain. Without it Django answers the check with a 400 and the deploy fails. ### Add a database Add a Postgres (or MySQL) [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. `dj_database_url.config()` in the settings above reads it. Migrations run on every start, so a new model goes live with the push that adds it. The database is private to the project: browse it with the built-in [schema browser and SQL editor](https://ethiodeploy.com/docs/database-tools). If you replace the start command (for example to run ASGI), keep the migration step in it: ``` python manage.py migrate && gunicorn myproject.asgi:application -k uvicorn_worker.UvicornWorker ``` ### Celery and background jobs Addons are private to the project that owns them, so a separate worker project can't reach the same database or Redis. For a small app, run the worker in the same container by setting the Start command to: ``` celery -A myproject worker --concurrency 2 & python manage.py migrate && gunicorn myproject.wsgi ``` Add a Redis addon and point Celery at `REDIS_URL`. The worker isn't restarted on its own if it crashes, so watch the Logs tab. On the Free plan the container has 256 MB of memory, which fits a small app with one or two gunicorn workers but not much else. ### Deploy a Django app for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. Gunicorn starts one worker unless `WEB_CONCURRENCY` is set, and one or two workers fit a small Django site in 256 MB. Add the Postgres addon from the database section above so your data survives deploys. Celery workers and beat stop while the app sleeps, so periodic tasks only run while it's awake. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Build fails: Failed to find your WSGI_APPLICATION django setting.** Cause: The settings module has no WSGI_APPLICATION, so we can't build the start command. Fix: Add `WSGI_APPLICATION = "myproject.wsgi.application"`, or set your own Start command. - **Bad Request (400) on every page.** Cause: The domain isn't in ALLOWED_HOSTS. Fix: Add the `ethiodeploy.com` subdomain and every custom domain. - **Deploy fails: GET / returned 400.** Cause: The .ethiodeploy.app address isn't in ALLOWED_HOSTS, so Django rejects the health check. Fix: Add it to `ALLOWED_HOSTS`, alongside any custom domain. - **CSRF verification failed (403) on login or forms.** Cause: CSRF_TRUSTED_ORIGINS is missing the https:// origin, or the proxy header isn't trusted. Fix: Add the full `https://…` origin and set `SECURE_PROXY_SSL_HEADER`. - **Admin has no CSS, or a ValueError about a missing manifest entry.** Cause: collectstatic never ran. Fix: Set the Build command to `python manage.py collectstatic --noinput`. - **Uploaded files disappear after a push.** Cause: Each deploy starts a new container with a fresh filesystem. Fix: Store media in object storage with `django-storages`. ### Questions **Can I host a Django app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory. Pro ($10/month) keeps it always on with 1 GB. **Do I need a Procfile or Dockerfile for Django?** No. Django is detected from manage.py and started with migrate and gunicorn automatically. If you have a Procfile from Heroku, its web line is used as the start command instead. **How do I run migrations on deploy?** The default start command runs python manage.py migrate before gunicorn on every deploy. If you set your own start command, begin it with python manage.py migrate &&. **Which Python version is used?** Add a .python-version file with the version you want, such as 3.12 or 3.13. A runtime.txt file from Heroku is not read. ## Deploy a FastAPI app: free FastAPI hosting with Postgres URL: https://ethiodeploy.com/deploy/fastapi Updated: 2026-10-29 FastAPI deploys from requirements.txt or pyproject.toml. Set a start command with fastapi run, add a health route, and push. Postgres is one click away. - Detected by: requirements.txt, pyproject.toml, Pipfile or main.py - Install: pip install -r requirements.txt (or poetry / uv / pdm) - Build: none by default - Start: python main.py (set your own) **Set a start command: the default only runs python main.py.** Without a start command we run `python main.py`, which serves nothing unless that file starts uvicorn itself. Set the Start command in Settings → Build to `fastapi run main.py --port $PORT`. `fastapi run` listens on `0.0.0.0` by default. ### Prepare your app Install FastAPI with its standard extras, which include the `fastapi` CLI and uvicorn: ``` fastapi[standard] ``` Then set the Start command to one of: ``` # app in main.py fastapi run main.py --port $PORT # app in app/main.py, two worker processes (Pro and above) fastapi run app/main.py --port $PORT --workers 2 # plain uvicorn uvicorn app.main:app --host 0.0.0.0 --port $PORT ``` Pin the Python version with a `.python-version` file. A Procfile `web:` line also works as the start command. ### HTTPS behind the proxy HTTPS ends at the platform's proxy, which forwards requests to your app over HTTP with an `X-Forwarded-Proto` header. Uvicorn only trusts that header from `127.0.0.1` by default, so redirects FastAPI generates (like the trailing-slash redirect from `/items` to `/items/`) point at `http://` and browsers block them as mixed content. Add the env var `FORWARDED_ALLOW_IPS=*`. Uvicorn and `fastapi run` both read it. ### Add a health route Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. ``` @app.get("/healthz", include_in_schema=False) def healthz(): return {"ok": True} ``` ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). Read them with `os.environ` or pydantic-settings' `BaseSettings`. A `.env` file in the repo isn't needed in production. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. It uses the `postgres://` scheme, which SQLAlchemy 2.x rejects, so rewrite it for the driver you use: ``` import os from sqlalchemy.ext.asyncio import create_async_engine url = os.environ["DATABASE_URL"].replace("postgres://", "postgresql+asyncpg://", 1) engine = create_async_engine(url, pool_size=5) ``` Use `postgresql+psycopg://` instead for a sync engine. Run Alembic migrations before the server starts: ``` alembic upgrade head && fastapi run main.py --port $PORT ``` ### Background work FastAPI's `BackgroundTasks` run inside the same process and need nothing extra. For a queue (ARQ, Celery, Dramatiq), add a Redis addon and start the worker in the same container, for example `arq app.worker.WorkerSettings & fastapi run main.py --port $PORT`. Addons are private to their project, so a separate worker project can't reach them. ### Host a FastAPI backend for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. Run a single worker on the Free plan: each extra Uvicorn worker is another copy of your app in memory. `BackgroundTasks` work as usual, but a queue worker or an in-process scheduler stops while the app sleeps. An API called by a frontend is fine; a webhook that must answer on the first try after a quiet spell needs Pro. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Deploy fails the health check, or the app exits right after starting.** Cause: No start command, so python main.py ran and didn't start a server. Fix: Set the Start command to `fastapi run main.py --port $PORT`. - **Deploy fails: GET / returned 404.** Cause: The app has no / route, so the health check never passes. Fix: Add a `/healthz` route and set it as the Health Check Path. - **Redirects go to http:// and the browser blocks them.** Cause: Uvicorn doesn't trust the proxy's X-Forwarded-Proto header. Fix: Set `FORWARDED_ALLOW_IPS=*`. - **NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres.** Cause: SQLAlchemy 2.x doesn't accept the postgres:// scheme. Fix: Replace it with `postgresql+asyncpg://` or `postgresql+psycopg://` before creating the engine. - **Container restarts under load on the Free plan.** Cause: Several workers don't fit in 256 MB of memory. Fix: Run a single worker on Free, or move to Pro for 1 GB. ### Questions **Can I deploy a FastAPI app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database. It sleeps after 30 minutes without traffic and wakes on the next request. Pro ($10/month) is always on with 1 GB of memory. **Should I use fastapi run or uvicorn?** Either works. fastapi run is uvicorn with production defaults and listens on 0.0.0.0. Pass --port $PORT to both. **Can I use WebSockets?** Yes. The app runs as a normal long-lived process, so WebSocket endpoints work like they do locally. **Is the /docs page public?** Yes, unless you turn it off. Pass docs_url=None and redoc_url=None to FastAPI() in production if you don't want the API docs exposed. ## Deploy a Flask app: free Flask hosting with Postgres URL: https://ethiodeploy.com/deploy/flask Updated: 2026-10-29 Flask needs one thing before it deploys: a gunicorn start command. Add ProxyFix so URLs and cookies know they're on HTTPS, then push. - Detected by: requirements.txt, pyproject.toml or Pipfile - Install: pip install -r requirements.txt (or poetry / uv / pdm) - Build: none by default - Start: none (set your own, or a Procfile web line) **Flask has no default start command.** Without one, the build stops with `No start command could be found`. Add `gunicorn` to your requirements and set the Start command to `gunicorn app:app` (module `app.py`, variable `app`). Gunicorn binds `0.0.0.0:$PORT` on its own when `PORT` is set, which we do. ### Prepare your app ``` flask gunicorn ``` Pick the start command that matches your layout: ``` # app.py with app = Flask(__name__) gunicorn app:app # application factory in myapp/__init__.py gunicorn "myapp:create_app()" # two workers (Pro and above) gunicorn -w 2 app:app ``` Coming from Heroku? A Procfile with a `web:` line is used as the start command, so you can leave it in the repo. Pin Python with a `.python-version` file; `runtime.txt` is not read. ### HTTPS and ProxyFix HTTPS ends at the platform's proxy. Tell Flask to trust the forwarded headers so `url_for(..., _external=True)` builds `https://` links and redirects stay on HTTPS: ``` from werkzeug.middleware.proxy_fix import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1) app.config.update( SECRET_KEY=os.environ["SECRET_KEY"], SESSION_COOKIE_SECURE=True, ) ``` Set `SECRET_KEY` in the project's env vars. Without a fixed key, sessions and flashed messages break on every deploy. ### Add a health route Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. ``` @app.get("/healthz") def healthz(): return "ok" ``` ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. Flask-SQLAlchemy uses SQLAlchemy 2.x, which rejects the `postgres://` scheme the URL comes with: ``` app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"].replace( "postgres://", "postgresql+psycopg://", 1 ) ``` Add `psycopg[binary]` to your requirements. With Flask-Migrate, run migrations at start: ``` flask --app app db upgrade && gunicorn app:app ``` ### Deploy a Flask app for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. Gunicorn starts one worker unless `WEB_CONCURRENCY` is set, and one or two workers fit a small Flask app in 256 MB. Add the Postgres addon from the database section above instead of SQLite: files on disk are wiped on every deploy. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Build fails: No start command could be found.** Cause: Flask has no default start command. Fix: Set the Start command to `gunicorn app:app`, or add a Procfile `web:` line. - **Failed to find attribute 'app' in 'app'.** Cause: The gunicorn target doesn't match your module or variable name. Fix: Use `module:variable`, or `"module:create_app()"` for a factory. - **Logins don't stick or flashed messages vanish after a deploy.** Cause: SECRET_KEY changes on each start. Fix: Set a fixed `SECRET_KEY` env var. - **Can't load plugin: sqlalchemy.dialects:postgres.** Cause: SQLAlchemy 2.x doesn't accept the postgres:// scheme. Fix: Rewrite it to `postgresql+psycopg://` as shown above. - **Redirects or external URLs use http://.** Cause: Flask doesn't know the original request was HTTPS. Fix: Wrap the app in `ProxyFix`. ### Questions **Can I host a Flask app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. It sleeps after 30 minutes without traffic, and the container has 256 MB of memory, enough for a small app with one or two gunicorn workers. **Do I still need a Procfile?** No. Set the start command in Settings → Build. If your repo already has a Procfile, its web line is used and you can skip the setting. **Can I run flask run in production?** Don't. flask run starts Werkzeug's development server, which isn't built for production traffic. Use gunicorn. ## Deploy a Laravel app for free with MySQL or Postgres URL: https://ethiodeploy.com/deploy/laravel Updated: 2026-10-29 Laravel is detected from artisan and built on the official PHP image with nginx and PHP-FPM. Vite assets are built for you and config, route and view caches are warmed on start. - Detected by: artisan + laravel/framework in composer.json - Install: composer install --no-dev --optimize-autoloader - Build: npm run build for Vite assets (if package.json has one) - Start: nginx + PHP-FPM, after config:cache, route:cache and view:cache **Laravel 11+ defaults to SQLite, which resets on every deploy.** New Laravel apps set `DB_CONNECTION=sqlite`. The app still starts (we create and migrate the SQLite file on start), but each deploy starts a fresh container, so the file and everything in it is wiped on every push. Add a MySQL or Postgres addon and set `DB_CONNECTION` as described below before you rely on any data. ### Prepare your app No Dockerfile or nginx config is needed. We build every PHP app from the official `php:X.Y-fpm` image: - **PHP version** comes from `require.php` in `composer.json` (or `config.platform.php`). We support 8.2 to 8.5 and prefer 8.4 when your constraint allows it. - **Extensions**: bcmath, intl, opcache, pcntl, pdo_mysql, pdo_pgsql and zip are always installed. Anything your app or its packages declare as `ext-*` in `composer.json` or `composer.lock` is added. - **Frontend assets**: if `package.json` has a `build` script, it runs on Node 22 after `composer install`, and the result lands in `public/build`. - **On start** we run `storage:link`, `config:cache`, `route:cache` and `view:cache` (plus a fresh SQLite file and migrations if the app is still on SQLite), then start PHP-FPM and nginx with `public/` as the web root. - **Logs** go to stderr (`LOG_CHANNEL=stderr`), so they show in the Logs tab. Trust the platform's proxy so Laravel generates `https://` URLs and asset links. In `bootstrap/app.php` (Laravel 11+): ``` ->withMiddleware(function (Middleware $middleware) { $middleware->trustProxies(at: '*'); }) ``` ### Environment variables Add these in the project's settings before the first deploy: ``` APP_KEY=base64:... # php artisan key:generate --show APP_ENV=production APP_DEBUG=false APP_URL=https://myapp.ethiodeploy.app ``` Config is cached at start, after env vars are loaded, so a changed value takes effect on the next deploy. `VITE_` values are baked into the assets at build time. See [Environment variables](https://ethiodeploy.com/docs/env-vars). ### Add MySQL or Postgres Add a MySQL or Postgres [addon](https://ethiodeploy.com/docs/addons). Its connection string is injected as `DATABASE_URL`, but Laravel 11 and later read `DB_URL`. Make the connection you use fall back to it in `config/database.php`: ``` 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DB_URL', env('DATABASE_URL')), // ... ], ``` Then set `DB_CONNECTION=pgsql` (or `mysql`). Laravel 10 and earlier already read `DATABASE_URL`. Run migrations before the server starts by setting the Start command to: ``` php artisan migrate --force && ethiodeploy-start ``` `ethiodeploy-start` is the script that warms the caches and starts nginx and PHP-FPM, so keep it at the end. ### Redis for cache, sessions and queues Laravel 11+ stores cache, sessions and queued jobs in the database by default, which works once migrations have run. To move them to Redis, add a Redis addon (`REDIS_URL` is injected) and set: ``` CACHE_STORE=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis ``` Laravel's default Redis client is the phpredis extension. Add `"ext-redis": "*"` to `require` in `composer.json` so it's installed, or install `predis/predis` and set `REDIS_CLIENT=predis`. ### Queue workers and the scheduler Addons are private to the project that owns them, so a separate worker project can't reach your database or Redis. Run the queue worker and the scheduler in the same container: ``` php artisan migrate --force && (php artisan queue:work --tries=3 &) && (php artisan schedule:work &) && ethiodeploy-start ``` The background processes aren't restarted on their own if they crash, so check the Logs tab after changes. For low volume, `QUEUE_CONNECTION=sync` runs jobs inline and needs no worker at all. ### File uploads Files written to `storage/app` are lost on the next deploy, because each deploy starts a fresh container. Use the `s3` disk with any S3-compatible storage for user uploads. Requests up to 64 MB are accepted. ### Deploy Laravel for free with MySQL The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. Add a MySQL addon, set `DB_CONNECTION=mysql` and add the `DB_URL` fallback from the MySQL section above. Don't stay on SQLite to save a step: the file is wiped on every deploy. `queue:work` and `schedule:work` stop while the app sleeps, so scheduled tasks are skipped until a request wakes it. On a free app, `QUEUE_CONNECTION=sync` runs jobs during the request and needs no worker. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Data disappears after every deploy.** Cause: The app is still on SQLite, the Laravel 11+ default, and the file lives in the container. Fix: Add a MySQL or Postgres addon and set `DB_CONNECTION` and the `DB_URL` fallback. - **No application encryption key has been specified.** Cause: APP_KEY isn't set. Fix: Run `php artisan key:generate --show` locally and add the value as `APP_KEY`. - **Mixed content warnings, or CSS and JS load over http://.** Cause: Laravel doesn't trust the proxy's forwarded headers. Fix: Add `trustProxies(at: '*')` and set `APP_URL` to your https URL. - **Build fails: composer.json requires php ..., which none of the supported versions satisfy.** Cause: Your PHP constraint excludes 8.2 to 8.5. Fix: Widen `require.php`, for example to `^8.2`. - **Class "Redis" not found.** Cause: The phpredis extension isn't installed. Fix: Add `"ext-redis": "*"` to composer.json, or use predis. - **Changed an env var but the app still uses the old value.** Cause: Config is cached when the container starts. Fix: Redeploy after changing env vars. ### Questions **Which PHP version does my Laravel app run on?** The newest of 8.2, 8.3, 8.4 and 8.5 that your composer.json php constraint allows, preferring 8.4. Images come from the official PHP Docker images, which are refreshed soon after each PHP security release. **Do I need a Dockerfile, nginx.conf or Procfile?** No. nginx, PHP-FPM, the web root and the start script are set up for you. A Procfile is ignored for PHP apps; set a custom start command in Settings if you need one. **Can I deploy a Laravel app for free with MySQL?** Yes. The Free plan runs one project with a 512 MB MySQL or Postgres addon at no cost. Set DB_CONNECTION=mysql and point DB_URL at DATABASE_URL. The app sleeps after 30 minutes without traffic, and queue workers and the scheduler stop while it sleeps. **Can I use MySQL instead of Postgres?** Yes. Add a MySQL addon instead (a project has one or the other) and set DB_CONNECTION=mysql. Both inject DATABASE_URL. **How do I run Laravel's scheduler without cron?** Run php artisan schedule:work in the background from your start command. It runs due tasks every minute, the same as a cron entry calling schedule:run. ## Deploy a Rails app for free with Postgres URL: https://ethiodeploy.com/deploy/rails Updated: 2026-10-29 Rails is detected from the Gemfile and built with nixpacks, not the Dockerfile Rails generates. Set RAILS_ENV and your master key, point the health check at /up, add Postgres, and push. - Detected by: Gemfile + config/application.rb - Install: bundle install - Build: bundle exec rake assets:precompile (with propshaft or sprockets) - Start: bundle exec bin/rails server -b 0.0.0.0 -p $PORT -e $RAILS_ENV **Set RAILS_ENV and RAILS_MASTER_KEY before the first deploy.** The default start command passes `-e $RAILS_ENV` to `rails server`, and nothing sets `RAILS_ENV` for you. Add `RAILS_ENV=production` to the project's env vars. Production also needs `RAILS_MASTER_KEY` (the contents of `config/master.key`, which git ignores) or a `SECRET_KEY_BASE`. Without one, Rails raises a missing `secret_key_base` error. ### Prepare your app We build Ruby apps with nixpacks. The `Dockerfile` that `rails new` generates since Rails 7.1 isn't used, and neither is Thruster. You can keep the file for other hosts. The build: - **Ruby version** comes from `.ruby-version` (every `rails new` writes one), the `ruby` line in the `Gemfile`, or `Gemfile.lock`. It's compiled with rbenv during the build, so Ruby builds take longer than Node or Python ones. - **Install** runs `bundle install`. If the repo has a `package.json`, its dependencies are installed too. - **Build** runs `bundle exec rake assets:precompile` when the `Gemfile` has `propshaft` or `sprockets`. API-only apps skip it. - **Start** runs `bin/rails server` on `0.0.0.0` and the `PORT` we set, so there is nothing to configure for the port. If the repo has a `Procfile`, its `web` line is used as the start command instead. Rails only generates `Procfile.dev`, which is ignored. ### Environment variables Add these in the project's settings before the first deploy: ``` RAILS_ENV=production RAILS_MASTER_KEY=... # contents of config/master.key ``` Rails reads `SECRET_KEY_BASE` from the environment first, then from the encrypted credentials. If you don't use credentials, set `SECRET_KEY_BASE` to the output of `bin/rails secret` instead of the master key. The build runs `assets:precompile` in production mode, and env vars are available during the build, so the same values work there. See [Environment variables](https://ethiodeploy.com/docs/env-vars). ### Production settings The platform terminates HTTPS and forwards plain HTTP to your app. Tell Rails so in `config/environments/production.rb`: ``` config.assume_ssl = true config.force_ssl = true ``` Both lines are already there, commented out. Rails turns them on itself only when you generate the app with `--skip-kamal`. Host authorization is off in production until you set `config.hosts`. If you set it, include your `.ethiodeploy.app` address and skip the check for the health route, as Rails' own comments suggest: ``` config.hosts = [ "myapp.ethiodeploy.app", "myapp.com" ] config.host_authorization = { exclude: ->(request) { request.path == "/up" } } ``` ### Health check Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. A new Rails app has no root route, so `/` returns 404 in production. Rails 7.1 and later add `/up`, which returns 200 once the app boots. Set the Health Check Path in Settings to `/up`. The check is sent with `Host: .ethiodeploy.app`, so a `config.hosts` list without that address fails it with a 403, unless `/up` is excluded. ### Add Postgres Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. Rails reads it with no code changes and maps its `postgres://` scheme to the PostgreSQL adapter. Add the driver if the app started on SQLite: ``` bundle add pg ``` `DATABASE_URL` only fills in the `primary` database. Rails 8 also lists `cache`, `queue` and `cable` databases under `production` in `config/database.yml`, and those still point at SQLite files or a local server. Move them into the main database (next section) and keep one entry: ``` production: <<: *default url: <%= ENV["DATABASE_URL"] %> ``` Run migrations before the server starts by setting the Start command in Settings → Build to: ``` bundle exec rails db:prepare && bundle exec bin/rails server -b 0.0.0.0 -p ${PORT:-3000} ``` `db:prepare` loads the schema into an empty database and runs pending migrations otherwise, so a new model goes live with the push that adds it. Browse the data with the built-in [schema browser and SQL editor](https://ethiodeploy.com/docs/database-tools). ### Solid Queue, Solid Cache and Solid Cable Rails 8 stores jobs, cache entries and Action Cable messages in separate databases by default. The Solid gems' READMEs describe how to use your main database instead: - Copy `db/queue_schema.rb`, `db/cache_schema.rb` and `db/cable_schema.rb` into normal migrations, then delete the schema files. - Remove `config.solid_queue.connects_to` from `production.rb`, the `database:` line under `production` in `config/cache.yml`, and `connects_to` from `config/cable.yml`. - Push. `db:prepare` runs the new migrations. Addons are private to the project that owns them, so a separate worker project can't reach the database. Run Solid Queue inside Puma instead: set `SOLID_QUEUE_IN_PUMA=1` and Rails 8's `config/puma.rb` loads the plugin. ### File uploads New apps store Active Storage files on the local disk (`config.active_storage.service = :local`). Each deploy starts a fresh container, so those files are lost on the next push. Use the `amazon` service in `config/storage.yml` with any S3-compatible storage, and add the `aws-sdk-s3` gem. ### Deploy a Rails app for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. Rails' `config/puma.rb` runs one Puma process with 3 threads unless `WEB_CONCURRENCY` or `RAILS_MAX_THREADS` is set. Leave `WEB_CONCURRENCY` unset on the Free plan: each extra worker is another process in the same 256 MB. Add the Postgres addon from the section above so your data survives deploys. The Solid Queue plugin forks its own worker and dispatcher processes by default. To keep them inside the Puma process, replace the plugin line in `config/puma.rb` with: ``` if ENV["SOLID_QUEUE_IN_PUMA"] plugin :solid_queue solid_queue_mode :async end ``` `solid_queue_mode` only exists once the plugin is loaded, so keep it inside the same check. Jobs stop while the app sleeps, so recurring tasks only run while it's awake. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **ArgumentError: Missing `secret_key_base` for 'production' environment.** Cause: Neither RAILS_MASTER_KEY nor SECRET_KEY_BASE is set. Fix: Add `RAILS_MASTER_KEY` from `config/master.key`, or a `SECRET_KEY_BASE` from `bin/rails secret`. - **Deploy fails: GET / returned 404.** Cause: The app has no root route, and / is the default health check path. Fix: Set the Health Check Path in Settings to `/up`. - **Deploy fails with a 403, and the logs say Blocked hosts.** Cause: config.hosts is set but doesn't include the .ethiodeploy.app address the health check sends. Fix: Add that address to `config.hosts`, or exclude `/up` with `config.host_authorization`. - **PG::ConnectionBad on start.** Cause: There is no Postgres addon, or a cache, queue or cable entry in database.yml still points at a local server. Fix: Add the addon and reduce `production` in `database.yml` to one entry with `url: <%= ENV["DATABASE_URL"] %>`. - **Pages load but CSS and JS return 404.** Cause: assets:precompile didn't run: the Gemfile has neither propshaft nor sprockets, or a custom Build command replaced it. Fix: Make the Build command `bundle exec rake assets:precompile`, plus anything else you need. - **Build fails: Please specify ruby's version in .ruby-version file.** Cause: The repo has no .ruby-version and no ruby line in the Gemfile or Gemfile.lock. Fix: Commit a `.ruby-version` file, for example `3.4.1`. ### Questions **Can I deploy a Rails app for free?** Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory, so keep Puma to one process. Pro ($10/month) keeps it always on with 1 GB. **Do I need the Dockerfile that Rails generates?** No. Rails apps are built with nixpacks, which doesn't read the Dockerfile, so Thruster and the Dockerfile's port aren't used. The app runs bin/rails server on the PORT we set. You can keep the file. **How do I run migrations on deploy?** Set the start command to bundle exec rails db:prepare && bundle exec bin/rails server -b 0.0.0.0 -p ${PORT:-3000}. db:prepare loads the schema into an empty database and runs pending migrations on every deploy. **Which Ruby version is used?** The one in .ruby-version, which rails new creates. Without it, the ruby line in the Gemfile or Gemfile.lock is used. ## Deploy an Express app: free Node.js and Express hosting URL: https://ethiodeploy.com/deploy/express Updated: 2026-10-29 Express apps deploy with npm start. Listen on PORT, turn on trust proxy for HTTPS cookies, and keep sessions out of memory so logins survive a deploy. - Detected by: package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build (if you have one) - Start: npm start **Listen on process.env.PORT.** We set `PORT` and send traffic there. A hardcoded `app.listen(3000)` happens to match today, but read the variable so a change never breaks your app: `app.listen(process.env.PORT || 3000)`. Don't pass `"localhost"` as the host; leave it out so Node listens on every interface. ### Prepare your app ``` // package.json "scripts": { "start": "node index.js" } // index.js const express = require("express"); const app = express(); app.set("trust proxy", 1); app.get("/healthz", (req, res) => res.send("ok")); app.listen(process.env.PORT || 3000); ``` We build on Node 22 unless `engines.node` in `package.json` asks for another version. For TypeScript, add a `build` script (`tsc`) and start the compiled output (`node dist/index.js`). The [Node.js guide](https://ethiodeploy.com/deploy/nodejs) covers TypeScript and monorepos in more detail. ### trust proxy and secure cookies HTTPS ends at the platform's proxy, so your app sees plain HTTP. With `app.set("trust proxy", 1)`, Express reads `X-Forwarded-Proto`: `req.secure` is true and cookies marked `secure` are actually sent. Without it, `express-session` silently drops a `secure` session cookie and users can never stay logged in, and `express-rate-limit` logs an `ERR_ERL_UNEXPECTED_X_FORWARDED_FOR` validation error. ### Health check Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. ### Sessions that survive a deploy `express-session`'s default MemoryStore lives in the process, so every deploy logs everyone out. Store sessions in a Redis or Postgres addon: ``` const session = require("express-session"); const { RedisStore } = require("connect-redis"); const { createClient } = require("redis"); const redis = createClient({ url: process.env.REDIS_URL }); redis.connect(); app.use(session({ store: new RedisStore({ client: redis }), secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: true }, })); ``` ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. The database is on a private network, so no SSL options are needed: ``` const { Pool } = require("pg"); const pool = new Pool({ connectionString: process.env.DATABASE_URL }); ``` The database isn't reachable while the app builds, so run migrations (Prisma, Drizzle, Knex) in front of the start command, for example `npx prisma migrate deploy && node index.js`. ### Graceful shutdown On each deploy the new container starts first, and the old one is stopped once the new one is healthy. Close the server on `SIGTERM` so requests in flight can finish: ``` const server = app.listen(process.env.PORT || 3000); process.on("SIGTERM", () => server.close(() => process.exit(0))); ``` ### Deploy an Express app for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. A single Node process fits a small Express API in 256 MB. Sleeping stops the process, so anything held in memory, like the default session store or a cache, is gone when it wakes. Keep sessions in the Redis or Postgres addon, as shown above. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **TypeError: Missing parameter name at index 1: *.** Cause: Express 5 changed route path syntax, so a bare * wildcard is no longer valid. Fix: Name the wildcard: `app.get("/*splat", …)`, or `"/{*splat}"` to also match the root. - **Users are logged out on every request or every deploy.** Cause: trust proxy is off (secure cookie dropped) or sessions live in memory. Fix: Set `trust proxy` and use a Redis or Postgres session store. - **Deploy fails: GET / returned 404 (or 401).** Cause: / doesn't return a 2xx or 3xx status, so the health check never passes. Fix: Add a `/healthz` route and set it as the Health Check Path. - **Health check times out.** Cause: The server listens on localhost or a different port. Fix: Call `app.listen(process.env.PORT)` without a host. - **Cannot find module 'dist/index.js'.** Cause: The TypeScript build didn't run or outputs somewhere else. Fix: Add a `build` script and match the path in `start`. ### Questions **Can I host an Express app for free?** Yes. The Free plan runs one project with a 512 MB Postgres or Redis at no cost. It sleeps after 30 minutes without traffic and wakes on the next request. Pro ($10/month) is always on with 1 GB of memory. **Do WebSockets and Socket.IO work?** Yes. The app runs as one long-lived process behind the proxy, so WebSocket upgrades work without extra setup. **Can I run a Discord or Telegram bot with Express?** If the bot only uses webhooks, run it as a normal web service. For a long-polling bot with no HTTP server, set the service type to Background Worker, which skips the health check and never sleeps. Workers on the Free plan count against its usage quota. ## Deploy a NestJS app for free with Postgres URL: https://ethiodeploy.com/deploy/nestjs Updated: 2026-10-29 NestJS apps build with nest build and need one setting: start the compiled dist/main instead of nest start. Add a Postgres addon, run migrations at start, and push. - Detected by: package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build (nest build) - Start: npm start (nest start, which compiles again) **Set the Start command to node dist/main.** We run `npm start`, and in a new Nest project that is `nest start`, which runs `nest build` before starting the app. It works, but every deploy and every wake from sleep pays for a full TypeScript compile. Set the Start command in Settings → Build to `node dist/main` so the container runs the output the build already made. ### Prepare your app The `main.ts` that `nest new` generates already reads `PORT`, and we set it for you. With no host argument, Node listens on every interface, so you don't need to change the call. A production-ready version: ``` import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.set('trust proxy', 1); app.enableShutdownHooks(); await app.listen(process.env.PORT ?? 3000); } void bootstrap(); ``` On the Fastify adapter, pass the host. Fastify listens only on localhost by default, and the health check can't reach it there: ``` await app.listen(process.env.PORT ?? 3000, '0.0.0.0'); ``` The build runs `npm run build`, which is `nest build` and writes `dist/`. Dev dependencies are installed and kept, so `@nestjs/cli` can stay a dev dependency. Then set the Start command to `node dist/main`. If you keep a `.ts` file in the project root, Nest outputs `dist/src/main.js` instead, so start that path. We build on Node 22 unless `engines.node` in `package.json` asks for another version. `app.enableShutdownHooks()` matters here: Nest's shutdown hooks are off by default, and the old container gets a `SIGTERM` when a new deploy goes live or the app goes to sleep. ### Environment variables and @nestjs/config Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). With `@nestjs/config`, load it once in the root module: ``` import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ConfigModule.forRoot({ isGlobal: true })], }) export class AppModule {} ``` Variables set in Settings take precedence over a `.env` file, so a local `.env` never overrides production values. Keep `.env` out of git anyway, and set `ignoreEnvFile: true` if you want production to read only the real environment. ### Proxy and CORS HTTPS ends at the platform's proxy, so your app sees plain HTTP. `app.set('trust proxy', 1)` in the `main.ts` above makes Express read the forwarded headers, so `secure` cookies are sent and `@nestjs/throttler` sees the real client IP. If a frontend on another domain calls the API, enable CORS for that origin: ``` app.enableCors({ origin: process.env.FRONTEND_URL }); ``` ### Health check Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. The generated `AppController` answers `GET /` with 200, so a new project passes as is. Once you add a global prefix like `app.setGlobalPrefix('api')`, `/` returns 404. Add a health route, keep it out of the prefix, and set `/health` as the Health Check Path: ``` app.setGlobalPrefix('api', { exclude: ['health'] }); ``` A plain `@Get()` that returns `"ok"` is enough. For a check that also pings the database, use `@nestjs/terminus`: ``` @Controller('health') export class HealthController { constructor( private health: HealthCheckService, private db: TypeOrmHealthIndicator, ) {} @Get() @HealthCheck() check() { return this.health.check([ () => this.db.pingCheck('database').withTimeout(1000), ]); } } ``` Terminus answers with an error status when a check fails, so a deploy whose database is unreachable never goes live. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically (a Redis addon injects `REDIS_URL`). The database is private to the project, so it isn't reachable while the app builds, only once the container runs. Run migrations from the Start command, not the Build command. TypeORM ``` TypeOrmModule.forRoot({ type: 'postgres', url: process.env.DATABASE_URL, autoLoadEntities: true, synchronize: false, migrations: ['dist/migrations/*.js'], migrationsRun: true, }) ``` Keep `synchronize` off in production: it changes the schema on every start and can drop data. `migrationsRun: true` applies pending migrations each time the app starts. Prisma Prisma 7 needs a driver adapter. For Postgres, pass `DATABASE_URL` to `@prisma/adapter-pg`: ``` import { PrismaPg } from '@prisma/adapter-pg'; import { PrismaClient } from '../generated/prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { constructor() { super({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) }); } async onModuleInit() { await this.$connect(); } } ``` The generated client isn't committed, so generate it before the build and apply migrations before the server starts. In Settings → Build: ``` Build command: npx prisma generate && npm run build Start command: npx prisma migrate deploy && node dist/main ``` ### Deploy a NestJS app for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. A compiled Nest API runs as one Node process and fits a small app in 256 MB. Start it with `node dist/main`, as above, so a wake from sleep runs your code straight away instead of compiling it first. Sleeping stops the process, so `@nestjs/schedule` cron jobs and in-memory caches stop with it. Keep cached data in the Redis addon and anything durable in Postgres. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Every deploy and wake starts with a TypeScript compile.** Cause: No Start command is set, so npm start runs nest start, which builds before running. Fix: Set the Start command to `node dist/main`. - **Cannot find module '/app/dist/main'.** Cause: A .ts file in the project root moved the entry point to dist/src/main.js. Fix: Start `node dist/src/main`, or move the file out of the root. - **Deploy fails: GET / returned 404.** Cause: A global prefix moved every route, or the root controller was removed. Fix: Add a `/health` route excluded from the prefix and set it as the Health Check Path. - **prisma migrate deploy fails in the build with a connection error.** Cause: The database addon is only reachable from the running container, not from the build. Fix: Move it to the Start command: `npx prisma migrate deploy && node dist/main`. - **Cannot find module '../generated/prisma/client'.** Cause: The Prisma client is generated, not committed, and nothing generated it. Fix: Set the Build command to `npx prisma generate && npm run build`. ### Questions **Can I deploy a NestJS app for free?** Yes. The Free plan runs one project with a 512 MB Postgres or Redis addon at no cost. The app sleeps after 30 minutes without traffic and wakes on the next request, and the container has 256 MB of memory. Pro ($10/month) keeps it always on with 1 GB. **Do I need a Dockerfile for NestJS?** No. We detect package.json, install from your lockfile and run npm run build. Set the Start command to node dist/main in Settings → Build so the app starts from the compiled output. **Does it work with the Fastify adapter?** Yes. Fastify listens only on localhost by default, so call app.listen(process.env.PORT ?? 3000, '0.0.0.0'). **How do I run database migrations on deploy?** Run them when the container starts. With Prisma, set the Start command to npx prisma migrate deploy && node dist/main. With TypeORM, set migrationsRun: true. The database isn't reachable during the build. ## Deploy a Go app: Go web app and API hosting URL: https://ethiodeploy.com/deploy/go Updated: 2026-10-23 Go apps are detected from go.mod and compiled into a single binary. Read PORT, add a health route, and push. - Detected by: go.mod - Install: go mod download - Build: go build -o out (or ./cmd/) - Start: ./out **Listen on the PORT variable.** Read the port from the environment: `http.ListenAndServe(":"+os.Getenv("PORT"), mux)`. Leave the host empty so the server listens on every interface. A server bound to `127.0.0.1` fails the health check. ### Prepare your app We detect Go from `go.mod`, download modules, and build a binary named `out`: - A `main.go` at the repo root is built with `go build -o out`. - With no root `main.go`, the package under `cmd/` is built. If you have several, set the Build command, for example `go build -o out ./cmd/api`. - `CGO_ENABLED=0` is set, so the binary is static. Packages that need cgo (such as `mattn/go-sqlite3`) won't build; use a pure-Go alternative like `modernc.org/sqlite`, or set `CGO_ENABLED=1` as an env var. ``` package main import ( "net/http" "os" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }) http.ListenAndServe(":"+os.Getenv("PORT"), mux) } ``` If go.mod asks for a newer Go than the build image has, Go downloads that toolchain during the build. This needs the full version in go.mod (`go 1.25.0`, not `go 1.25`), which is what `go mod init` and `go get go@latest` write. Files your binary reads at runtime (templates, static assets) are copied with the repo, but `//go:embed` keeps everything in the binary and avoids path surprises. ### Health check Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. ### Add a database Add a Postgres [addon](https://ethiodeploy.com/docs/addons) and `DATABASE_URL` is injected automatically. pgx accepts it as-is: ``` pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL")) ``` Run migrations (goose, golang-migrate, Atlas) when the binary starts, or in front of it in the Start command. ### Graceful shutdown On each deploy, the old container is stopped once the new one is healthy. Catch `SIGTERM` and call `Shutdown` so requests in flight finish: ``` srv := &http.Server{Addr: ":" + os.Getenv("PORT"), Handler: mux} go srv.ListenAndServe() ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) defer stop() <-ctx.Done() srv.Shutdown(context.Background()) ``` ### Workers and bots A Go program with no HTTP server (a queue consumer, a Telegram bot) runs as a Background Worker: set the service type in Settings. It skips the HTTP health check, and the old instance is stopped before the new one starts, so two copies never poll at once. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Build fails: go: download go1.25 for linux/amd64: toolchain not available.** Cause: go.mod asks for a newer Go than the build image has, written without a patch number, so Go can't find a toolchain to download. Fix: Write the full version in go.mod, for example `go 1.25.0`. Go then downloads that toolchain during the build. - **Build fails: no Go files in the repo root.** Cause: main lives in a subfolder that isn't under cmd/. Fix: Set the Build command to `go build -o out ./path/to/main`. - **cgo or sqlite3 errors at build or start.** Cause: CGO_ENABLED=0 is set by default. Fix: Use a pure-Go driver, or set `CGO_ENABLED=1`. - **Health check times out.** Cause: The server binds 127.0.0.1 or a fixed port. Fix: Listen on `":"+os.Getenv("PORT")`. - **Deploy fails: GET / returned 404.** Cause: / doesn't return a 2xx or 3xx status, so the health check never passes. Fix: Add `/healthz` and set it as the Health Check Path. ### Questions **Can I host a Go app for free?** Yes. The Free plan runs one project with a 512 MB Postgres or Redis. It sleeps after 30 minutes without traffic. A Go binary's small memory footprint fits the Free plan's 256 MB container well. **Do I need a Dockerfile?** No. go.mod is detected and the binary is built and started for you. If your repo has a Procfile, its web line is used as the start command. **Does it work with Gin, Echo, Fiber or Chi?** Yes. They all build the same way. Make the router listen on the PORT environment variable. ## Host a Telegram bot for free: webhook or polling URL: https://ethiodeploy.com/deploy/telegram-bot Updated: 2026-10-29 Run a Telegram bot as a web service that receives webhooks, or as a Background Worker that long-polls. Both work on the Free plan. A worker never sleeps; a webhook bot sleeps and wakes on the next message. - Detected by: package.json, requirements.txt or pyproject.toml - Install: npm ci / pnpm / yarn, or pip install -r requirements.txt - Build: npm run build (if you have one) - Start: npm start, or python main.py **A polling bot must be a Background Worker.** A bot that calls `getUpdates` (python-telegram-bot's `run_polling()`, grammY's `bot.start()`) serves no HTTP, so a web service's health check never passes and the deploy fails. Set Service Type to Background Worker in Settings → Build, or switch the bot to webhooks. ### Webhook or long polling Telegram delivers updates in one of two ways, and a bot can only use one at a time: - **Webhook.** Telegram sends each update as an HTTPS POST to your app. Deploy it as a normal web service. On the Free plan it sleeps after 30 minutes without traffic and wakes on the next update, so the first reply after a quiet spell waits for the app to boot. - **Long polling.** Your bot asks Telegram for updates in a loop. Deploy it as a Background Worker. A worker never sleeps on the Free plan, but its memory and CPU count against the monthly usage quota. For a small bot on the Free plan, long polling as a worker is the simpler choice: no public URL, no health route, and no wait after a quiet spell. Use a webhook when the bot is part of a web app, or when you'd rather sleep than spend quota. ### Webhook mode as a web service The app needs two routes: a POST route Telegram sends updates to, and a `GET /` that returns 200 for the health check. Listen on `0.0.0.0` and the `PORT` env var. Check the `X-Telegram-Bot-Api-Secret-Token` header, and answer 200 fast: Telegram re-sends an update when the request doesn't finish in time. Python, following python-telegram-bot's custom webhook example (add `python-telegram-bot`, `starlette` and `uvicorn` to `requirements.txt`): ``` # main.py import asyncio import os import uvicorn from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import PlainTextResponse, Response from starlette.routing import Route from telegram import Update from telegram.ext import Application, CommandHandler SECRET = os.environ["WEBHOOK_SECRET"] async def start(update, context): await update.message.reply_text("Hi!") async def main(): bot = Application.builder().token(os.environ["BOT_TOKEN"]).updater(None).build() bot.add_handler(CommandHandler("start", start)) async def telegram(request: Request) -> Response: if request.headers.get("X-Telegram-Bot-Api-Secret-Token") != SECRET: return Response(status_code=403) await bot.update_queue.put(Update.de_json(data=await request.json(), bot=bot.bot)) return Response() async def health(request: Request) -> PlainTextResponse: return PlainTextResponse("ok") web = Starlette(routes=[ Route("/telegram", telegram, methods=["POST"]), Route("/", health, methods=["GET"]), ]) port = int(os.environ.get("PORT", 3000)) server = uvicorn.Server(uvicorn.Config(web, host="0.0.0.0", port=port)) async with bot: await bot.start() await server.serve() await bot.stop() asyncio.run(main()) ``` The update goes on a queue and the route returns at once, so a slow handler never holds up Telegram. With no start command set, we run `python main.py`, which is what this needs. Node, with grammY and Express (`npm install grammy express`): ``` // index.js const express = require("express"); const { Bot, webhookCallback } = require("grammy"); const bot = new Bot(process.env.BOT_TOKEN); bot.command("start", (ctx) => ctx.reply("Hi!")); const app = express(); app.use(express.json()); app.get("/", (req, res) => res.send("ok")); app.post( "/telegram", webhookCallback(bot, "express", { secretToken: process.env.WEBHOOK_SECRET }) ); app.listen(process.env.PORT || 3000); ``` grammY compares the secret header for you. Don't call `bot.start()` in webhook mode; that starts long polling. Add `"start": "node index.js"` to your `package.json` scripts. ### Point Telegram at your app After the first deploy, register the webhook once. Pick a secret of 1 to 256 letters, digits, `_` or `-`, save it as `WEBHOOK_SECRET` in the project's settings, and run: ``` curl "https://api.telegram.org/bot/setWebhook?url=https://.ethiodeploy.app/telegram&secret_token=" ``` The URL uses port 443, which Telegram accepts. Your app still listens on `PORT`; HTTPS ends at the platform's proxy. To see whether deliveries work, call `getWebhookInfo`: ``` curl "https://api.telegram.org/bot/getWebhookInfo" ``` A growing `pending_update_count` or a `last_error_message` means Telegram can't reach the route or isn't getting a 2xx back. ### What happens when a sleeping bot gets a message On the Free plan, a web service stops after 30 minutes without traffic. When the next update arrives, we start the app and hold Telegram's request for up to 30 seconds while it boots. Once your health check passes, the request goes through to your app unchanged, and the bot replies. The user just waits a few seconds longer. If the app takes longer than 30 seconds to boot, Telegram gets a 503. Telegram repeats a delivery that doesn't get a 2xx and gives up after “a reasonable amount of attempts”. Each retry finds the app booting or awake, so it normally lands. Keep startup light: don't load large files or call slow APIs before the server starts listening. Redeploys don't cause this wait. A web service deploys side by side, and the old version answers until the new one is healthy. ### Long polling as a Background Worker Set Service Type to **Background Worker** when you create the project, or later in Settings → Build. A worker gets no domain and no HTTP health check, so you don't need a health route or `PORT`. The deploy passes once the process is still running after a short startup grace period. ``` # main.py (python-telegram-bot) import os from telegram.ext import Application, CommandHandler async def start(update, context): await update.message.reply_text("Hi!") app = Application.builder().token(os.environ["BOT_TOKEN"]).build() app.add_handler(CommandHandler("start", start)) app.run_polling() ``` ``` // index.js (grammY) const { Bot } = require("grammy"); const bot = new Bot(process.env.BOT_TOKEN); bot.command("start", (ctx) => ctx.reply("Hi!")); bot.start(); ``` Only one process can poll a bot token at a time. A worker deploy stops the old instance before it starts the new one, so two copies never poll at once. That costs a few seconds of downtime per deploy. Telegram keeps updates for up to 24 hours until the bot fetches them, so messages sent in that gap still arrive. Polling doesn't work while a webhook is set. If the bot used webhooks before, call `deleteWebhook` first: ``` curl "https://api.telegram.org/bot/deleteWebhook" ``` ### Health check This applies to webhook mode only. Every deploy requests your health check path (`/` by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If `/` returns 404 or needs a login, add a route like `/healthz` that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving. Telegram only POSTs to `/telegram`, so without a `GET /` route the check gets a 404 or 405. ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). Put the token from BotFather in `BOT_TOKEN` and, for webhook mode, your secret in `WEBHOOK_SECRET`. Keep both out of the repo. ### Host a Telegram bot for free The Free plan costs nothing and needs no card. It includes: - One project, live over HTTPS at `.ethiodeploy.app`. - 256 MB of memory and half a CPU core. - A managed Postgres, MySQL or Redis [addon](https://ethiodeploy.com/docs/addons) with 512 MB of storage. It doesn't expire. - A redeploy on every push, the same as the paid plans. The catch is sleep. After 30 minutes without traffic the app stops, and the next request starts it again. We hold that request for up to 30 seconds while the app boots, so the first visitor after a quiet spell waits a little. Sleep only applies to web services. A Background Worker keeps polling around the clock, but it counts against the Free plan's monthly usage quota: 50 GB-hours of memory and 20 CPU-hours, measured from what the bot actually uses. A bot averaging about 65 MB of memory uses close to 50 GB-hours in a 31-day month. If you go over and stay over for 14 days, the project stops until the next month or until you upgrade. A webhook bot only uses quota while it's awake, but the first reply after a quiet spell waits for the boot. The Free plan has one project, so if you also run a website, add the webhook route to that app instead of deploying the bot separately. When the app has to stay awake or needs your own domain, [Pro](https://ethiodeploy.com/pricing) is $10 or 1,399 ETB a month: always on, 1 GB of memory and a 5 GB database. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **409 Conflict errors in the logs, or the bot stops answering.** Cause: Two processes are polling the same bot token, such as a copy on your laptop or another host, or a polling bot deployed as a web service, where deploys briefly run old and new side by side. Fix: Stop the other copy, and deploy polling bots as a Background Worker so deploys never overlap. - **The webhook is set but the bot never replies.** Cause: Telegram can't reach the route, gets a non-2xx answer, or your app rejects the secret header. Fix: Call `getWebhookInfo` and read `last_error_message`. Check the URL path matches your POST route, and that `WEBHOOK_SECRET` matches the `secret_token` you passed to `setWebhook`. - **Deploy fails: the app never answered on port $PORT.** Cause: The server listens on `127.0.0.1` or a fixed port. python-telegram-bot's `run_webhook()` defaults to `127.0.0.1` and port 80. Port 8443 is for Telegram's side of the webhook, not yours. Fix: Listen on `0.0.0.0` and `PORT`. Telegram reaches you on 443 through the platform's proxy. - **Deploy fails: GET / returned 404 or 405.** Cause: The app only has the POST webhook route, so the health check never passes. Fix: Add a `GET /` route that returns 200, or set another GET route as the Health Check Path. - **A polling bot fails to start after switching from webhooks.** Cause: getUpdates doesn't work while a webhook is set. Fix: Call `deleteWebhook` once, then redeploy. ### Questions **Can I host a Telegram bot for free 24/7?** Yes, within limits. As a Background Worker, a long-polling bot runs without sleeping, but its memory and CPU count against the Free plan's monthly quota of 50 GB-hours and 20 CPU-hours. A small bot fits; one that stays over the quota for 14 days is stopped until the next month. As a webhook web service it answers at any hour but sleeps after 30 minutes without traffic, so the first reply after a quiet spell waits for a boot. Pro ($10/month) keeps a web service always on. **Should my bot use webhooks or long polling?** On the Free plan, long polling as a Background Worker is simpler and never sleeps. Use a webhook if the bot lives inside a web app, or if you'd rather it sleep when idle than use quota. **Do I lose messages while the bot sleeps or redeploys?** Usually not. Telegram keeps updates for up to 24 hours until the bot receives them, and it repeats a webhook delivery that doesn't get a 2xx response. A message can be lost if the bot stays unreachable longer than Telegram keeps retrying. **Which port should my bot listen on?** The PORT env var, on 0.0.0.0. Telegram's port list (443, 80, 88, 8443) is about the public webhook URL, which is https://.ethiodeploy.app on 443. ## Move your Firebase Studio app before the shutdown URL: https://ethiodeploy.com/deploy/firebase-studio Updated: 2026-10-23 Firebase Studio shuts down on March 22, 2027. Push your app to GitHub, add your Gemini key and Firebase config, and it runs as a normal Next.js app. - Detected by: next in package.json - Install: npm ci (from your lockfile) - Build: npm run build - Start: next start **Export before March 22, 2027.** Google stopped new Firebase Studio workspaces on June 22, 2026. On March 22, 2027 the service shuts down, and anything left in a workspace is deleted permanently. Firestore, Authentication and App Hosting keep running; only the Studio editor goes away. ### Get your code out of Firebase Studio The simplest path is pushing the workspace to GitHub: - Switch from the Prototyper view to the Code view. - Open Source Control (Ctrl/Cmd-Shift-G) and commit your changes. - Check `.gitignore` includes `.env`, so your Gemini key isn't uploaded. - Click “Publish Branch”, authorize GitHub, and create a private repository. Alternatives: use “Move now” → “Zip and Download” in the workspace, or run `npx firebase-tools@latest studio:export PATH`, then push the folder to GitHub yourself. To keep your chat history, also zip `/home/user/.idx/ai`. ### What a Studio app looks like The App Prototyping agent builds Next.js apps with Genkit flows that call Gemini. A typical export has: - `dev` on port 9002 and `genkit:dev` scripts for local work, plus the standard `build` and `start`. We use `build` and `start`, so the 9002 port doesn't matter. - `src/ai/genkit.ts` and `src/ai/flows/`: Genkit runs inside Next.js server code, so there's no separate AI server to deploy. - `apphosting.yaml` and `.idx/dev.nix`: Firebase-only config. Leave them in the repo; we ignore them. ### Environment variables In Studio, your keys lived in `.env` and App Hosting. Add them in the project's settings on EthioDeploy: - `GEMINI_API_KEY`: the Genkit Google AI plugin reads this first (it also accepts `GOOGLE_API_KEY` or `GOOGLE_GENAI_API_KEY`). Get a key from Google AI Studio if you don't have one. - Any `NEXT_PUBLIC_FIREBASE_*` values your app reads. These are baked in at build time, so add them before the deploy. Some newer Studio apps hardcode the Firebase config in `src/firebase/config.ts` and first try `initializeApp()` with no arguments, which only works on App Hosting. They fall back to the hardcoded config and log an “Automatic initialization failed” warning. The app works; pass the config object directly to remove the warning. ### Firebase Auth and Firestore Your Firestore data and Firebase Auth users stay in your Firebase project, and the app keeps talking to them from its new address. Two things to update: - In the Firebase console, go to Authentication → Settings → Authorized domains and add `.ethiodeploy.app` and any custom domain. Google sign-in fails on unlisted domains. - If you use App Check with reCAPTCHA, add the new domain to the reCAPTCHA key's allowed domains. ### Fix the build before you rely on it Studio templates ship `next.config.ts` with `typescript.ignoreBuildErrors: true`. The build passes, but type errors are hidden until they break at runtime. Run `npm run typecheck` locally (or ask the [AI coding agent](https://ethiodeploy.com/ai-coding-agent) to fix the errors), then turn the flag off. Placeholder images are loaded from hosts like `placehold.co` and `picsum.photos`, listed in `images.remotePatterns`. Swap them for your own images, and add any new image host there. Everything else in the [Next.js guide](https://ethiodeploy.com/deploy/nextjs) applies. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **AI features fail with an API key error.** Cause: GEMINI_API_KEY isn't set on EthioDeploy. Fix: Add it in Settings and redeploy. - **Google sign-in fails with auth/unauthorized-domain.** Cause: The new domain isn't in Firebase Authentication's authorized domains. Fix: Add the ethiodeploy.com subdomain and any custom domain in the Firebase console. - **Images fail with hostname is not configured under images.** Cause: The image host isn't in next.config's images.remotePatterns. Fix: Add the host to `images.remotePatterns`. - **A page crashes in production but the build passed.** Cause: ignoreBuildErrors hid a type error. Fix: Run `npm run typecheck`, fix the errors, and turn the flag off. - **A NEXT_PUBLIC_FIREBASE_ value is undefined in the browser.** Cause: It wasn't set when the app was built. Fix: Add it in Settings, then trigger a new deploy. ### Questions **When does Firebase Studio shut down?** March 22, 2027. New workspace creation and signups stopped on June 22, 2026. Existing workspaces can still be opened and exported until the shutdown, after which their data is deleted permanently. **Will my Firestore data and users be deleted?** No. Google says Firestore, Authentication, App Hosting and other Firebase services are not affected. Only the Firebase Studio development environment shuts down. **Do I have to move my hosting?** No. Firebase App Hosting is unaffected, and Google suggests Antigravity or Google AI Studio for editing. Move hosting if you want to leave Google Cloud billing, pay in Birr, or use an AI coding agent that deploys to the same place. **Can I keep editing the app with AI after moving?** Yes. EthioDeploy's agent mode reads, edits and runs your project's code in a sandbox, then opens a pull request you can review. Or edit locally with any AI editor and push to GitHub. ## Deploy a Replit app outside Replit URL: https://ethiodeploy.com/deploy/replit Updated: 2026-10-26 Push your Replit app to GitHub and EthioDeploy builds it on every push. Most apps need a port change and new env vars; apps that use Replit Auth or App Storage need more work. - Detected by: package.json (or requirements.txt / pyproject.toml for Python) - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build - Start: npm start (apps with a server), or static files with an index.html fallback (frontend-only Vite apps) **Read PORT instead of a fixed port.** On Replit, the first port your server opens is mapped to the public URL, so any port works. We set `PORT=3000` and send traffic there. If your server listens on a hardcoded port, the health check never gets an answer and the deploy fails. Use `process.env.PORT` and bind to `0.0.0.0`. ### Get your code out of Replit Push it to GitHub from the Git pane: Tools → Git, initialize the repository if needed, connect your GitHub account, set the remote, then commit and push. Private GitHub repositories authenticate with a personal access token, not your password. Or download it: in the file tree, open the three-dot menu and choose “Download as zip”, then push the folder to a new GitHub repository yourself. The zip includes uncommitted changes but not your Secrets, so copy those separately. `.replit`, `replit.nix` and `replit.md` are only read by Replit. Leave them in the repo; we ignore them. If `.replit` has a `[deployment]` build or run command your app needs, copy it into the project's Build settings. ### Prepare your app Check three things in `package.json` and your server code: - A `build` script, if the app has a frontend to compile, and a `start` script that runs the production server, not the dev server. - The server listens on `process.env.PORT` and `0.0.0.0`. - Nothing depends on `REPLIT_DOMAINS`, `REPLIT_DEV_DOMAIN` or `REPL_ID`. Replit sets these; we don't, so they're empty here. Replace them with your own variable, such as `APP_URL`. The rest is the same as any Node app. See the [Node.js guide](https://ethiodeploy.com/deploy/nodejs), or the [Python guide](https://ethiodeploy.com/deploy/python) for Python repls. ### Move your database Replit's database is PostgreSQL, so it moves to an EthioDeploy [Postgres addon](https://ethiodeploy.com/docs/addons) without changing your queries. The addon sets `DATABASE_URL`, the same variable Replit uses. - Copy your data out. Replit's production database accepts any PostgreSQL client, using the connection string from its Settings tab, so `pg_dump` works. The development database can't be reached from outside Replit. - Create your tables on the addon by running your migrations from the app, for example as part of the start command. - Load the data. The addon is private to your project, so you can't restore into it from your own machine. Paste a small SQL dump into the dashboard's [SQL editor](https://ethiodeploy.com/docs/database-tools), or run `pg_restore` against `DATABASE_URL` from code running in your project. The older Replit key-value store (`REPLIT_DB_URL`) only works on Replit. Move that data into Postgres or a [Redis addon](https://ethiodeploy.com/docs/addons). ### Replace Replit Auth, App Storage and Secrets - **Secrets** are plain environment variables. In Replit, use “Edit as .env” to copy them all, then add them in the project's settings. See [Environment variables](https://ethiodeploy.com/docs/env-vars). - **Replit Auth** signs users in with their Replit accounts through Replit's own OAuth apps. Plan to replace it. Replit documents moving to Clerk, and any auth library works on EthioDeploy. - **App Storage** (`@replit/object-storage`) authenticates with the repl's own identity, so expect it to fail elsewhere. Move the files to an S3-compatible bucket such as Cloudflare R2. Replit only offers downloading files one at a time. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Health check times out after a successful build.** Cause: The server listens on a hardcoded port or on localhost. Fix: Listen on `process.env.PORT` and `0.0.0.0`, then push. - **The app starts its dev server in production.** Cause: The `start` script runs the dev command, or there is none and the run command lived in `.replit`. Fix: Point `start` at the production server, or set a Start Command in Settings. - **Links or redirects point at an empty host.** Cause: The code builds URLs from REPLIT_DOMAINS or REPLIT_DEV_DOMAIN. Fix: Read your own variable such as `APP_URL` and set it in Settings. - **Sign-in fails.** Cause: The app still uses Replit Auth. Fix: Switch to an auth provider you control, such as Clerk. - **Database queries fail with relation does not exist.** Cause: The new Postgres addon is empty. Fix: Run your migrations, then load your data. ### Questions **Can I keep building in Replit after moving?** Yes, if you push from Replit's Git pane. Every push to the default branch redeploys on EthioDeploy. Remove any use of Replit Auth or App Storage first, because those stop working outside Replit. **Does my Replit database come with me?** The data does, but not automatically. Dump the production database with pg_dump using the connection string from its Settings tab, then load it into an EthioDeploy Postgres addon. The development database can't be reached from outside Replit. **How does EthioDeploy pricing compare to Replit Deployments?** Replit charges per deployment: Autoscale is $2 a month plus compute and request fees, and Reserved VMs start at $15 a month, on top of a Core or Pro plan. EthioDeploy has a free plan, and Pro is a flat $10 a month with no compute or request fees. ## Deploy a v0 app outside Vercel URL: https://ethiodeploy.com/deploy/v0 Updated: 2026-10-26 v0 apps are Next.js apps. Sync to GitHub and they build with next build and next start. Vercel services need a key or a replacement. - Detected by: next in package.json - Install: npm ci / pnpm / yarn (from your lockfile) - Build: npm run build - Start: next start (or the standalone server) **v0 changes reach main through a pull request.** v0 never writes to your base branch. Each chat works on its own branch, and publishing merges it with a pull request. EthioDeploy deploys the default branch, so a change shows up after that pull request is merged. ### Get your code onto GitHub In v0, open the project menu → Settings → GitHub → Connect, choose where the repository goes, name it and create it. v0 makes a private repository with the current code, and from then on that repository is the source of truth. GitHub sync is on every plan, including Free. Then create a project on EthioDeploy from that repository. ### Prepare your app New v0 chats start as a Next.js app with Tailwind CSS and `shadcn/ui`, so it builds like any Next.js project. We run `next build` and `next start`, and image optimization and caching work with no extra setup. See the [Next.js guide](https://ethiodeploy.com/deploy/nextjs). Open `next.config` before relying on the build. If it sets `typescript.ignoreBuildErrors`, the build passes even with type errors. Fix them and turn the flag off. ### Vercel services in your app Check `package.json` and your env vars for these: - **Analytics and Speed Insights** (`@vercel/analytics`, `@vercel/speed-insights`) load a script that only exists on Vercel. Elsewhere it fails to load and logs a message, and the app keeps working. Remove the components or use another analytics tool. - **AI SDK through AI Gateway**: on Vercel it can authenticate automatically. Elsewhere, create a Gateway API key and set `AI_GATEWAY_API_KEY`. The AI SDK reads it for you. - **Vercel Blob**: set `BLOB_READ_WRITE_TOKEN` from your Blob store. Vercel documents it as the option for code running outside Vercel. - **Neon Postgres**: keep it by copying its `DATABASE_URL` into the project's settings, or switch to a [Postgres addon](https://ethiodeploy.com/docs/addons), which sets `DATABASE_URL` for you. Do one or the other, not both. - **Upstash Redis**: copy its REST URL and token, or switch to a [Redis addon](https://ethiodeploy.com/docs/addons). ### Cron jobs from vercel.json A `crons` entry in `vercel.json` makes Vercel send a GET request to a route on a schedule, with `Authorization: Bearer $CRON_SECRET` when `CRON_SECRET` is set. We don't read `vercel.json`, so use any outside scheduler (a scheduled GitHub Actions workflow works) to send the same request to your app's URL with the same header. ### Environment variables Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See [Environment variables](https://ethiodeploy.com/docs/env-vars). `NEXT_PUBLIC_*` values are baked in when the app builds, so add them before the first deploy. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Your v0 change isn't live.** Cause: The chat's branch hasn't been merged into the default branch yet. Fix: Publish in v0, or merge its pull request on GitHub. - **AI features fail with an authentication error.** Cause: The app relied on Vercel's automatic AI Gateway authentication. Fix: Create an AI Gateway API key and set `AI_GATEWAY_API_KEY`. - **Uploads fail.** Cause: Vercel Blob has no token outside Vercel. Fix: Set `BLOB_READ_WRITE_TOKEN` in Settings. - **Console shows "[Vercel Web Analytics] Failed to load script".** Cause: The analytics script only exists on Vercel. Fix: Remove the Analytics component, or ignore it; nothing else breaks. - **Scheduled tasks stopped running.** Cause: Cron jobs in vercel.json only run on Vercel. Fix: Call the cron route from an outside scheduler with your CRON_SECRET. ### Questions **Do I need to change my v0 code to host it on EthioDeploy?** Usually not. v0 apps are standard Next.js. You add your env vars, and replace or re-key the Vercel services the app uses: Analytics, AI Gateway, Blob and cron jobs. **Can I keep using v0 after moving the hosting?** Yes. v0 keeps editing through GitHub, and EthioDeploy redeploys whenever a change is merged into the default branch. **Does ISR and caching work outside Vercel?** Yes. Next.js caching and revalidation work with next start on a single instance, which is how EthioDeploy runs your app. The cache starts empty after each deploy. ## How to deploy an app created in Google AI Studio URL: https://ethiodeploy.com/deploy/google-ai-studio Updated: 2026-10-26 Sync your AI Studio app to GitHub and EthioDeploy builds it on every push. Set GEMINI_API_KEY in Settings and make sure it never ends up in the browser. - Detected by: package.json - Install: npm ci (from your lockfile) - Build: npm run build - Start: npm start (apps with a Node server), or static files with an index.html fallback (frontend-only apps) **Keep your Gemini key out of the browser.** New AI Studio apps call Gemini from a server, so the key stays secret. Older exports could copy `GEMINI_API_KEY` into the browser JavaScript at build time, and our env vars are available during the build. Before you go live, search the build output for your key. If it shows up, anyone can use it. ### Get your code out of AI Studio In your app, open Settings → GitHub and create a new repository (or link an existing one). The sync goes both ways: AI Studio pushes its changes, and you can pull edits made elsewhere back in. Or export a zip from the Code tab and push the folder to GitHub yourself. Either way, you set `GEMINI_API_KEY` on the new host; it isn't part of the code. ### Prepare your app AI Studio builds full-stack apps: a React frontend by default (Angular and Next.js are also options) and a Node.js server that calls Gemini. How we run it depends on what's in the repo: - **A Node server**: we run `npm run build` and then `npm start`. The server has to listen on `process.env.PORT` and `0.0.0.0`. - **Next.js**: see the [Next.js guide](https://ethiodeploy.com/deploy/nextjs). - **Frontend only** (older apps): we serve the built files with an `index.html` fallback. Check the key warning above before deploying one of these. Older exports load React from a CDN through an import map in `index.html`. If the page loads blank, remove the import map and let the build bundle the packages from `package.json`. ### Your Gemini key Add `GEMINI_API_KEY` in the project's settings. If you also set `GOOGLE_API_KEY`, Google's libraries use that one instead. Everyone who uses your app spends your Gemini quota. Restrict the key in Google Cloud and set a budget alert. Google's own guidance is to call Gemini from a backend, never from browser code. Apps that used Firebase Auth keep using your Firebase project. In the Firebase console, add `.ethiodeploy.app` and any custom domain under Authentication → Settings → Authorized domains. ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Blank page after deploy.** Cause: An import map in index.html loads packages from a CDN that fails, or clashes with the bundled copies. Fix: Remove the import map so the build bundles everything from package.json. - **Your Gemini key appears in the built JavaScript.** Cause: The build config copies GEMINI_API_KEY into browser code. Fix: Move Gemini calls to the server, or update the app's Gemini features in AI Studio, which moves the key server-side. Then rotate the key. - **AI features fail with an API key error.** Cause: GEMINI_API_KEY isn't set on EthioDeploy. Fix: Add it in Settings and redeploy. - **Health check times out.** Cause: The server listens on a fixed port or on localhost. Fix: Listen on `process.env.PORT` and `0.0.0.0`. - **Google sign-in fails with auth/unauthorized-domain.** Cause: The new domain isn't in Firebase Authentication's authorized domains. Fix: Add it in the Firebase console. ### Questions **Why not use AI Studio's Deploy to Cloud Run button?** You can. It needs a Google Cloud project with billing, except on the limited Starter tier, and Cloud Run charges by usage. Host on EthioDeploy if you want a flat monthly price, Postgres and Redis addons, or an AI coding agent that deploys to the same place. **Can I keep editing in AI Studio after moving?** Yes. With GitHub sync on, AI Studio pushes each change to the repository, and EthioDeploy redeploys on every push to the default branch. **Does the Gemini API cost money?** There is a free tier for most Flash models, with lower rate limits, and Google may use free-tier content to improve its products. The paid tier charges per token. Every visitor's requests count against your key. ## Host a Base44 app's code outside Base44 URL: https://ethiodeploy.com/deploy/base44 Updated: 2026-10-26 A Base44 export is a React and Vite frontend that still calls Base44 for data, sign-in and functions. You can host the frontend here today; leaving Base44 completely means replacing its backend. - Detected by: vite in package.json - Install: npm ci (from your lockfile) - Build: npm run build - Start: Static files with an index.html fallback **Your app still runs on Base44's backend.** Base44 says it doesn't support self-hosting, and the exported code uses the Base44 SDK for data, logins, backend functions and integrations. Hosting the code here moves the frontend only. Your Base44 plan still has to stay active. ### Get your code out of Base44 Code export needs the Builder plan or higher. From the dashboard, click the GitHub icon, connect GitHub, authorize Base44 and create a repository. The sync is two-way on `main`, and only the app owner can connect it. Or open the editor's More actions menu and export a zip. The zip has the frontend, backend functions and table structure, but not your data, secrets or users. ### Host the frontend Base44 apps are React apps built with Vite. We run `npm run build`, serve `dist/`, and fall back to `index.html` so every page route works. The app finds its Base44 backend through `VITE_BASE44_*` variables, such as `VITE_BASE44_APP_ID` (the ID is in your editor URL). Copy them from your local `.env` into the project's settings before the first deploy, because Vite bakes them in at build time. Deploy to your `ethiodeploy.com` subdomain first and test sign-in and data loading there before moving a custom domain. ### Leaving Base44 completely To run without Base44, every SDK call needs your own backend. In order of size: - **Data** (`base44.entities`): create Postgres tables with a [Postgres addon](https://ethiodeploy.com/docs/addons) and an API in front of them. Export each table as CSV from Dashboard → Data. - **Sign-in** (`base44.auth`): switch to an auth library or provider you control. Users have to sign up again or be imported. - **Backend functions**: these are Deno handlers in `base44/functions`. Port them to routes in your own server. - **Integrations** (LLM calls, email, file uploads): call the providers directly with your own keys. This is a rewrite of the data layer, not a config change. An AI coding tool can do much of it: EthioDeploy's [agent mode](https://ethiodeploy.com/docs/agent-mode) works on your repo and opens a pull request, or use [Claude Code or Cursor](https://ethiodeploy.com/deploy/claude-code-cursor). ### Deploy - Sign in with GitHub and install the EthioDeploy app on the repo. - Click “New Project” and pick the repo. - Watch the build in the dashboard. When the health check passes, your app is live at `.ethiodeploy.app`. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy. ### Troubleshooting - **Blank page or "app not found" errors.** Cause: A VITE_BASE44_ value wasn't set when the app was built. Fix: Add it in Settings, then trigger a new deploy. - **Data or sign-in calls fail on your EthioDeploy address.** Cause: The SDK is sending requests to your own domain instead of Base44's. Fix: Check where the SDK client is created and set its server URL to your Base44 backend, then test again on the subdomain. - **GitHub sync doesn't push.** Cause: The branch isn't main, or you aren't the app owner. Fix: Base44 only syncs main, and only the owner can connect GitHub. ### Questions **Can I self-host a Base44 app?** Not as exported. Base44's docs say it doesn't support self-hosting and the exported code still relies on Base44 for data, logins, functions and integrations. You can host the frontend anywhere, but running fully elsewhere means replacing those parts with your own backend. **Which Base44 plan do I need to export code?** Builder or higher, for both zip export and GitHub sync. Data export as CSV is available from the Data tab. **Why host the frontend on EthioDeploy if Base44 hosts it already?** Mostly as a first step out: once the frontend runs here, you can replace the Base44 backend one piece at a time with Postgres, your own auth and your own server routes, deployed from the same repository. # Comparisons ## EthioDeploy vs Heroku URL: https://ethiodeploy.com/compare/heroku Last checked: 2026-10-21 Heroku runs apps on dynos and sells Postgres and Key-Value Store as separate add-ons. EthioDeploy deploys the same kind of apps from GitHub into containers, with a free plan, a database included in every plan and an AI coding agent built in. Here is where each one fits. Choose Heroku if: - You need enterprise network isolation through Heroku Private Spaces. - Your team already runs on Heroku pipelines and its add-ons, and moving would cost more than it saves. - You're adding AI features to your app and want Heroku's Managed Inference API next to your dynos. Choose EthioDeploy if: - You want a free plan to start. Heroku's cheapest dyno is Eco at $5 a month. - You want more memory for the money: 1 GB per container on Pro at $10/month, where Heroku's 1 GB dyno starts at $50/month. - You want Postgres or Redis included in your plan instead of billed as a separate add-on. - You want an AI coding agent that edits and runs your repo in a sandbox, then opens a pull request. - You want to buy your domain in the same dashboard, or pay in Birr by bank transfer. | Feature | EthioDeploy | Heroku | | --- | --- | --- | | Free plan | 1 project, sleeps after 30 minutes idle | No free dyno tier on the pricing page. Eco is $5/month and also sleeps after 30 minutes without traffic | | Entry paid price | Pro $10/month flat, always-on, no fixed project cap | Basic $7/month per always-on dyno; Standard-1X $25/month per dyno | | Memory | 1 GB per container on Pro ($10), 2 GB on Business ($30) | 0.5 GB on Eco, Basic and Standard-1X; 1 GB from Standard-2X at $50/month | | Managed database | Postgres or Redis addon included in the plan: 512 MB on Free, 5 GB on Pro, 20 GB on Business | Paid add-ons: Postgres Essential-0 $5/month for 1 GB; Key-Value Store mini has 25 MB and no persistence | | Background workers | Background Worker service type | Worker dynos, scaled separately from web dynos | | Built-in AI | Coding agent in a live sandbox: diff, apply, PR and branch preview. Plans from $5/month | Managed Inference and Agents API for using AI models in your app, plus an MCP server in early development | | Domains | Buy domains in the dashboard | Bring your own: no domain registration or DNS service. Custom domains are free, up to 1,000 per app | ### Questions **Does Heroku have a free plan?** Heroku's pricing page doesn't list a free dyno tier. The cheapest option is Eco at $5 a month for a pool of 1,000 dyno hours shared across your account, and Eco dynos sleep after 30 minutes without traffic. EthioDeploy's Free plan runs one project at no cost and sleeps after 30 minutes idle. **Is EthioDeploy a Heroku alternative?** For web apps, APIs and background workers deployed from GitHub with a managed Postgres or Redis, yes. If you depend on Private Spaces or a large set of Heroku add-ons, Heroku remains the better fit. **How do I move an app from Heroku to EthioDeploy?** Connect the same GitHub repository and EthioDeploy detects your stack and builds it. Copy your config vars into environment variables and add a Postgres or Redis addon. The addon is private to your project, so you can't restore into it from your own machine: paste a small SQL dump into the dashboard's SQL editor, or run pg_restore against DATABASE_URL from code running in your project. Then point your domain at EthioDeploy. Sources: - [Heroku pricing](https://www.heroku.com/pricing) - [Heroku Eco dyno hours](https://devcenter.heroku.com/articles/eco-dyno-hours) - [Heroku Postgres plans](https://devcenter.heroku.com/articles/heroku-postgres-plans) - [Heroku Key-Value Store](https://devcenter.heroku.com/articles/heroku-redis) - [Heroku background jobs](https://devcenter.heroku.com/articles/background-jobs-queueing) - [Heroku custom domains](https://devcenter.heroku.com/articles/custom-domains) - [Heroku AI](https://www.heroku.com/ai/) - [Heroku MCP server](https://devcenter.heroku.com/articles/heroku-mcp-server) ## EthioDeploy vs Vercel URL: https://ethiodeploy.com/compare/vercel Last checked: 2026-09-29 Vercel is the home of Next.js and a great fit for frontend and serverless apps. EthioDeploy is a container-based host with an AI coding agent built in and local payment options. Here is where each one fits. Choose Vercel if: - Your app is mostly frontend or serverless, especially Next.js, and you want Vercel's global CDN. - You want the team features around a Pro seat, such as free Viewer seats. - You're happy paying by card and your project is fine with function time limits. Choose EthioDeploy if: - You want a full container with a real always-on process, background workers, and native Postgres or Redis. - You want flat, predictable plan pricing instead of per-seat plus usage. - You can't easily pay by international card and want to pay in Birr by bank transfer. - You want an AI coding agent that edits and runs your repo in a sandbox, built into the deploy flow. | Feature | EthioDeploy | Vercel | | --- | --- | --- | | Best for | Full-stack apps and workers in containers | Frontend and serverless apps, especially Next.js | | Entry paid price | Pro $10/month flat | Pro $20/month per developer seat, plus usage | | Free plan | 1 project, sleeps after 30 minutes idle | Hobby: no sleep, but non-commercial personal use only | | Managed database | Postgres and Redis addons, connection string injected automatically | No native Postgres or Redis. Available through Marketplace partners such as Neon, Upstash and Supabase | | Long-running processes | Background Worker service type for bots and queue consumers | Serverless functions with a duration cap (300 s on Hobby, configurable up to 800 s on Pro) | | Payment methods | Card in USD, or bank transfer in Birr | Credit or debit card only; monthly billing on Pro | | Built-in AI | Coding agent in a live sandbox: diff, apply, PR and branch preview. Plans from $5/month | Vercel Agent ($0.25 per million tokens plus token cost on Pro), v0 as a separate paid product, and an MCP server in public beta | | Domains | Buy domains in the dashboard | Sells domains (registrar API and MCP tool are documented) | ### Questions **Is EthioDeploy a Vercel alternative?** For full-stack apps that need containers, background workers or a managed database, yes. For frontend-heavy or serverless apps that benefit from a global edge network, Vercel is a strong choice and we don't claim to replace that. **Can I host Next.js on EthioDeploy?** Yes. Next.js is auto-detected and built in a container, including standalone output. See the Next.js deploy guide. **Can I pay for EthioDeploy without an international card?** Yes. You can pay in Ethiopian Birr by bank transfer, or by card in USD. Vercel's billing documentation lists credit or debit cards only. Sources: - [Vercel pricing](https://vercel.com/pricing) - [Vercel Hobby plan](https://vercel.com/docs/plans/hobby) - [Vercel Pro plan billing](https://vercel.com/docs/plans/pro-plan/billing) - [Vercel function runtimes and limits](https://vercel.com/docs/functions/runtimes) - [Vercel storage](https://vercel.com/docs/storage) - [Vercel MCP server](https://vercel.com/docs/agent-resources/vercel-mcp) ## EthioDeploy vs Render URL: https://ethiodeploy.com/compare/render Last checked: 2026-09-29 Render and EthioDeploy are both container hosts with managed databases and background workers. The differences are pricing shape, payment options and the AI agent built into EthioDeploy. Choose Render if: - You want a wider set of service types, including cron jobs, private services and workflows. - You want a larger team on one flat workspace price. - You're comfortable paying by card. Choose EthioDeploy if: - You want to pay in Birr by bank transfer, or by card, on the same plans. - You want an AI coding agent that works on your repo in a sandbox and opens a pull request. - You want a small, simple set of plans without workspace tiers. | Feature | EthioDeploy | Render | | --- | --- | --- | | Pricing shape | Per-account plans: Free, Pro $10, Business $30 a month | Flat workspace plans: Hobby free, Pro $25/month with unlimited members, plus per-second compute | | Free plan behavior | Sleeps after 30 minutes idle | Free instances spin down after 15 minutes idle and take about a minute to restart | | Free database | Free-plan Postgres or Redis addon with 512 MB storage | Free Postgres is 1 GB, expires after 30 days with a 14-day grace period, and has no backups | | Background workers | Background Worker service type | Background workers, cron jobs, private services and workflows | | Payment methods | Card in USD, or bank transfer in Birr | Major credit and debit cards | | Built-in AI | Coding agent in a live sandbox with diff, apply, PR and branch preview | An MCP server that lets Claude, Cursor and Codex manage services and read logs | | Domains | Buy domains in the dashboard | Bring your own domain (2 on Hobby, 15 on Pro) | ### Questions **Is EthioDeploy an alternative to Render?** If you want a simple container host with managed Postgres or Redis, workers, local Birr payments and a built-in AI coding agent, yes. If you need Render's wider service types, Render is the better fit. **Do both platforms sleep free apps?** Yes. Free apps sleep when idle on both. Render's free instances spin down after 15 minutes of inactivity, and EthioDeploy's Free plan sleeps after 30 minutes. **Can I run a Telegram bot or queue worker?** Yes. Set the service type to Background Worker on EthioDeploy, which skips the HTTP health check and domain routing. Sources: - [Render workspace plan changes](https://render.com/changelog/updated-plans-for-render-workspaces) - [Render new workspace plans](https://render.com/docs/new-workspace-plans) - [Render free tier](https://render.com/docs/free) - [Render service types](https://render.com/docs/service-types) - [Render custom domains](https://render.com/docs/custom-domains) - [Render MCP server](https://render.com/docs/mcp-server) ## EthioDeploy vs Railway URL: https://ethiodeploy.com/compare/railway Last checked: 2026-09-29 Railway bills by the resources you use. EthioDeploy sells plans with a usage quota. Both deploy containers from GitHub. Here is how they differ in pricing, payments and AI. Choose Railway if: - You prefer pure usage-based billing, with stopped services costing nothing. - You want to run many services and databases as templates on one canvas. Choose EthioDeploy if: - You want predictable monthly plans with a managed Postgres or Redis addon that injects its connection string for you. - You want to pay in Birr by bank transfer as well as by card. - You want an AI coding agent that edits your repo in a sandbox and opens a pull request. | Feature | EthioDeploy | Railway | | --- | --- | --- | | Pricing shape | Plans: Free, Pro $10, Business $30 a month, with a usage quota and wallet for overage | Subscription with usage credit (Hobby $5, Pro $20) plus usage: about $10/GB RAM and $20/vCPU per month | | Free plan | 1 project, sleeps after 30 minutes idle | Free plan with $1 of monthly credit, limited to 0.5 GB RAM and 1 vCPU, no custom domains | | Databases | Postgres and Redis addons, connection string injected automatically | Databases run as services, deployed from templates and billed by resource usage | | Payment methods | Card in USD, or bank transfer in Birr | Credit cards for plan subscriptions | | Built-in AI | Coding agent in a live sandbox with diff, apply, PR and branch preview. Plans from $5/month | MCP server that includes a railway-agent tool for debugging logs | | Domains | Buy domains in the dashboard | Buy domains in the dashboard | ### Questions **Is EthioDeploy an alternative to Railway?** For simple GitHub-to-container deploys with a managed database and a built-in AI agent, yes. If you want pure pay-for-what-you-use billing, Railway fits better. **How does billing differ?** Railway charges a subscription plus the compute you use. EthioDeploy sells flat monthly plans with a usage quota, and any overage comes from a prepaid wallet. **Can I use EthioDeploy without an international card?** Yes. Pay in Ethiopian Birr by bank transfer, or by card in USD. Railway's documentation lists credit cards for plan subscriptions. Sources: - [Railway pricing](https://railway.com/pricing) - [Railway plans reference](https://docs.railway.com/reference/pricing/plans) - [Railway pricing FAQs](https://docs.railway.com/reference/pricing/faqs) - [Railway databases](https://docs.railway.com/databases/build-a-database-service) - [Railway MCP server](https://docs.railway.com/ai/mcp-server) - [Railway domains](https://railway.com/domains) ## EthioDeploy vs Fly.io URL: https://ethiodeploy.com/compare/fly-io Last checked: 2026-10-23 Fly.io runs your app as lightweight VMs called Machines in 17 regions and bills for exactly what runs. EthioDeploy builds your GitHub repo into a container in one region, with a free plan and a database included in every plan. The trade-off is reach and flexibility against price and simplicity. Choose Fly.io if: - Your users are spread across continents and you want the app running close to each of them. - You want machines that stop when idle and start again on the next request, so idle apps cost almost nothing. - You're comfortable with Dockerfiles and a fly.toml, and want VM-level control with volumes and a machines API. Choose EthioDeploy if: - You want a free plan. New Fly.io organizations get a trial of 2 hours of machine runtime or 7 days, then pay as they go. - You want Postgres without a separate bill. Fly's Managed Postgres starts at $38/month; EthioDeploy Pro includes a 5 GB database for $10/month. - You want git-push deploys with no Dockerfile or config file to write. - You want an AI coding agent that edits and runs your repo in a sandbox, then opens a pull request. - You want to pay in Birr by bank transfer, or buy your domain in the same dashboard. | Feature | EthioDeploy | Fly.io | | --- | --- | --- | | Free plan | 1 project, sleeps after 30 minutes idle | No free tier for new organizations. Trial: 2 hours of machine runtime or 7 days, whichever comes first | | Pricing shape | Plans: Free, Pro $10, Business $30 a month, with a usage quota | Pay per machine, billed by usage, no platform fee | | About 1 GB of memory | Pro $10/month: 1 GB per container, plus a database and custom domains | shared-cpu-1x with 1 GB: $6.70/month. Extra RAM $6/GB/month | | Managed Postgres | Included: 512 MB on Free, 5 GB on Pro, 20 GB on Business | Managed Postgres from $38/month (Basic, 1 GB RAM) plus $0.28/GB storage | | Redis | Redis addon included in the plan | Upstash Redis: $0.20 per 100k requests, or fixed plans from $10/month | | Idle behavior | Free sleeps after 30 minutes. Paid plans are always on | Configurable auto-stop or suspend; a stopped machine pays only for its root filesystem | | Regions | One region | 17 regions with Anycast routing and private networking | | Build setup | Auto-detected from your repo. No Dockerfile or config file | fly launch generates a Dockerfile and fly.toml | | Payment methods | Card in USD, or bank transfer in Birr | Card through Stripe; prepaid credits from $25 | | Built-in AI | Coding agent in a live sandbox: diff, apply, PR and branch preview. Plans from $5/month | fly mcp server lets Claude or Cursor manage apps, logs and secrets (marked experimental) | | Domains | Buy domains in the dashboard | Bring your own domain; certificates via fly certs, first 10 free | ### Questions **Does Fly.io have a free tier?** Not for new organizations. Fly.io's pricing docs say new organizations don't get a free tier or a monthly free usage allowance. They get a trial of up to 2 hours of machine runtime or 7 days. EthioDeploy's Free plan runs one project at no cost and sleeps after 30 minutes idle. **Is Fly.io cheaper than EthioDeploy?** For the app alone, it can be: a 1 GB shared machine is $6.70 a month, against $10 for EthioDeploy Pro. Add a managed database and it flips, since Fly's Managed Postgres starts at $38 a month while EthioDeploy includes a 5 GB Postgres, MySQL or Redis in Pro. **Do I need a Dockerfile?** Not on EthioDeploy: it detects Node.js, Python, Go, Ruby, PHP and static sites from your repo. Fly.io's fly launch generates a Dockerfile for you, which you then maintain. **Can I run a background worker or bot?** Yes on both. On Fly.io any process can run on a machine. On EthioDeploy, set the project's service type to Background Worker, which skips the HTTP health check and is never put to sleep. **Which is better for users in Africa?** Fly.io has a region in Johannesburg, so an app there is close to southern African users, though its Managed Postgres isn't offered in that region. EthioDeploy runs in one region and doesn't claim edge performance. Its advantage for African teams is billing: you can pay in Ethiopian Birr by bank transfer. Sources: - [Fly.io pricing](https://docs.fly.io/about/pricing) - [Fly.io billing](https://docs.fly.io/about/billing) - [Fly.io autostop and autostart](https://docs.fly.io/launch/autostop-autostart/) - [Fly.io Managed Postgres](https://docs.fly.io/mpg/) - [Fly.io Upstash Redis](https://docs.fly.io/upstash/redis/) - [Fly.io regions](https://docs.fly.io/reference/regions/) - [Fly.io custom domains](https://fly.io/docs/networking/custom-domain/) - [Fly.io MCP server](https://fly.io/docs/flyctl/mcp-server/) ## EthioDeploy vs Netlify URL: https://ethiodeploy.com/compare/netlify Last checked: 2026-10-23 Netlify is a frontend platform: static sites and frameworks on a global CDN, with serverless functions, forms and now a Postgres database, all billed in credits. EthioDeploy runs your whole app as a long-lived container with a database next to it. If your app is mostly frontend, Netlify is a strong fit. If it's a server, a bot or a worker, that's what EthioDeploy is built for. Choose Netlify if: - Your site is static or Jamstack and benefits from a global CDN, with free deploy previews and branch deploys. - You want forms, blobs, a database and AI Gateway on one credit bill. - You want a hard cap on the free plan so you can never get a surprise bill, and you're fine with the site pausing at the cap. Choose EthioDeploy if: - Your app is a long-running server. Netlify's synchronous functions time out at 60 seconds and background functions at 15 minutes. - You run bots, queue consumers or WebSocket servers, which need a process that stays up. - You want flat monthly pricing instead of credits, where bandwidth costs 20 credits per GB since April 2026. - You want to pay in Birr by bank transfer, as well as by card. - You want an AI coding agent that edits and runs your repo in a sandbox, then opens a pull request. | Feature | EthioDeploy | Netlify | | --- | --- | --- | | Best for | Full-stack apps, APIs, bots and workers in containers | Static sites and frontend frameworks with serverless functions | | Free plan | 1 project, sleeps after 30 minutes idle | 300 credits a month, hard limit. When they run out, all projects pause until the next cycle or an upgrade | | Entry paid price | Pro $10/month flat, always on | Personal $9/month for 1,000 credits; Pro $20/month for 3,000 credits with unlimited members | | How usage is billed | Monthly usage quota, overage from a prepaid wallet | Credits: 15 per production deploy, 20 per GB of bandwidth, 10 per GB-hour of compute, 2 per 10k requests | | Long-running processes | Always-on web services and Background Workers | Synchronous functions 60 s, background functions 15 min, scheduled functions 30 s. No persistent workers | | Managed database | Postgres, MySQL or Redis addon included: 512 MB on Free, 5 GB on Pro | Netlify Database (Postgres on Neon), billed in compute credits; free plan sleeps after 5 minutes | | Payment methods | Card in USD, or bank transfer in Birr | Card, Apple Pay or Google Pay. ACH or wire on Enterprise only | | Built-in AI | Coding agent in a live sandbox: diff, apply, PR and branch preview. Plans from $5/month | Agent Runners (hosted agents, billed in credits) and a remote MCP server | | Domains | Buy domains in the dashboard | Buy domains in the dashboard | ### Questions **What happens when Netlify's free credits run out?** The Free plan has 300 credits a month as a hard limit, and you can't buy more on it. When they're used up, every project on the account is paused and shows a 'Site not available' page until you upgrade or the next cycle starts. **Did Netlify raise its prices?** On April 14, 2026 Netlify changed its credit rates: bandwidth went from 10 to 20 credits per GB and compute from 5 to 10 credits per GB-hour, while web requests dropped from 3 to 2 credits per 10k and forms became free. Sites with heavy bandwidth use more credits than before. **Can I run an Express, Django or Laravel server on Netlify?** Not as a long-running server. Netlify runs server code as functions with time limits, so a traditional server has to be adapted to functions. On EthioDeploy it runs as-is in a container. See the deploy guides for Express, Django and Laravel. **Is EthioDeploy a Netlify alternative for static sites?** It can host them: Vite and other static builds are served with an index.html fallback so client-side routes work. But it serves them from one region, without Netlify's global CDN, so for a purely static site with global traffic Netlify is the better fit. **Can I pay without an international card?** On EthioDeploy, yes: pay in Ethiopian Birr by bank transfer, or by card in USD. Netlify's billing docs list cards, Apple Pay and Google Pay, with ACH or wire only on Enterprise. Sources: - [Netlify pricing](https://www.netlify.com/pricing/) - [Netlify credit-based plans](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/credit-based-pricing-plans/) - [Netlify pricing updates, April 2026](https://www.netlify.com/changelog/2026-04-14-pricing-updates-april-2026/) - [Netlify paused projects](https://docs.netlify.com/manage/accounts-and-billing/billing/resume-paused-projects/) - [Netlify functions configuration](https://docs.netlify.com/build/functions/configuration/) - [Netlify background functions](https://docs.netlify.com/build/functions/background-functions/) - [Netlify scheduled functions](https://docs.netlify.com/build/functions/scheduled-functions/) - [Netlify Database billing](https://docs.netlify.com/build/data-and-storage/netlify-database/billing-and-usage/) - [Netlify payment methods](https://docs.netlify.com/manage/accounts-and-billing/billing/manage-payments/) - [Netlify MCP server](https://docs.netlify.com/build/build-with-ai/netlify-mcp-server/) - [Netlify domain registration](https://docs.netlify.com/manage/domains/configure-domains/register-and-buy-a-domain/) ## Heroku alternatives URL: https://ethiodeploy.com/alternatives/heroku Last checked: 2026-10-23 Most people looking for Heroku alternatives in 2026 are reacting to one announcement. On February 6, 2026, Heroku said it is moving to a sustaining engineering model focused on stability, security and support rather than new features, and that it will no longer offer Enterprise contracts to new customers. Card-paying customers can keep using it with no pricing changes, so nothing forces a move today. But a platform that has stopped adding features is a reasonable moment to look around. Heroku still does the basics well: git-based deploys, a mature add-on marketplace, Procfile process types and a polished Postgres. What it lacks is a free tier. Heroku stopped offering free dynos, free Postgres and free Redis starting November 28, 2022, and its cheapest dyno today is Eco at $5 a month, which sleeps after 30 minutes of inactivity. Memory is expensive too: 1 GB starts at $50 a month on a Standard-2X dyno. Below are five Heroku alternatives that a real switcher would shortlist, what each one is best at, which still have a free tier, what each costs for a small always-on app, and how much of your Heroku setup carries over. ### EthioDeploy Best for: A cheap always-on app with a database included, and an AI coding agent. Starting price: Free; Pro $10/month. EthioDeploy builds your GitHub repo into a container on every push, with no Dockerfile. It detects Node.js, Python, Go, Ruby, PHP and static sites, and reads the web line of a Procfile as the start command. Every plan includes a managed Postgres, MySQL or Redis addon, and each project has an AI coding agent that edits and runs your code in a sandbox, then opens a pull request. Pricing: Free runs 1 project (256 MB, sleeps after 30 minutes idle) with a 512 MB database. Pro is $10/month: always on, 1 GB per container, a 5 GB database and custom domains. Business is $30/month: 2 GB per container, a 20 GB database and 5 team seats. Both paid plans meter usage against a monthly quota. ### Render Best for: The widest set of Heroku-style service types, including cron jobs. Starting price: Free; $7/month per service. Render is the closest match to Heroku's feature set: web services, background workers, cron jobs and private services are all first-class, and it publishes a step-by-step Heroku migration guide. Pricing: Free web services spin down after 15 minutes without traffic. Starter is $7/month for 512 MB; the next size is $25/month for 2 GB. Postgres starts at $6/month; free Postgres expires after 30 days. Key Value is free at 25 MB, paid from $10/month. ### Railway Best for: Pure usage-based billing and multi-service projects on one canvas. Starting price: Free ($1 credit); Hobby $5/month. Railway bills per second for the memory and CPU you actually use. Databases are one-click templates that run as services next to your app, and its Heroku guide includes a command to import your Heroku config vars. Pricing: Hobby is $5/month including $5 of usage. RAM is $10 per GB per month and CPU $20 per vCPU per month, so a 1 GB service costs about $10 plus CPU. The Free plan gives $1 of credit a month, capped at 0.5 GB per service. ### Fly.io Best for: Running close to users in many regions. Starting price: Pay as you go, no free tier. Fly.io runs your app as Machines (lightweight VMs) in 17 regions, with private networking between them. `fly launch` generates a Dockerfile, and machines can stop when idle and start again on the next request. Pricing: New organizations get no free tier, only a trial of 2 hours of machine runtime or 7 days. A shared-cpu-1x machine with 1 GB is $6.70/month. Managed Postgres starts at $38/month. Redis is offered through Upstash from $10/month or per request. ### DigitalOcean App Platform Best for: Heroku buildpack compatibility from a large, established cloud. Starting price: $5/month per container. App Platform builds with Cloud Native Buildpacks, and for Node.js it uses Heroku's own buildpack, so builds behave much like they did on Heroku. Worker components and scheduled jobs cover the rest of a typical Heroku app. Pricing: Containers start at $5/month for 512 MiB; 1 GiB is $10 fixed or $12 shared. A dev Postgres is $7/month; managed Postgres starts at $15.15/month. Redis has been replaced by Valkey, from $15/month. The free tier covers up to 3 static sites only. ### Questions **Is Heroku shutting down?** No. On February 6, 2026 Heroku announced a move to a sustaining engineering model focused on stability, security, reliability and support, rather than new features. Existing customers, and new customers paying by card, can keep using it with no pricing changes. It stopped offering Enterprise contracts to new customers. **Does Heroku still have a free tier?** No. Heroku stopped offering free dynos, free Heroku Postgres and free Heroku Data for Redis starting November 28, 2022. The cheapest option now is the Eco dyno at $5 a month, which sleeps after 30 minutes of inactivity, and databases are paid add-ons. **What is the cheapest Heroku alternative?** For an always-on app with about 1 GB of memory and a Postgres database, EthioDeploy Pro at $10 a month includes both. Fly.io's 1 GB machine is $6.70 a month, but its managed Postgres starts at $38. Render's cheapest always-on service is $7 a month with 512 MB. **Which Heroku alternatives have a free tier?** EthioDeploy (1 project, sleeps after 30 minutes idle), Render (web services sleep after 15 minutes, free Postgres expires after 30 days) and Railway ($1 of credit a month). Fly.io has no free tier for new accounts, and DigitalOcean's free tier covers static sites only. **Can I keep my Procfile?** Partly, on most platforms. EthioDeploy uses the web line as the start command for Node.js, Python, Go and Ruby apps. Railway reads a Procfile but supports one process. Render, Fly.io and DigitalOcean have you recreate each process type by hand. Worker processes become separate services everywhere. **How do I move my Heroku config vars?** On EthioDeploy, run heroku config -s > .env to export them, delete the DATABASE_URL and REDIS_URL lines (they point at Heroku, and the addon sets its own), then run ethiodeploy env push .env to import the rest. Delete the local .env file afterwards, since it holds your secrets. **What replaces Heroku Scheduler?** Render, Railway and DigitalOcean have cron jobs built in. On EthioDeploy and Fly.io, run a scheduler inside a long-running process, for example a Background Worker running Laravel's schedule:work, a node-cron script or APScheduler. **Which Heroku alternative is closest to Heroku buildpacks?** DigitalOcean App Platform uses Cloud Native Buildpacks and Heroku's own Node.js buildpack. Koyeb also builds with a builder derived from Heroku's, but it announced it is joining Mistral AI and removing its free Starter plan, so check its current terms first. Sources: - [Heroku: An Update on Heroku (Feb 6, 2026)](https://www.heroku.com/blog/an-update-on-heroku/) - [Heroku pricing](https://www.heroku.com/pricing) - [Heroku's Next Chapter (Aug 25, 2022)](https://www.heroku.com/blog/next-chapter) - [Heroku Postgres plans](https://devcenter.heroku.com/articles/heroku-postgres-plans) - [Render pricing](https://render.com/pricing) - [Render free tier](https://render.com/docs/free) - [Render: migrate from Heroku](https://render.com/docs/migrate-from-heroku) - [Railway pricing plans](https://docs.railway.com/reference/pricing/plans) - [Railway: migrate from Heroku](https://docs.railway.com/platform/migrate-from-heroku) - [Railway cron jobs](https://docs.railway.com/reference/cron-jobs) - [Fly.io pricing](https://docs.fly.io/about/pricing) - [Fly.io Managed Postgres](https://docs.fly.io/mpg/) - [Fly.io: migrate from Heroku](https://docs.fly.io/getting-started/migrate-from-heroku/) - [Fly.io regions](https://docs.fly.io/reference/regions/) - [DigitalOcean App Platform pricing](https://www.digitalocean.com/pricing/app-platform) - [DigitalOcean: migrate from Heroku](https://docs.digitalocean.com/products/app-platform/getting-started/migrate-from-heroku/) - [DigitalOcean Node.js buildpack](https://docs.digitalocean.com/products/app-platform/reference/buildpacks/nodejs/) - [Koyeb is joining Mistral AI](https://www.koyeb.com/blog/koyeb-is-joining-mistral-ai-to-build-the-future-of-ai-infrastructure) - [Koyeb: build from git](https://www.koyeb.com/docs/build-and-deploy/build-from-git) ## Vercel alternatives URL: https://ethiodeploy.com/alternatives/vercel Last checked: 2026-10-27 Three limits push people to look for Vercel alternatives. The Hobby plan is for non-commercial personal use only, so a project that takes payments or runs ads needs Pro at $20 per developer seat. Functions stop at a maximum duration (300 seconds on Hobby, 800 seconds on Pro), and WebSocket connections close when they do. And there is no first-party Postgres any more: Vercel Postgres was moved to Neon in December 2024, so databases come from Marketplace partners and are billed separately. Vercel is still the default home for Next.js, with a global CDN in 94 cities and preview deployments on every push. Always-on Daemons, Services and WebSockets now exist, but all three are in beta. Below are five Vercel alternatives, what each is best at, what it costs, and which kind of app fits it. ### EthioDeploy Best for: Full-stack apps that need an always-on server and a database at a flat price. Starting price: Free; Pro $10/month. EthioDeploy builds your GitHub repo into a container on every push, with no Dockerfile. Next.js runs as one long-running Node server, standalone output included, so there are no function time limits and WebSockets stay open. Every plan includes a managed Postgres, MySQL or Redis addon, and each project has an AI coding agent that edits and runs your code in a sandbox, then opens a pull request with a branch preview. Pricing: Free runs 1 project (256 MB, sleeps after 30 minutes idle) with a 512 MB database. Pro is $10/month flat: always on, 1 GB per container, a 5 GB database, custom domains and 1 branch preview per project. Business is $30/month with 2 GB containers, a 20 GB database and 5 team seats. Pay by card in USD or by bank transfer in Ethiopian Birr. ### Netlify Best for: Static and Jamstack sites with unlimited team members. Starting price: Free (300 credits); Personal $9/month. Netlify works much like Vercel: git-based deploys, a global CDN, deploy previews and serverless functions. Since September 2025 every plan is credit-based, and Pro has no per-seat price. Pricing: Free has 300 credits a month, Personal is $9 for 1,000 credits and Pro starts at $20 for 3,000 credits with unlimited members. A production deploy costs 15 credits, bandwidth 20 credits per GB and compute 10 credits per GB-hour. Deploy previews are free. ### Cloudflare Workers Best for: Edge-first apps with very low cost at high traffic. Starting price: Free; Workers Paid $5/month. Cloudflare runs your code in V8 isolates on its network of 348 cities. Cloudflare now steers new projects to Workers rather than Pages, recommends vinext for Next.js apps, and has Containers, Durable Objects, D1 and R2 for state. Pricing: Free allows 100,000 requests a day with 10 ms of CPU per request. Workers Paid starts at $5 a month with 10 million requests and 30 million CPU-ms included, then $0.30 per million requests. Static asset requests and egress are free. ### Render Best for: Long-running services, workers and cron jobs on one platform. Starting price: Free; $7/month per service. Render runs web services, background workers, cron jobs and private services as always-on containers, with managed Postgres and Key Value. Since April 2026 workspace plans have no seat fees. Pricing: Free web services spin down after 15 minutes. The smallest always-on service is $7/month for 512 MB; the next size is $25/month for 2 GB. Postgres starts at $6/month. Full-stack preview environments need the Pro workspace at $25/month plus compute. ### Railway Best for: Usage-based billing for multi-service projects. Starting price: Free ($1 credit); Hobby $5/month. Railway bills per second for the memory and CPU each service uses. Databases are one-click templates that run as services next to your app, and PR environments are created automatically. Pricing: Hobby is $5/month including $5 of usage; Pro is $20/month per workspace with unlimited seats. RAM is about $10 per GB per month and CPU about $20 per vCPU per month, so a 1 GB service costs about $10 plus CPU. Egress is $0.05 per GB. ### Questions **Can I use Vercel's free plan for a commercial project?** No. Vercel's fair use guidelines restrict Hobby to non-commercial personal use, which rules out taking payments, running ads or being paid to build the site. EthioDeploy's Free plan has no commercial-use restriction, though the app sleeps after 30 minutes without traffic. **What is the cheapest Vercel alternative for a full-stack app?** For an always-on app with about 1 GB of memory and a database, EthioDeploy Pro at $10 a month includes both. Railway costs about $10 a month for 1 GB plus CPU and database usage, and Render's smallest always-on service is $7 a month with 512 MB. **Which Vercel alternatives have no function time limit?** Platforms that run your app as a long-lived container: EthioDeploy, Render and Railway. Netlify's synchronous functions stop at 60 seconds, and Cloudflare Workers allow up to 5 minutes of CPU time on the paid plan. **Can I host Next.js outside Vercel?** Yes. EthioDeploy, Render and Railway run next start or standalone output as a Node server. Netlify supports Next.js through its own adapter, and Cloudflare recommends vinext. Features tied to Vercel's infrastructure, such as its image CDN, can behave differently elsewhere. **Does Vercel still have its own Postgres database?** No. Vercel Postgres is no longer available; existing databases moved to Neon in December 2024. New projects add Postgres from Marketplace providers such as Neon or Supabase, billed separately. **Which Vercel alternative has a global CDN?** Cloudflare Workers (348 cities) and Netlify (100+ points of presence). EthioDeploy, Render and Railway run each app in a single region, without a global edge network. **Can I pay for a Vercel alternative without an international card?** Vercel's billing docs list credit or debit cards only, charged in USD. EthioDeploy accepts cards in USD and bank transfers in Ethiopian Birr. Sources: - [Vercel pricing](https://vercel.com/pricing) - [Vercel fair use guidelines](https://vercel.com/docs/limits/fair-use-guidelines) - [Vercel function limitations](https://vercel.com/docs/functions/limitations) - [Vercel Daemons](https://vercel.com/kb/guide/vercel-daemons) - [Vercel WebSockets](https://vercel.com/kb/guide/do-vercel-serverless-functions-support-websocket-connections) - [Vercel Postgres](https://vercel.com/docs/postgres) - [Vercel cron jobs](https://vercel.com/docs/cron-jobs/usage-and-pricing) - [Vercel regions](https://vercel.com/docs/regions) - [Vercel Pro billing](https://vercel.com/docs/plans/pro-plan/billing) - [Vercel Agent pricing](https://vercel.com/docs/agent/pricing) - [Netlify pricing](https://www.netlify.com/pricing/) - [Netlify: how credits work](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/how-credits-work/) - [Netlify billing FAQ](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/billing-faq-for-credit-based-plans/) - [Netlify functions configuration](https://docs.netlify.com/build/functions/configuration/) - [Cloudflare Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/) - [Cloudflare Workers limits](https://developers.cloudflare.com/workers/platform/limits/) - [Cloudflare: Next.js on Workers](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/) - [Cloudflare network](https://www.cloudflare.com/network/) - [Render pricing](https://render.com/pricing) - [Render free tier](https://render.com/docs/free) - [Railway pricing](https://railway.com/pricing) - [Railway incident report, May 19, 2026](https://blog.railway.com/p/incident-report-may-19-2026-gcp-account-outage) ## Netlify alternatives URL: https://ethiodeploy.com/alternatives/netlify Last checked: 2026-10-27 Many searches for Netlify alternatives start with a billing surprise. Since September 4, 2025, new Netlify accounts are on credit-based plans, and when a team's credits run out every site on it is paused, paid plans included, unless auto-recharge is turned on. It is off by default. The credit rates have also changed since launch: Pro went from 5,000 to 3,000 monthly credits, and bandwidth and compute now cost twice as many credits as at launch. Netlify is still a strong static and Jamstack host. Deploy previews cost no credits, Pro has no seat fees, and Netlify Database adds serverless Postgres. Its limits are on the server side: synchronous functions stop at 60 seconds, function runtimes are JavaScript, TypeScript and Go, and there is no documented always-on server. Below are five Netlify alternatives, from free static hosting to full containers, and who each one suits. ### EthioDeploy Best for: Apps that need a real server and a database, at a flat price that never pauses mid-month. Starting price: Free; Pro $10/month. EthioDeploy builds your GitHub repo into a container on every push, with no Dockerfile. Vite and other static builds are served with an index.html fallback, and apps with a server run it as a normal long-running process. Every plan includes a managed Postgres, MySQL or Redis addon, and each project has a built-in AI coding agent. Pricing: Free runs 1 project (256 MB, sleeps after 30 minutes idle) with a 512 MB database. Pro is $10/month: always on, 1 GB per container, a 5 GB database and custom domains. Business is $30/month with 2 GB containers and 5 team seats. Paid plans meter usage against a monthly quota, with overage taken from a prepaid wallet. ### Vercel Best for: Next.js apps that want the framework's home platform. Starting price: Free (non-commercial); Pro $20/month per seat. Vercel is the company behind Next.js. It deploys on every push with previews, serves from a CDN in 94 cities and runs server code on Fluid compute in Node.js, Python, Go, Ruby, Rust or a container image. Pricing: Hobby is free for non-commercial personal use. Pro is $20 a month per developer seat with $20 of usage credit included, then data transfer from $0.15 per GB and function CPU from $0.128 per hour. Viewer seats are free. ### Cloudflare Workers Best for: Static sites with heavy traffic, at almost no cost. Starting price: Free; Workers Paid $5/month. Cloudflare now steers new sites to Workers with static assets instead of Pages. Static asset requests are free and unlimited, and Workers add server code at the edge, with D1, KV, R2 and Containers for state. Pricing: Static asset requests are free on both plans. Requests to your Worker code are 100,000 a day free, or 10 million a month included in Workers Paid at $5, then $0.30 per million. Egress is free. ### Render Best for: Free static sites plus always-on backends on one platform. Starting price: Free; $7/month per service. Render hosts static sites for free and runs backends as always-on web services, workers and cron jobs, with managed Postgres and Key Value. Workspace plans have no seat fees since April 2026. Pricing: Static sites are free. Free web services spin down after 15 minutes; always-on services start at $7/month for 512 MB, then $25 for 2 GB. Postgres starts at $6/month. Bandwidth beyond the plan's allowance (5 GB on Hobby) is $0.15 per GB. ### GitHub Pages Best for: Free hosting for personal, project and documentation sites. Starting price: Free. GitHub Pages publishes static HTML, CSS and JavaScript straight from a repository, with custom domains and automatic HTTPS. Builds run through GitHub Actions. Pricing: Free for public repositories. Private repositories need GitHub Pro, Team or Enterprise, and the published site is public either way unless you are on Enterprise Cloud. ### Questions **Why did Netlify pause my site?** On credit-based plans, when a team runs out of credits Netlify pauses all of its web projects and visitors see a Site not available page. This applies to paid plans too unless auto-recharge is turned on. Free teams have to wait for the next billing cycle or upgrade. **What is the best free Netlify alternative?** For static sites, Cloudflare Workers (free, unlimited static requests) or GitHub Pages (free for public repos, but not for commercial sites). For an app with a server, EthioDeploy's Free plan runs one project with a 512 MB database, and Render's free web services sleep after 15 minutes. **Which Netlify alternatives have flat pricing?** EthioDeploy has flat plans ($10 and $30 a month) with a usage quota. Render charges a fixed price per service. Vercel, Cloudflare and Railway combine a base fee with usage charges. **Which Netlify alternatives can run Python or PHP?** EthioDeploy and Render run Python as long-running services, and EthioDeploy also detects PHP. Vercel and Cloudflare Workers run Python as functions. Netlify's function runtimes are JavaScript, TypeScript and Go. **Can I move a Netlify site to EthioDeploy?** Yes. Connect the same GitHub repo. Static builds such as Vite are served with an index.html fallback so client-side routes work. Netlify Functions, Forms and redirects in netlify.toml don't carry over; move function code into your app's server. **Is Netlify cheaper than Vercel?** For teams, often yes: Netlify Pro starts at $20 a month with unlimited members, while Vercel Pro is $20 per developer seat. For a single developer the base price is the same, and the bill depends on bandwidth and compute in both cases. Sources: - [Netlify pricing](https://www.netlify.com/pricing/) - [Netlify credit-based plans](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/credit-based-pricing-plans/) - [Netlify: how credits work](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/how-credits-work/) - [Netlify billing FAQ](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/billing-faq-for-credit-based-plans/) - [Netlify: updates to Personal and Pro plans](https://www.netlify.com/changelog/updates-credit-based-personal-and-pro-plans/) - [Netlify: pricing for 3 billion builders](https://www.netlify.com/blog/pricing-netlify-for-3-billion-builders/) - [Netlify functions configuration](https://docs.netlify.com/build/functions/configuration/) - [Netlify Database](https://www.netlify.com/changelog/2026-04-28-netlify-database/) - [Vercel pricing](https://vercel.com/pricing) - [Vercel fair use guidelines](https://vercel.com/docs/limits/fair-use-guidelines) - [Vercel function limitations](https://vercel.com/docs/functions/limitations) - [Cloudflare Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/) - [Cloudflare Pages limits](https://developers.cloudflare.com/pages/platform/limits/) - [Cloudflare: migrate from Pages to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) - [Render pricing](https://render.com/pricing) - [Render free tier](https://render.com/docs/free) - [GitHub Pages limits](https://docs.github.com/en/pages/getting-started-with-github-pages/github-pages-limits) - [GitHub Pages: creating a site](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site) ## Render alternatives URL: https://ethiodeploy.com/alternatives/render Last checked: 2026-10-27 People looking for Render alternatives usually run into its pricing steps. The smallest always-on service is $7 a month for 512 MB, and the next size up is $25 for 2 GB, with nothing at 1 GB in between. Free web services spin down after 15 minutes without traffic and take about a minute to wake, free Postgres is deleted 30 days after creation, and full-stack preview environments need the Pro workspace at $25 a month plus compute. Render gets a lot right. Web services, background workers, cron jobs and private services are all first-class, it runs in five regions, and since April 2026 its workspace plans have no seat fees. Below are five Render alternatives with what each costs for a small always-on app and where each one fits better or worse than Render. ### EthioDeploy Best for: A 1 GB always-on app with a database for $10, and an AI coding agent. Starting price: Free; Pro $10/month. EthioDeploy builds your GitHub repo into a container on every push, with no Dockerfile, for Node.js, Python, Go, Ruby, PHP and static sites. The plan price includes the database and branch previews, and each project has an AI coding agent that edits and runs your code in a sandbox, then opens a pull request. Pricing: Free runs 1 project (256 MB, sleeps after 30 minutes idle) with a 512 MB database that doesn't expire. Pro is $10/month: always on, 1 GB per container, a 5 GB database, custom domains and 1 branch preview per project. Business is $30/month: 2 GB per container, a 20 GB database, 2 previews per project and 5 team seats. ### Railway Best for: Paying only for the RAM and CPU you use. Starting price: Free ($1 credit); Hobby $5/month. Railway bills per second for each service's memory and CPU, with no fixed instance sizes. Databases are one-click templates, PR environments are created automatically, and it builds with Railpack or a Dockerfile. Pricing: Hobby is $5/month including $5 of usage; Pro is $20/month per workspace. RAM is about $10 per GB-month and CPU about $20 per vCPU-month, so 1 GB costs about $10 plus CPU. Egress is $0.05 per GB, volumes $0.15 per GB-month. ### Fly.io Best for: Running in many regions close to users. Starting price: Pay as you go, no free tier. Fly.io runs apps as Machines (lightweight VMs) in 17 regions. It builds from a Dockerfile, buildpacks or a prebuilt image, and machines can stop when idle and start on the next request. Pricing: No free tier for new organizations, only a trial of 2 hours of machine runtime or 7 days. A shared-cpu-1x machine is $3.69/month for 512 MB and $6.70 for 1 GB in the cheapest regions. Managed Postgres starts at $38/month. ### DigitalOcean App Platform Best for: A 1 GB container from a large, established cloud. Starting price: $5/month per container. App Platform builds with buildpacks for Go, Node.js, PHP, Python, Ruby, Rust, .NET and Bun, or from a Dockerfile. Workers and jobs (including cron-scheduled ones) cover the rest of a typical app. Pricing: 512 MiB is $5/month, 1 GiB $10 (fixed, single instance) or $12 (shared, can scale), 2 GiB $25. A dev Postgres is $7/month; managed Postgres starts at $15.15. The free tier covers 3 static sites only. ### Heroku Best for: Teams that want the original PaaS workflow and add-on marketplace. Starting price: Eco $5/month. Heroku runs apps on dynos built with buildpacks, with Procfile process types, review apps, Heroku Scheduler and a large add-on marketplace. In February 2026 it moved to a sustaining engineering model focused on stability rather than new features. Pricing: Eco is $5/month for 1,000 shared dyno hours and sleeps after 30 minutes. Basic is $7 per dyno, Standard-1X $25 and Standard-2X (1 GB) $50. Postgres starts at $5/month and Key-Value Store at $3. ### Questions **Why does my Render app take a minute to load?** Render's free web services spin down after 15 minutes without inbound traffic and take about a minute to spin back up on the next request. Paid instances, from $7 a month, stay running. **What is the cheapest Render alternative?** For a 1 GB always-on app with Postgres, EthioDeploy Pro at $10 a month includes both. Fly.io's 1 GB machine is $6.70 but its Managed Postgres starts at $38, and DigitalOcean's 1 GB container is $10 to $12 plus $7 for a dev database. **Does Render's free Postgres expire?** Yes. A free Render Postgres database expires 30 days after creation, then has a 14-day grace period before it is deleted. EthioDeploy's free 512 MB database doesn't expire. **Does Render still charge per seat?** No. Since April 23, 2026 Render's workspace plans (Hobby, Pro at $25 a month, Scale at $499) have no seat fees. Compute is billed on top of the workspace plan. **Which Render alternatives have cron jobs?** Railway, DigitalOcean App Platform and Heroku (Scheduler) have scheduled jobs, and Fly.io can run machines on an hourly, daily, weekly or monthly schedule. EthioDeploy has no cron service type; run a scheduler inside a Background Worker. **How do I move a Render service to EthioDeploy?** Create a project from the same GitHub repo; the stack is detected, so the build and start commands usually need no changes. Copy your environment variables into a .env file and run ethiodeploy env push .env. Add a Postgres addon, which sets DATABASE_URL for you. Sources: - [Render pricing](https://render.com/pricing) - [Render free tier](https://render.com/docs/free) - [Render: updated plans for workspaces](https://render.com/changelog/updated-plans-for-render-workspaces) - [Render preview environments](https://render.com/docs/preview-environments) - [Render cron jobs](https://render.com/docs/cronjobs) - [Render regions](https://render.com/docs/regions) - [Railway pricing](https://railway.com/pricing) - [Railway pricing plans](https://docs.railway.com/reference/pricing/plans) - [Railway environments](https://docs.railway.com/guides/environments) - [Railway incident report, May 19, 2026](https://blog.railway.com/p/incident-report-may-19-2026-gcp-account-outage) - [Fly.io pricing](https://docs.fly.io/about/pricing) - [Fly.io Managed Postgres](https://docs.fly.io/mpg/) - [Fly.io regions](https://docs.fly.io/reference/regions/) - [Fly.io review apps](https://docs.fly.io/blueprints/review-apps-guide) - [DigitalOcean App Platform pricing](https://www.digitalocean.com/pricing/app-platform) - [DigitalOcean App Platform availability](https://docs.digitalocean.com/products/app-platform/details/availability/) - [Heroku pricing](https://www.heroku.com/pricing) - [Heroku: An Update on Heroku (Feb 6, 2026)](https://www.heroku.com/blog/an-update-on-heroku/) ## Railway alternatives URL: https://ethiodeploy.com/alternatives/railway Last checked: 2026-10-27 The usual reason to look for Railway alternatives is a bill you can't predict. Railway charges per second for RAM and CPU, and its billing docs say running services pay for both even when idle, so the $5 Hobby fee is a minimum rather than a price. Databases have no fixed price either; they run as templates and bill as usage. Reliability is the other reason: on May 19, 2026 a Google Cloud account suspension took Railway down across all regions for about 8 hours, according to its own incident report. Railway is still easy to work with. Its canvas makes multi-service projects easy to follow, PR environments appear automatically, and Pro includes unlimited seats for $20 a month. Below are five Railway alternatives, from flat-priced hosting to running Coolify on your own server. ### EthioDeploy Best for: A flat monthly price with the database included. Starting price: Free; Pro $10/month. EthioDeploy builds your GitHub repo into a container on every push, with no Dockerfile. Plans are flat: the price covers an always-on container, a managed Postgres, MySQL or Redis, and branch previews. Each project also has an AI coding agent that edits and runs your code in a sandbox, then opens a pull request. Pricing: Free runs 1 project (256 MB, sleeps after 30 minutes idle) with a 512 MB database. Pro is $10/month: always on, 1 GB per container, a 5 GB database and custom domains. Business is $30/month: 2 GB per container, a 20 GB database and 5 team seats. Both meter usage against a monthly quota; overage comes from a prepaid wallet. ### Render Best for: Fixed per-service prices with cron jobs and private services. Starting price: Free; $7/month per service. Render runs web services, background workers, cron jobs and private services at a fixed price per instance size, with managed Postgres and Key Value. Workspace plans have no seat fees since April 2026. Pricing: Free web services sleep after 15 minutes. Always-on services are $7/month for 512 MB and $25 for 2 GB. Postgres starts at $6/month, Key Value at $10. Preview environments need the Pro workspace at $25/month. ### Fly.io Best for: Many regions and machines that stop when idle. Starting price: Pay as you go, no free tier. Fly.io runs apps as Machines in 17 regions, built from a Dockerfile, buildpacks or an image. Machines can stop or suspend when idle and aren't billed for CPU or RAM while stopped. Pricing: No free tier for new organizations, only a trial of 2 hours of runtime or 7 days. shared-cpu-1x is $3.69/month for 512 MB and $6.70 for 1 GB in the cheapest regions. Managed Postgres starts at $38/month. ### DigitalOcean App Platform Best for: Fixed container prices from an established cloud. Starting price: $5/month per container. App Platform builds with buildpacks or a Dockerfile and runs services, workers and jobs at fixed monthly prices per container size, next to DigitalOcean's managed databases. Pricing: 512 MiB is $5/month, 1 GiB $10 or $12, 2 GiB $25. A dev Postgres is $7/month; managed Postgres from $15.15 and Valkey from $15. Free tier: 3 static sites. ### Coolify Best for: Self-hosting a Railway-style platform on your own server. Starting price: Free self-hosted; Cloud $5/month. Coolify is an open-source (Apache-2.0) platform you install on your own server. It deploys from Nixpacks, Railpack, a Dockerfile or Docker Compose, and has one-click Postgres, MySQL, Redis, MongoDB and more. Pricing: Self-hosted Coolify is free. Coolify Cloud, which manages Coolify for you while you bring the servers, is $5/month for 2 servers plus $3 per extra server. The server itself is billed by your provider. ### Questions **Why is my Railway bill higher than $5?** The Hobby fee is a minimum usage commitment that includes $5 of usage. Running services are billed for RAM and CPU every second, even when idle, plus egress at $0.05 per GB and volumes at $0.15 per GB-month. A single always-on 1 GB service costs about $10 a month in RAM before CPU. **What is the cheapest Railway alternative?** For an always-on 1 GB app with Postgres, EthioDeploy Pro at $10 a month includes both. Fly.io's 1 GB machine is $6.70 a month, but its Managed Postgres starts at $38. If you already rent a server, self-hosted Coolify is free. **Does Railway have a free tier?** Yes, two kinds. New accounts get a 30-day trial with $5 of credit, and the Free plan gives $1 of usage a month with services capped at 0.5 GB of RAM. Trial volumes are deleted 30 days after the credit expires. **Which Railway alternatives have fixed prices?** EthioDeploy (flat plans at $10 and $30 a month), Render (fixed price per instance size) and DigitalOcean App Platform (fixed price per container). Fly.io publishes a fixed monthly price per machine size but bills per second of runtime. **Is Coolify a good Railway alternative?** If you are comfortable running a Linux server, yes. Coolify is free and open source and deploys from Nixpacks, Railpack, a Dockerfile or Docker Compose. You handle the server's updates, firewall and backups, and it needs at least 2 CPU cores and 2 GB of RAM. **How do I move from Railway to EthioDeploy?** Create a project from the same GitHub repo; Node.js, Python, Go, Ruby, PHP and static sites are detected without a Dockerfile. Export your variables to a .env file and run ethiodeploy env push .env, then add a Postgres or Redis addon, which sets DATABASE_URL or REDIS_URL. Sources: - [Railway pricing](https://railway.com/pricing) - [Railway pricing plans](https://docs.railway.com/reference/pricing/plans) - [Railway: understanding your bill](https://docs.railway.com/pricing/understanding-your-bill) - [Railway free trial](https://docs.railway.com/reference/pricing/free-trial) - [Railway regions](https://docs.railway.com/reference/regions) - [Railway cron jobs](https://docs.railway.com/reference/cron-jobs) - [Railway incident report, May 19, 2026](https://blog.railway.com/p/incident-report-may-19-2026-gcp-account-outage) - [Render pricing](https://render.com/pricing) - [Render free tier](https://render.com/docs/free) - [Fly.io pricing](https://docs.fly.io/about/pricing) - [Fly.io Managed Postgres](https://docs.fly.io/mpg/) - [Fly.io autostop and autostart](https://docs.fly.io/launch/autostop-autostart) - [DigitalOcean App Platform pricing](https://www.digitalocean.com/pricing/app-platform) - [DigitalOcean managed databases pricing](https://www.digitalocean.com/pricing/managed-databases) - [Coolify pricing](https://coolify.io/pricing/) - [Coolify installation requirements](https://coolify.io/docs/get-started/installation) - [Coolify on GitHub](https://github.com/coollabsio/coolify) # Blog ## Vite env variables undefined in production: causes and fixes URL: https://ethiodeploy.com/blog/errors/vite-env-variables-undefined-in-production By: Brook Solomon Updated: 2026-10-29 A Vite env variable is undefined in production when the build never saw it. Vite replaces every import.meta.env.VITE_ reference with a fixed value while vite build runs, so a variable added to your server afterwards changes nothing until you build again. The other common causes are a name without the VITE_ prefix and code that reads process.env in the browser. ### Why is my Vite env variable undefined in production? Because Vite reads it once, during the build. The [Vite docs](https://vite.dev/guide/env-and-mode) say `import.meta.env` constants are “defined as global variables during dev and statically replaced at build time.” The built JavaScript holds the literal value. If the variable was missing when `vite build` ran, the built code has nothing to read. That is why an app can work on your laptop and break after deploy. Your laptop has a `.env.local` file. The machine that builds your app usually doesn't. ``` // src/api.ts, what you write const apiUrl = import.meta.env.VITE_API_URL // dist/assets/index-*.js, what ships after the build const apiUrl = "https://api.example.com" ``` ### Quick checklist | What you see | Cause | Fix | | --- | --- | --- | | Works locally, undefined after deploy | The value lives in `.env.local`, which git ignores, so the build server never had it | Set the variable in your host's settings, then rebuild | | Undefined everywhere, even locally | The name has no `VITE_` prefix | Rename it to `VITE_*` and restart the dev server | | `process is not defined` in the browser console | Browser code reads `process.env` | Read `import.meta.env` instead | | Old value still shows after you changed it | The built files still hold the old value | Redeploy so `vite build` runs again | | Undefined inside `vite.config.ts` | `.env` files aren't loaded into `process.env` while the config runs | Use `loadEnv` | | `"3000" + 1` gives `"30001"` | Every value is a string | Convert it with `Number()`, or compare to `"true"` | ### Does the variable name start with VITE_? Only variables that start with `VITE_` reach your client code. Everything else is left out on purpose, so a database password in the same `.env` file never lands in the browser. This is the example from the Vite docs: ``` # .env VITE_SOME_KEY=123 DB_PASSWORD=foobar ``` ``` console.log(import.meta.env.VITE_SOME_KEY) // "123" console.log(import.meta.env.DB_PASSWORD) // undefined ``` Moving from Create React App? `REACT_APP_` names are not exposed. Rename them, or add the old prefix with the `envPrefix` option, which takes a string or an array: ``` // vite.config.ts export default defineConfig({ envPrefix: ["VITE_", "REACT_APP_"], }) ``` Never set `envPrefix` to an empty string. That would expose every variable in the environment, and Vite throws an error if you try. ### Was the variable set when vite build ran? The machine that runs `vite build` needs the variable. Setting it later on the server that serves the files does nothing for `VITE_` values. Three details decide whether the build sees it: - `.env.local` and `.env.*.local` are meant to stay out of git, so a CI runner or a hosting platform never sees them. - `vite build` runs in `production` mode by default, so it loads `.env`, `.env.local`, `.env.production` and `.env.production.local`. A `.env.development` file is ignored. - Variables already set in the environment win over every `.env` file. Setting `VITE_API_URL` in your host's settings works even if a committed `.env` has a different value. With Docker, pass the value as a build argument. Setting it with `docker run -e` is too late, because the image already holds the built files. ``` FROM node:22 AS build WORKDIR /app COPY . . ARG VITE_API_URL ENV VITE_API_URL=$VITE_API_URL RUN npm ci && npm run build ``` ``` docker build --build-arg VITE_API_URL=https://api.example.com . ``` ### Are you reading process.env in browser code? Read `import.meta.env` instead. `process` is a Node.js global, and a Vite app doesn't have it in the browser, so `process.env.VITE_API_URL` usually fails with `ReferenceError: process is not defined`. This one is common in code copied from Create React App or webpack projects, and in AI-generated code. ### Why is the value undefined in vite.config? Because Vite reads your `.env` files after it has loaded the config. The Vite docs say those files are “not automatically injected into `process.env` while your `vite.config.*` is running.” Load them yourself with `loadEnv`: ``` import { defineConfig, loadEnv } from 'vite' export default defineConfig(({ mode }) => { // '' loads every variable, whatever its prefix const env = loadEnv(mode, process.cwd(), '') return { define: { __APP_ENV__: JSON.stringify(env.APP_ENV) }, } }) ``` ### Why does my number or boolean act like a string? Every value in `import.meta.env` is a string, including `123` and `true`. Convert it where you read it: ``` const port = Number(import.meta.env.VITE_PORT) const debug = import.meta.env.VITE_DEBUG === "true" ``` ### How to fix it on EthioDeploy - Open the project, go to Environment Variables and add the variable with its `VITE_` name. - Leave “Runtime only” off. Runtime-only variables are kept out of the build, so a `VITE_` value marked that way is always undefined in the browser. - Redeploy. A changed value takes effect on the next build. Apps exported from Lovable and Bolt hit this often, because their Supabase keys sit in a `.env` file that isn't in the repo. See [Deploy a Lovable app](https://ethiodeploy.com/deploy/lovable) and [Environment variables](https://ethiodeploy.com/docs/env-vars). ### Questions **Can I change a Vite env variable without rebuilding?** No. The value is written into the built JavaScript, so a change needs a new vite build. If you need settings that change without a rebuild, serve them from your backend or from a config.json file the app fetches when it starts. **Is it safe to put an API key in a VITE_ variable?** No. VITE_ values are bundled into the JavaScript your visitors download, so anyone can read them. Keep secret keys on a server and call that server from the app. Public keys that are meant for the browser, like a Supabase publishable key, are fine. **Does vite build read .env.local?** Yes, .env.local is loaded in every mode. But it is normally listed in .gitignore, so it exists on your laptop and not on the build server. Set the same values in your host's environment settings. **Which .env file does vite build use?** vite build runs in production mode, so it loads .env, .env.local, .env.production and .env.production.local. Mode-specific files win over generic ones, and variables already set in the environment win over all of them. **Why is my env variable undefined in vite.config.ts?** Vite loads .env files after the config runs, so they are not in process.env yet. Call loadEnv(mode, process.cwd(), '') inside defineConfig to read them. Sources: - [Vite: Env variables and modes](https://vite.dev/guide/env-and-mode) - [Vite: envPrefix option](https://vite.dev/config/shared-options#envprefix) - [Vite: Using environment variables in config](https://vite.dev/config/#using-environment-variables-in-config) ## 502 Bad Gateway after deploy: bind to 0.0.0.0 and $PORT URL: https://ethiodeploy.com/blog/errors/502-bad-gateway-bind-0-0-0-0-port By: Brook Solomon Updated: 2026-10-29 A 502 right after a deploy almost always means the platform's proxy can't reach your app. The app is listening on 127.0.0.1 instead of 0.0.0.0, or on a hard-coded port instead of the one in the PORT environment variable. Bind to 0.0.0.0 and read PORT, and the 502 goes away. ### Why does my app return 502 after deploy when it works locally? Because the proxy in front of your app can't connect to it. On your laptop you open `localhost:5000` from the same machine, so an app listening on `127.0.0.1` works fine. On a hosting platform, requests arrive from a proxy in another container or machine. An app bound to `127.0.0.1` only accepts connections from itself, so the proxy gets nothing back and returns 502. The second cause is the port. Platforms tell your app which port to use through the `PORT` environment variable. An app that always listens on 5000 or 8000 is running, just not where the proxy is looking. Every major platform documents the same rule: - Render: “Every Render web service must bind to a port on host `0.0.0.0` to serve HTTP requests.” Its default `PORT` is 10000. - Railway says the most common reason for “Application failed to respond” is that the app “is not listening on the correct host or port.” - Heroku assigns the port as `PORT` and kills a web process that hasn't bound to it within 60 seconds (error R10, which then shows up as H10 App crashed). ### What host and port does my framework use by default? Most development servers default to `127.0.0.1` on purpose, so your work-in-progress isn't reachable from the network. That default is what breaks in production. | Server | Default host | Default port | Reads PORT? | | --- | --- | --- | --- | | Node `server.listen`, Express | All interfaces | None, you pass it | Only if your code does | | Next.js `next start` | `0.0.0.0` | 3000 | Yes | | Vite `vite preview` | `localhost` | 4173 | No | | uvicorn | `127.0.0.1` | 8000 | No | | gunicorn | `127.0.0.1`, or `0.0.0.0` when PORT is set | 8000 | Yes | | Flask `flask run` | `127.0.0.1` | 5000 | No | | Django `runserver` | `127.0.0.1` | 8000 | No | | Go `ListenAndServe(":8080")` | All interfaces | The one in the string | Only if your code does | | Rails `rails server` | `localhost` in development, `0.0.0.0` otherwise | 3000 | Yes | ### How do I fix it in my framework? Read the port from the environment and listen on all interfaces. The start commands: ``` # Node / Express app.listen(process.env.PORT || 3000, "0.0.0.0") # FastAPI, Starlette (uvicorn) uvicorn main:app --host 0.0.0.0 --port $PORT # Flask, Django (gunicorn binds 0.0.0.0:$PORT on its own when PORT is set) gunicorn app:app gunicorn myproject.wsgi # Vite static preview vite preview --host 0.0.0.0 --port $PORT --strictPort # Go http.ListenAndServe(":"+os.Getenv("PORT"), nil) ``` Three gotchas that look like a 502 but aren't a missing host: - Next.js can't take `PORT` from a `.env` file. Its docs say the HTTP server boots before `.env` is read. Set `PORT` in the platform's environment instead. - Vite moves to the next free port when the one you asked for is busy. Add `--strictPort` so it fails loudly instead of listening somewhere else. - Django's `runserver` and Flask's `flask run` are development servers. Their docs say not to use them in production. Run gunicorn instead. ### What happens on EthioDeploy? EthioDeploy sets `PORT=3000` and `HOSTNAME=0.0.0.0` for every web service, and overrides any value you set for those two. Then every deploy waits for a health check: a GET to `/` that has to return a 2xx or 3xx status within 30 seconds. If the app never answers, the deploy fails and your previous version keeps serving traffic, so a wrong bind never takes the site down. The build log says what went wrong: ``` Health check timed out after 30000ms. The app never answered on port $PORT. Make sure it listens on 0.0.0.0 and reads the PORT env var. ``` If your home page needs a login or returns 404 by design, set a Health Check Path such as `/healthz` in Settings. The framework guides list the exact start command for each stack: [Express](https://ethiodeploy.com/deploy/express), [FastAPI](https://ethiodeploy.com/deploy/fastapi), [Django](https://ethiodeploy.com/deploy/django), [Flask](https://ethiodeploy.com/deploy/flask) and [Go](https://ethiodeploy.com/deploy/go). ### Questions **What does binding to 0.0.0.0 mean?** It tells the server to accept connections on every network interface of the machine. 127.0.0.1 only accepts connections from the same machine, so a proxy running elsewhere can't reach it. **Is listening on 0.0.0.0 a security risk?** Not inside a container on a hosting platform. The container only receives traffic the platform's proxy sends it. On your own laptop or a server with a public IP, 0.0.0.0 does expose the app to the network, which is why development servers default to 127.0.0.1. **Why does my app work locally but not after deploy?** Locally you connect from the same machine, so 127.0.0.1 and a fixed port work. After deploy the platform's proxy connects from outside the app and expects the port in the PORT environment variable. **What port should my app listen on?** The one in the PORT environment variable. Each platform picks its own: Render's default is 10000, Heroku assigns one per dyno, and EthioDeploy sets 3000. Reading PORT works on all of them. **What is Heroku error H10 App crashed?** Heroku shows H10 when a web dyno crashed or timed out while booting. A common cause is error R10: the process didn't bind to $PORT within 60 seconds of starting. Sources: - [Render: Web services, port binding](https://render.com/docs/web-services) - [Railway: Application failed to respond](https://docs.railway.com/reference/errors/application-failed-to-respond) - [Heroku: Error codes (R10, H10)](https://devcenter.heroku.com/articles/error-codes) - [Node.js: server.listen](https://nodejs.org/api/net.html) - [Next.js: CLI](https://nextjs.org/docs/app/api-reference/cli/next) - [Vite: Preview options](https://vite.dev/config/preview-options) - [Uvicorn: Settings](https://uvicorn.dev/settings/) - [Gunicorn: bind setting](https://gunicorn.org/reference/settings/) - [Flask: API, Flask.run](https://flask.palletsprojects.com/en/stable/api/) - [Django: runserver](https://docs.djangoproject.com/en/stable/ref/django-admin/) - [Go: net/http](https://pkg.go.dev/net/http) - [Rails: Command line guide](https://guides.rubyonrails.org/command_line.html) ## Render free tier cold starts: why your app sleeps and how to avoid it URL: https://ethiodeploy.com/blog/hosting/render-free-tier-cold-starts By: Brook Solomon Updated: 2026-10-29 Render spins a free web service down after 15 minutes without inbound traffic, and the next request waits about a minute while it starts again. That is the cold start. You avoid it by moving to a paid instance (Starter is $7 a month), by keeping the service busy within Render's 750 free hours, or by hosting on a plan that doesn't sleep. ### Why does my Render app take a minute to load? Because it was asleep. Render's docs say: “Render spins down a Free web service that goes 15 minutes without receiving any inbound traffic.” The next HTTP request or new WebSocket connection starts it again, and “this process takes about one minute.” Browsers see a loading page while that happens. Inbound traffic means HTTP requests and WebSocket messages on existing connections. A webhook from Stripe or a Telegram bot waiting for messages also hits that cold start if nothing else has called the service in the last 15 minutes. ### What else does the Render free tier limit? The sleep is the limit people notice first. These are the others, from Render's free tier docs: - **750 free instance hours per workspace per month.** A free service uses hours while it runs. When they run out, Render suspends all your free services until the next month. - **Free Postgres expires after 30 days.** You get a 14-day grace period to upgrade, then the database and its data are deleted. Storage is 1 GB, and you can have one free database per workspace. - **Free Key Value (Redis) doesn't persist to disk.** A restart wipes its data. - **Web services only.** Background workers, cron jobs and private services have no free instance type. - **512 MB of RAM and 0.1 CPU,** no persistent disks, and no outbound SMTP on ports 25, 465 or 587. Render is clear about the purpose: free instances are for testing and hobby projects, and its docs say “Do not use them for production applications.” ### How do I stop Render from spinning down my app? Four options, from most to least reliable: - **Upgrade the service.** Spinning down is a free-instance behavior. Render's cheapest paid instance, Starter, is $7 a month with 512 MB of RAM and 0.5 CPU. - **Ping it on a schedule.** An uptime monitor that requests the service every 10 minutes counts as inbound traffic, so it never reaches 15 idle minutes. The catch is the hours: a service that never sleeps runs about 720 to 744 hours a month, which fits inside 750 for one service and not for two. Render's free tier docs don't mention pings, so read its acceptable use policy and decide for yourself. - **Make the cold start shorter.** A smaller app boots faster. Avoid running migrations or downloading large models on every start, and load heavy dependencies only when a route needs them. - **Host it on a plan that stays on.** See the comparison below. ### How does EthioDeploy's free plan compare? EthioDeploy's free plan also sleeps, after 30 minutes without traffic instead of 15. A visitor who wakes it waits while your app boots (we hold the request for up to 30 seconds), and browsers see an auto-refreshing “waking up” page if the boot takes longer. API clients and webhooks get a 503 in that case, so neither free tier is a good home for a webhook that must answer on the first try. | | Render free | EthioDeploy free | | --- | --- | --- | | Sleeps after | 15 minutes | 30 minutes | | Memory | 512 MB | 256 MB | | Free Postgres | 1 GB, deleted after 30 days plus 14 days grace | 512 MB, no expiry | | Projects | Up to 25 services, sharing 750 instance hours | 1 project | | Custom domain on free | Yes | No | | Cheapest always-on | Starter, $7/month, 512 MB | Pro, $10/month or 1,399 ETB, 1 GB | Render gives more on the free tier itself: more memory, custom domains and more than one service. Ours keeps your database. If you only need one small always-on service, Render's Starter is cheaper. Pro costs more, but includes 1 GB and room for several small apps, and you can pay it in Birr. Full details are on [EthioDeploy vs Render](https://ethiodeploy.com/compare/render). ### Questions **How long does the Render free tier take to wake up?** Render's docs say spinning a free web service back up takes about one minute. Browsers see a loading page in the meantime. **Does a keep-alive ping stop Render from sleeping?** A request every 10 minutes counts as inbound traffic, so the service never sits idle for 15 minutes. It still uses free instance hours the whole time, and 750 hours a month covers only one always-awake service. **What happens when I run out of Render's 750 free hours?** Render suspends all of your free web services until the start of the next month, when the hours reset. Unused hours don't roll over. **Does the Render free Postgres database get deleted?** Yes. A free Render Postgres database expires 30 days after you create it. You then have 14 days to upgrade it before Render deletes it with all its data. **What is the cheapest Render plan that doesn't sleep?** The Starter instance at $7 a month, with 512 MB of RAM and 0.5 CPU. Only free instances spin down when idle. Sources: - [Render: Deploy for free](https://render.com/docs/free) - [Render: Pricing](https://render.com/pricing) - [Render: Acceptable use policy](https://render.com/acceptable-use) ## How to pay for Vercel, AWS and hosting from Ethiopia URL: https://ethiodeploy.com/blog/ethiopia/pay-for-vercel-aws-hosting-from-ethiopia By: Brook Solomon Updated: 2026-10-29 Vercel, AWS, Railway and most other hosts only accept international cards billed in US dollars. From Ethiopia that means a bank card backed by a foreign-currency account, such as Dashen Bank's Mastercard prepaid card, or a card held by someone abroad. The telebirr Virtual Visa card can't make payments yet. The other route is a host that takes Birr. ### Why is it hard to pay for Vercel or AWS from Ethiopia? Because these platforms charge a card in US dollars, and a card can only pay a foreign merchant if there are foreign-currency funds behind it. Ethiopian rules on who can hold foreign currency decide who can get such a card. That changed in February 2026. A National Bank of Ethiopia [notice](https://nbe.gov.et/nbe_news/public-notice-notice-on-relaxation-of-foreign-exchange-directives-fxd-01-2024/) allows banks to issue “internationally recognized cards for all foreign exchange account holders that will be used by the customers to make outbound retail payments including e-commerce as long as the account holders have sufficient FX in their accounts.” So the question is whether you can hold foreign currency. The NBE's [FX FAQ](https://nbe.gov.et/fx/) lets residents open a foreign-currency account if they earn salary, rental or other income in foreign currency, or receive remittances from abroad and keep them in foreign currency. The minimum deposit is USD 100. ### Which Ethiopian cards work for online payments? The one with published terms is [Dashen Bank's Mastercard multi-currency international prepaid card](https://dashenbanksc.com/dashen-mastercard-multi-currency-international-prepaid-card/): - It holds US dollars, and every transaction is in USD. - Dashen's FAQ says you can use it for online purchases while in Ethiopia. Each online payment needs a one-time password. - To get one you need a Dashen account plus a passport, visa and flight ticket, or a foreign-currency account if you aren't travelling. - The plastic card costs ETB 500 to issue, with no annual fee, and expires after two years. Dashen also launched a separate Dashen Virtual Card for international payments in April 2026, through its Super App. Awash Bank announced Mastercard prepaid cards for online payments too. Neither has published limits or fees we could check, so ask the bank before you apply. One catch: a prepaid card is not always accepted. Vercel's billing docs say it takes “credit/debit card only” and list prepaid cards and some virtual cards as invalid. Test a small charge before you depend on it for a production bill. ### Can I pay with the telebirr Virtual Visa card? Not yet. The card gives you a 16-digit Visa number for receiving money from abroad. ethio telecom's [FAQ](https://www.ethiotelecom.et/virtual-visa-card/) answers the question directly: “Can I use my Virtual VISA for payments? No, at present, only remittances are possible.” It adds that cross-border payments will come through a telebirr USD wallet once the NBE rules are reviewed. Until then it can't pay a hosting bill. ### What does each hosting platform accept? | Platform | Accepts | Watch out for | | --- | --- | --- | | Vercel (Pro) | Credit or debit cards: Visa, Mastercard, Amex and others, charged in USD | Prepaid and some virtual cards are invalid | | AWS | For Africa outside South Africa, billed by AWS EMEA SARL: Visa, Mastercard or Amex credit cards, or bank transfer | A $1 test charge at sign-up | | Railway | Credit cards; invoices only on Enterprise | No other method listed | | EthioDeploy | Card in USD, or bank transfer in Birr through telebirr (CBE transfers are paused for now) | Birr payments cover 30 days and don't renew on their own | Many developers in Ethiopia use a card held by a relative or colleague abroad. It works, but the account and the bill then depend on someone else's card. ### How do I pay for hosting in Birr? On EthioDeploy you pay by bank transfer in Birr, and no card is involved. Pro is 1,399 ETB a month and Business is 4,999 ETB. - Pick the plan and choose Birr on the pricing page or in Settings → Billing. - Transfer the amount from telebirr. CBE transfers are paused for now. - Enter the bank and your reference number. We check it against the bank, usually within seconds. Each payment covers 30 days. A Birr plan doesn't renew on its own: pay again before the date in Settings → Billing, and you get a 2-day grace period after it. Domains can be bought in Birr too. See [Billing](https://ethiodeploy.com/docs/billing) and [Hosting in Ethiopia](https://ethiodeploy.com/ethiopia-hosting). ### Questions **Can I use an Ethiopian bank card to pay for Vercel?** Only an international card backed by foreign currency, and Vercel lists prepaid cards as invalid. A debit or credit card from a foreign-currency account has the best chance. Test a small charge first. **Does the telebirr Virtual Visa card work for online payments?** No, not yet. ethio telecom's FAQ says that at present only remittances are possible. Payments are planned through a future telebirr USD wallet. **Who can get a foreign-currency account in Ethiopia?** Residents who earn salary, rental or other income in foreign currency, or who receive remittances from abroad and keep them in foreign currency. The National Bank of Ethiopia sets a minimum deposit of USD 100. **Can I pay for AWS from Ethiopia?** AWS bills customers in Africa outside South Africa through AWS EMEA SARL, which accepts Visa, Mastercard and Amex credit cards or bank transfer. You need one of those cards, and AWS places a $1 test charge at sign-up. **Is there hosting I can pay for in Birr?** Yes. EthioDeploy takes bank transfers in Birr from telebirr (CBE transfers are paused for now): Pro is 1,399 ETB a month and Business 4,999 ETB. There's also a free plan that needs no payment. Sources: - [NBE: Notice on relaxation of FX directive FXD/01/2024 (Feb 2026)](https://nbe.gov.et/nbe_news/public-notice-notice-on-relaxation-of-foreign-exchange-directives-fxd-01-2024/) - [NBE: Foreign exchange FAQ](https://nbe.gov.et/fx/) - [Dashen Bank: Mastercard international prepaid card](https://dashenbanksc.com/dashen-mastercard-multi-currency-international-prepaid-card/) - [Dashen Bank: FAQ](https://dashenbanksc.com/frequently-asked-questions/) - [Dashen Bank: Terms and tariffs](https://dashenbanksc.com/terms-and-tariffs/) - [ethio telecom: telebirr Virtual VISA card](https://www.ethiotelecom.et/virtual-visa-card/) - [Vercel: Pro plan billing](https://vercel.com/docs/plans/pro-plan/billing) - [AWS: Finding the seller of record](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/finding-the-seller-of-record.html) - [AWS Europe: payment methods](https://aws.amazon.com/legal/aws-emea/) - [Railway: Pricing FAQs](https://docs.railway.com/pricing/faqs)