App Hosting
An app is a program of yours that we run from a GitHub repository and keep running: a web dashboard, an API, a webhook receiver, a status page, a queue worker, a Discord bot. You point us at the repository, we install it, build it if it needs building, and start it. Every push to your branch deploys the new version.
- Languages: Node.js, Python, Bun, Deno, Go, Java, PHP, Ruby, Rust, C# on .NET and static sites, detected from your repository (the full list is below). Every tier runs every runtime.
- Free plan: your first app is free, 256 MB of memory, no card needed. See The free plan.
- Paid plans: $2/mo or $20/yr and up per app, for more memory and more than one app.
- Regions: pick one when you create a paid app, and move it later if you want.
Apps live under Apps in your control panel; Discord, Telegram and WhatsApp bots have their own Bots page. A bot is an app with a token, so everything on this page applies to bots too. The bot-specific parts (tokens, intents, the premade bots) are on the Discord Bot Hosting page.
Create an app from a repository
Push your code to a GitHub repository first, public or private. Then, in the panel, go to Apps → Host an app. The dialog asks three things.
Step 1: what you want to host. Pick one:
| Choice | For |
|---|---|
| Discord bot | A bot that logs in to Discord with a token. |
| Telegram bot | A bot with a token from BotFather. |
| WhatsApp bot | A bot on Meta’s official Cloud API, with a web address for the webhook. |
| Web app or API | Anything that answers on a port. Gets a web address. |
| Static site | Plain HTML, or a site built with Astro, Vite and friends. |
| Self-hosted app | Uptime Kuma, n8n, Vaultwarden, Gitea or Lavalink from its official image. See the catalog. |
| Something else | A worker, a script, a game bot: any repository we can clone, install and keep running. |
Step 2: a template or your own repository. Each answer offers a few ready-made apps that need no code, and Your own repository for your own code. The example apps are the templates.
Step 3: the details.
- Name a label for the app, only you see it.
- Region on a paid plan, the region the app runs in. There is no region field on the free plan, because free apps run where there is room for them.
- GitHub repository for example
https://github.com/you/my-app. For a private one, connect GitHub when we ask, or paste a token further down. - Branch optional. Leave it blank to track whatever the repository calls its default branch.
- Start command and Build command, both optional. Leave them blank and we work them out from your repository.
- Language auto-detect unless you set it.
- Plan free, or one of the paid plans with its price.
- Environment variables, one
KEY=VALUEper line. Keys and tokens belong here, never in the repository. They are encrypted at rest.
Click Deploy. We clone the code, install the dependencies and start the app.
A Discord or Telegram bot asks for its token in this step as well, and we check the token with Discord or Telegram before the app is created, so a typo is caught straight away rather than turning into a crash loop.
Your first app is free once your email is verified. For more apps, or more memory, add App Hosting to your subscription in the plan builder.
Prefer the terminal? npx fadehost apps create --repo https://github.com/you/your-app creates the same app, and npx fadehost apps deploy <app> deploys the latest commit without opening the panel. See The FadeHost CLI.
The app page and its tabs
Once the app exists, its page is a row of tabs.
| Tab | What is on it |
|---|---|
| Overview | Start, stop and restart, the deploy button, the live console, the crash doctor and Activity. |
| Deploys | Deploy history and rollback, the image build log, and auto-deploy from GitHub. |
| Settings | The repository, branch, start and build commands, environment variables, the web address, the public port, the database, alerts, team sharing, the region and deleting the app. |
| Firewall | What the app may reach on the internet. |
| Schedules | Scheduled commands. |
| Backups | Backups and restoring one. |
| Plan and usage | The plan, the meters and the traffic chart. |
An app run from a catalog image has no Deploys tab, because there are no commits to deploy. Team members only see the tabs their access level covers.
Runtimes
We read your repository and pick the runtime. You can override it under Language, and set your own Start command, at any time.
| Your repository contains | Runtime | Installed with | Started with |
|---|---|---|---|
package.json | Node.js | npm, pnpm, yarn or bun, whichever your lockfile or packageManager pin names | npm start, or your entry file if there is no start script |
requirements.txt, pyproject.toml or Pipfile | Python | pip, in a virtual environment we keep for you | python bot.py or python main.py |
bun.lock, bun.lockb or a packageManager pin naming bun | Bun | bun install | your start script with bun, or the entry file |
deno.json or deno.jsonc | Deno | modules cached under /data | deno task start, or deno run -A on the entry file |
go.mod | Go | go build of your main package, modules and builds cached under /data | the built binary |
pom.xml, build.gradle or build.gradle.kts, or a .jar at the root | Java 21 | mvn package or gradlew build (tests skipped) | java -jar on the built or committed jar |
composer.json or index.php | PHP | composer install without dev packages | PHP’s built-in server on PORT, serving public/ when it exists, else the root |
Gemfile | Ruby 3.4 | bundle install into /data | your start command, or rackup when there is a config.ru |
Cargo.toml | Rust 1.99 | cargo build --release, with the crates and everything it compiles cached under /data | the binary the build wrote |
a .csproj, .fsproj, .sln or global.json | C# or F# on .NET 10 | dotnet restore, then dotnet publish -c Release into /data | dotnet on the published assembly |
index.html, or a build script producing dist, build, public, out or _site | Static site | your build script when there is one | a small static server on PORT (set SPA=1 for single-page apps) |
Set a Start command when your entry point is somewhere else, for example node dist/server.js or python -m myapp.
Listening on a port
Every app gets a PORT environment variable, whether or not it has a web address. The default is 8080.
Your web server has to listen on that port and on 0.0.0.0. A server bound to 127.0.0.1 only answers itself, so nothing outside the container, including your web address, can reach it.
// Node.js (Express)
app.listen(process.env.PORT || 8080, "0.0.0.0")
# Python (FastAPI with uvicorn)
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
An app with no web server, a Discord bot or a worker, can ignore PORT entirely.
Self-hosted apps from the catalog
Some apps are not code you write but software you run: Uptime Kuma, n8n, Vaultwarden, Gitea, a Lavalink node for a music bot. The create dialog’s Self-hosted app choice lists them. Pick one, name it, deploy: the app runs from the project’s own official image, inside the same sandbox as any app, with its data under /data (backed up like any app), a web address when it has one, and the environment that makes it work set for you.
| App | What it is | Smallest plan | Address |
|---|---|---|---|
| Uptime Kuma | Uptime monitor and status pages | Free | Yes |
| Vaultwarden | A Bitwarden-compatible password server | Free | Yes |
| n8n | Workflow automation | Starter | Yes |
| Gitea | A Git server with pull requests and Actions | Starter | Yes |
| Lavalink | The audio node music bots use | Starter | With the public port add-on |
There is no repository, so there is no build, no deploy history and no auto-deploy on push. Redeploy pulls the latest image and restarts the app on the same data; Restart keeps the image. Settings are environment variables on the app’s Environment page, as documented by each project. A Lavalink node shows its private name on its page; a bot hosted here reaches it at that name on port 2333.
Environment variables
Tokens, API keys, database URLs and any other configuration live in environment variables, not in your repository. They are injected into the app at runtime.
- On create: fill in the Environment variables box, one
KEY=valueper line. - Later: open the app → Settings → Environment variables. The variables are a table, values masked. Add rows, remove rows, then Save once and the app restarts with the new values.
- Names must start with a letter or an underscore, for example
DISCORD_TOKENorDATABASE_URL.
Some variables we set for you: PORT always, APP_URL while a web address is enabled, and the database credentials when you create a database.
Build step
Every deploy runs three things in order: the install, the build command, then the start command.
If your app has to be compiled before it runs (TypeScript, esbuild, tsup, a bundled dist/ folder), you do not have to commit the compiled files. Leave the build command blank and we detect the build:
- When the file your start command runs is not in the repository (say
node dist/index.jswithdistin.gitignore) and yourpackage.jsonhas abuildscript, we install yourdevDependenciesand run that script before starting the app. - In a workspace (pnpm, npm or Yarn workspaces), we build only the package the app starts and the workspace packages it depends on, so a sibling web app in the same repository never has to build.
- A committed entry file is never built over. An app whose
index.jsis in git starts as before, even if the repository has abuildscript.
To build differently, set a Build command under Build & start on the app’s Settings tab. It runs on every deploy, exactly as written:
pnpm --filter @workspace/api run build
A failed build prints its error in the live console, and every restart repeats one line naming the file the build never produced. Fix it in your repository, or fix the build command, then press Redeploy.
Dockerfile and prebuilt images
A paid app can be built into an image instead of installed at start. Set Build mode on the app’s Settings tab:
- Dockerfile: we build the Dockerfile in your repository on a build box, push the image to our private registry and run that image on the node. Anything a Dockerfile can express works: system packages, a different base image, multi-stage builds, a compiled binary.
- Railpack: no Dockerfile needed. The build box reads the repository, picks the runtime and produces the image for you. Same result as the default runtime install, but built once per commit and started in seconds on every restart.
Image builds are for paid tiers, because a prebuilt image skips the source scan the free tier relies on. A build that fails prints its log on the app’s page, and the previous image keeps running until a build succeeds.
Web address
Every app can answer at https://<name>.fadehost.app with HTTPS. Use it for a dashboard, OAuth2 redirect URLs, incoming webhooks or a status page. Open the app → Web address, pick a name, and it is live within seconds.
It comes with the plan on every tier, free or paid. On the free tier the address sleeps when nobody is using it and wakes on the next request, which takes a few seconds; the switch that keeps it awake is locked to paid tiers, where the address stays up at no extra cost.
The full section, including what your app has to do and what is not allowed, is under Web address and applies to every app, bot or not.
A few rules about the name itself:
- 3 to 32 characters, lowercase letters, numbers and dashes.
- One rename a day, with ten minutes after a change to fix a typo for free.
- Switching the address off takes it down at once and holds the name for a day.
Cookies: set them without a Domain attribute and the browser keeps them to your address, which is what every framework does by default. fadehost.app itself is shared by every app we host, so a cookie set with Domain=fadehost.app is rewritten on the way out to apply to your address only. A cookie for your own domain is left as you set it.
Storage: what survives a deploy
Your code is checked out at /data/app, which is also the app’s working directory. Every deploy resets it to match your repository, so a file your app wrote next to its code can be gone after the next push.
Anything under /data is a volume that survives deploys and restarts. Write runtime data there, with an absolute path:
const file = "/data/storage/state.json"
For anything more than a file or two, use a database. Every paid app includes a one-click MySQL database (open the app → Settings → Database → Create database) and the credentials are injected into the environment. PostgreSQL, MongoDB and Redis are available as managed databases on the same private network.
Plans and limits
A paid plan is a number of app slots, and every slot is the same size: 1 GB of memory and 1 vCPU, always on. What the plans differ in is how many apps one unit carries and how much disk each one gets.
| Free | Starter | Standard | Pro | |
|---|---|---|---|---|
| Price / month | $0 | $2 | $3 | $6 |
| Price / year | free | $20 | $30 | $60 |
| Apps included | 1 | 1 | 2 | 4 |
| RAM per app | 256 MB | 1 GB | 1 GB | 1 GB |
| vCPU per app | 0.25 | 1 | 1 | 1 |
| Extra memory per app | +$1/mo per GB, up to 3 GB | +$1/mo per GB, up to 3 GB | +$1/mo per GB, up to 3 GB | |
Disk under /data | 3 GB | 5 GB | 10 GB | 20 GB |
| Auto-deploy on push | Yes | Yes | Yes | Yes |
| Live console, crash doctor, deploy history and rollback | Yes | Yes | Yes | Yes |
| Scheduled commands, plan and usage meters | Yes | Yes | Yes | Yes |
| Daily traffic limit | 1 GB | none | none | none |
| 1-click MySQL database | Yes | Yes | Yes | |
Web address (https://name.fadehost.app) | Included, sleeps when idle | Included, always on | Included, always on | Included, always on |
| You pick the region | Yes | Yes | Yes | |
| Outbound traffic | A fixed list | Everything, or your own list | Everything, or your own list | Everything, or your own list |
| Mail through a provider | Yes | Yes | Yes | |
| Daily backups, kept 7 days | +$1/mo | +$1/mo | +$1/mo | +$1/mo |
| Public port, TCP and UDP | +$2/mo | +$2/mo | +$2/mo | |
| Starter bundle, 3 apps | $5/mo or $50/yr | |||
| Dockerfile and Railpack image builds | Yes | Yes | Yes | |
| Apps per account | 1 (verified email) | As many as your plans carry | As many as your plans carry | As many as your plans carry |
Standard is your app and your test app, both always on, for $3 a month. Hold more than one unit of a plan for more apps again: two Standard units are four apps.
When one app needs more than a gigabyte, add memory to that app alone from its Plan and usage tab: $1 a month per gigabyte, up to 3 GB, so an app can run on 4 GB. Lower it again and the gigabytes go back to your plan for another app at no extra cost. Plan and usage has the details.
Every app runs in a hardened container: non-root, a read-only filesystem apart from its own storage, and hard memory and CPU limits. It restarts on its own if it falls over, with no sleep timer and nothing to renew.
Deploy history and rollback
The app’s page lists every deploy with its commit and when it went live. Roll back on an older one pins the app to that commit and redeploys it; pushes to the branch keep arriving in the history but do not go live until you Unpin. Use it when a push breaks the app and you want the last good build back while you fix the code.
Scheduled commands
The Schedules tab runs a shell command inside the app’s container on a cron schedule, up to ten per app: a nightly cleanup, a cache warm-up, a report script. Each run keeps its exit code and the last lines of output, and a command is stopped after 60 seconds.
Backups
The Backups tab turns on a daily backup of everything under /data for $1 a month per app. Backups are stored off the node, kept for seven days, and restored from the same list in one click: the app stops, the data comes back, and it starts again if it was running. Take one by hand before a risky change with Back up now.
Public port
An app that is not a website, such as a Lavalink node a bot on another host connects to, a game or voice server, or anything speaking its own protocol, can have its own port opened to the internet. Public port, on the app’s Settings tab, switches it on for $2 a month per app, on paid plans: the node publishes the app’s port on a fixed port of its own public address, TCP and UDP, and the app’s page shows the address to hand out. The app restarts once when the port is switched on or off, and it stays awake while the port is on. Nothing sits in front of the port, so the app itself must decide who gets in: set a password where the software has one.
The Starter bundle, under Billing, is three Starter apps for $5 a month, for a bot, its Lavalink node and a dashboard, say, or three small apps.
Plan and usage
The Plan and usage tab names the plan the app is on and shows four meters: memory, CPU, disk and traffic. Readings are taken every five minutes, and a chart under them draws the app’s traffic by day for the last 30 days. An app sitting at its memory limit is the first thing to check when the crash doctor reports an out-of-memory restart, and the next plan up is one button on the same tab. The full tour is on Plan and usage.
Example apps
Ready-made apps you can deploy from the panel (Apps, Host an app, then a template for what you chose) by filling in a field or two. Each is a public FadeHost repository you can read, fork and change.
| App | What it does | Runtime |
|---|---|---|
| Server status page | A public page of your servers: online or sleeping, who is on, the address to join. | Node.js |
| Whitelist applications | A form players fill in, an admin page for staff, the whitelist command run on approval, an optional Discord notice. | Node.js |
| Bot with a dashboard | A welcome and log bot whose settings live on a web page behind Discord login. | Node.js |
| Activity digest | Who played and for how long, summed up once a day to a Discord webhook, with today’s page. | Python |
| Server guide site | Rules, how to join and the mod list as a static site edited in Markdown. | Static site |
| Express API starter, Flask API starter | Bare services wired to the port and the persistent folder, ready to build on. | Node.js, Python |
| Telegram bot | A grammY bot with a few commands, polling by default or webhooks behind the web address. | Node.js |
| WhatsApp bot | An Express webhook receiver for the official WhatsApp Cloud API, with signature checks and a reply helper. | Node.js |
The status page, the whitelist app and the digest talk to the FadeHost API for you: the panel creates their access token when you deploy them, so there is nothing to paste. Each README lists the environment variables.
What is not allowed
App hosting runs web apps and workers. It is not for:
- phishing or credential-harvesting pages,
- proxies and tunnels of any kind, which get the app stopped,
- breaking the terms of the platform a bot connects to: self-bots, bulk adds or scraping of Telegram groups and channels, messaging people who did not ask (a Telegram user session for your own account is fine),
- adult or illegal content,
- bulk file or media delivery through the web address,
- raw TCP or UDP services, outbound mail, or workloads that need privileged containers or a GPU,
- traffic far beyond fair use.
The full rules are in the terms of service. Report an abusive app to support@fadehost.com.
Troubleshooting
The address says the app is not listening
The web address is on, but visitors get a page saying the app is not answering on port N, and the Web address card says the same.
- Check the app is actually running. The live console shows whether it started.
- Check it listens on
0.0.0.0, not127.0.0.1orlocalhost. - Check the port. Read
PORTfrom the environment, or set the card’s port field to the one your app hard-codes. The change takes effect at once, no restart needed. - Some frameworks need telling:
next start -H 0.0.0.0 -p $PORT,flask run --host=0.0.0.0 --port=$PORT.
The app restarts over and over
Open the app and read the crash doctor card. It reads the logs and names the cause, usually within a minute. The usual reasons:
- a missing environment variable, so the app throws on start,
- a dependency that is installed on your machine but missing from
package.jsonorrequirements.txt, - a build that never produced the file the start command runs (the console names that file on every restart),
- a script that finishes instead of staying up. An app that exits cleanly looks like a crash to us, because nothing is left running.
Fix it in your repository or the environment, then press Redeploy. The live console keeps everything the app printed, so you can see what it said last.
Need help?
Join our Discord and we will get your app running.