How it works

Appi.sh is zero-config hosting for your apps. Build your app once, ship it with a single command, and it goes live — no servers to configure, no infrastructure to manage.

We use Docker as the delivery format — the standard way to package modern applications so they run the same way everywhere. When you're ready, you run the docker push command generated for you on the Dashboard. As soon as your app arrives, we detect it, start hosting it, and give you a public URL at https://<your-slot-id>.appi.sh.

Never touched Docker before? You don't need to be an expert. If you're coding with an AI assistant (Cursor, Claude Code, Copilot, ChatGPT, Aider, etc.), ask it two things — it will walk you through everything:

  1. How to install Docker on your computer.
  2. To generate a Dockerfile for your app — just tell it what framework or language you're using.

That's it. After docker push finishes, give it a few seconds — the Dashboard will flip from Waiting for deploy to Active.

Active means "we've received your image and are working on it" — not yet "your app is reachable". Next to the status you'll see a small info icon — hover it to see the deployment sub-status. It walks through PushingDeployingAwaiting Health CheckHealthy. Once it reads Healthy, your app is actually live at https://<your-slot-id>.appi.sh.

FAQ

My app listens on a different port — what do I do?

Add a port_<number> tag to your image. Examples:

  • port_80 (default)
  • port_3000 — Node.js, Next.js
  • port_8080 — Spring, Tomcat

Full example build command:

docker buildx build --platform linux/amd64 ^
  -t push.to.appi.sh/<slot-id>/app:port_3000 ^
  --push .
How do I pass environment variables?

Add a tag in the format env_<KEY>___<VALUE> — one tag per variable.

Heads up on the separator: between the key and the value there are three underscores (___), not one. This is because your key itself can contain single underscores (e.g. DB_HOST), so we need a distinct separator.

Allowed characters in both key and value follow Docker's tag rules: letters a-z A-Z, digits 0-9, underscore _, dot ., and hyphen -. Anything else — spaces, slashes, colons, @, !, etc. — won't fit directly. For those, use env64 (see below).

Examples:

  • env_NODE_ENV___production
  • env_LOG_LEVEL___debug
  • env_API_VERSION___v2.1
  • env_FEATURE_FLAGS___beta-enabled

Build command with multiple tags (one -t flag each):

docker buildx build --platform linux/amd64 ^
  -t push.to.appi.sh/<slot-id>/app:port_3000 ^
  -t push.to.appi.sh/<slot-id>/app:env_NODE_ENV___production ^
  -t push.to.appi.sh/<slot-id>/app:env_LOG_LEVEL___debug ^
  --push .

Values with special characters — use env64. If your value contains anything Docker tags don't allow (spaces, ://, @, :, ?, =, etc.), use the env64_ prefix and base64url-encode the value. We decode it before passing the variable to your app.

Format: env64_<KEY>___<BASE64URL_VALUE>

base64url is the URL-safe variant: same alphabet as base64 but + becomes -, / becomes _, and the = padding is dropped.

Quick encode commands:

# Linux / macOS
echo -n 'your value here' | base64 | tr '+/' '-_' | tr -d '='

# Python
python -c "import base64,sys; print(base64.urlsafe_b64encode(sys.argv[1].encode()).rstrip(b'=').decode())" 'your value here'

Example — setting DATABASE_URL=postgres://user:pass@host:5432/db:

-t push.to.appi.sh/<slot-id>/app:env64_DATABASE_URL___cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri

Tip: the KEY stays plain — only the VALUE gets encoded. Docker tags cap at 128 characters total, so very long values may still need an external secrets approach.

What image tags are supported?

Tags are how you configure your deployment. You can attach multiple tags to a single image — we read all of them.

Currently recognised tags:

  • port_<number> — the port your app listens on inside the container. Default is port_80.
  • env_<KEY>___<VALUE> — an environment variable passed to your app at runtime (for values with simple characters).
  • env64_<KEY>___<BASE64URL_VALUE> — same as env_, but the value is base64url-encoded so special characters (URLs, connection strings, passwords) can pass through.

Don't add tags that aren't port, env, env64, or part of the command line we give you on the Dashboard. They will be ignored — so there's no benefit, and they just clutter your registry and our metadata.

More tag types (custom domains, resource limits, startup commands) are on the roadmap.

What are the limits?

Every account gets 1 free slot with a lifetime of up to 2 hours per deployment. When the slot expires, your containers are stopped automatically.

Pushing a new image renews the 2-hour clock — so as long as you keep iterating, the slot stays alive.

Paid slots extend the lifetime up to 1 month per deployment, and will soon allow multiple slots per account.

What about privacy and data isolation?

We do not read, inspect or retain the contents of your images beyond what's required to host them. Your image lives in a private registry scoped to your account.

Each running application is isolated in its own container with its own network namespace. Apps belonging to different users cannot see or reach each other.

We do store operational metadata — image digest, tags, timestamps, health-check results — to run the service itself. Nothing from inside your app.

How do I update my app?

Just run the same docker push command again with your new build. The service detects the new image, replaces the running container, and keeps the same public URL.

The previous deployment is marked as superseded automatically — no manual cleanup required.

What platforms are supported?

Images must be built for linux/amd64. Our generated build command includes --platform linux/amd64 — keep that flag even if you're on Apple Silicon or ARM.

Any language / framework that produces a Docker image works: Node, Python, Go, Rust, Java, .NET, PHP, Ruby, static sites via nginx, etc.

What happens when my slot expires?

When the expiration time hits, we stop your containers and mark the slot as Expired on the Dashboard. Your image itself remains in the registry — nothing is deleted.

To resume hosting, push a new image (or the same one again). A fresh 2-hour window starts.

My app requires a database — do you support that?

Built-in databases — coming soon. We're working on the ability to spin up a database alongside your app as part of the deployment itself, so demo apps can run end-to-end out of the box with no external dependencies.

For now, use an external database (Supabase, Neon, PlanetScale, Railway, your own server — anything that speaks the wire protocol) and pass the connection string through an env64 variable (base64url-encoded, so ://, @, :, ? etc. all pass through):

-t push.to.appi.sh/<slot-id>/app:env64_DATABASE_URL___cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri

The example above decodes to postgres://user:pass@host:5432/db. See the "How do I pass environment variables?" section for the full env64 format and encoding commands.

Can I put a live demo in my GitHub README?

Yes — that's one of the things APPISH is built for. Push your image once, paste https://<your-slot>.appi.sh into your README under a "Try it live" heading, and visitors can poke at your project without cloning anything.

Keeping the demo alive: a free slot stays up for 2 hours after each push, and every new push resets the clock. So before a release announcement, a Show HN post, or a PR review you expect to get traffic on, just run docker push again to refresh the window. For demos that need to stay up indefinitely, a paid slot extends the lifetime up to one month per deployment.

I still have questions — how do I reach you?

Join our Discord community to chat with the team, ask questions, and share what you're building: appi.sh/discord.

Prefer email? Drop us a line at [email protected] — any question, bug report or feature request is welcome.