Deploy a Node.js API without managing servers
Package a Node.js API as a Docker image that runs without root on port 8080, push it to Docker Hub and host it on blitz.cloud in the EU with HTTPS.
To deploy a Node.js API without managing a server on blitz.cloud, you put it in a small Docker image that listens on port 8080 and runs as a normal user, push that image to Docker Hub, and pick it in the blitz.cloud dashboard. blitz.cloud runs it in its own sandbox under an HTTPS address and restarts it if the process dies. No VM, no nginx config, no certificates to renew.
The code in this guide is the node-api example. It uses Node's built-in http module so there is nothing to learn beyond Node itself. An Express or Fastify app works the same way.
The server
Two details make a Node app behave well on blitz.cloud. It reads its port from the environment with 8080 as the default, and it stops cleanly when it receives SIGTERM, which is how a container is asked to shut down during a restart.
import http from "node:http";
const port = Number(process.env.PORT ?? 8080);
const server = http.createServer((req, res) => {
if (req.url === "/healthz") {
res.writeHead(200, { "content-type": "application/json" });
return res.end(JSON.stringify({ ok: true }));
}
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ hello: "from blitz.cloud" }));
});
server.listen(port, () => console.log(`listening on :${port}`));
process.on("SIGTERM", () => server.close(() => process.exit(0)));
The full version in the repository also talks to PostgreSQL when DATABASE_URL is set. That part is covered in Deploy an app with a PostgreSQL database.
The Dockerfile
# Install dependencies in a throwaway stage so the final image has no npm cache.
FROM node:22.23.2-alpine3.24 AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22.23.2-alpine3.24
ENV NODE_ENV=production PORT=8080
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
COPY src ./src
# The official Node image ships a "node" user with uid 1000, the same uid
# blitz.cloud runs every app as.
USER node
EXPOSE 8080
CMD ["node", "src/server.js"]
EXPOSE 8080 is not decoration here. blitz.cloud reads the ports an image declares and uses that one, so you don't have to type a port anywhere.
Starting with node directly rather than npm start matters too: npm doesn't pass SIGTERM on reliably, so your shutdown handler would never run.
Test it like blitz.cloud would run it
docker build --platform linux/amd64 -t blitz-example-node-api .
docker run --rm --user 1000:1000 --cap-drop ALL --security-opt no-new-privileges \
-p 8080:8080 blitz-example-node-api
curl http://localhost:8080/
# {"hello":"from blitz.cloud","database":"none"}
When we ran exactly this, / and /healthz answered 200. If your own app fails at this step, it will fail on blitz.cloud too, and the error is easier to read on your laptop.
Push to Docker Hub
docker build --platform linux/amd64 -t yourname/blitz-example-node-api:1.0.0 -t yourname/blitz-example-node-api:latest .
docker login
docker push --all-tags yourname/blitz-example-node-api
The repository has to be public. Docker Hub is what the wizard searches; a public image on ghcr.io or Quay works too if you type its full name. Tag every build twice, with a version like 1.0.0 and with latest: the version tells you which build is which, and latest is what the Restart route further down relies on.
If you'd rather not push from your laptop, the examples repository has a GitHub Actions workflow that builds for amd64 and pushes on every commit to main, once you add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN as secrets in your fork.
Deploy on blitz.cloud
- In the dashboard, click Host something new and choose "An app from Docker Hub".
- Search for
yourname/blitz-example-node-api, picklatestand click Continue (the section on shipping new versions below explains why). A repository you just pushed can take a while to show up in Docker Hub's search. - What this app needs should say port 8080, "the one the app itself declares".
- Name it, give it an address such as
notes, and click Put it online.
Your API is then at https://notes.yourname.blitz.cloud.
Configuration and secrets
Settings go on the app's Environment tab. Values are stored encrypted, masked on screen, and reach the app the next time it restarts, so change a value, then restart. Up to 64 settings per app.
Shipping a new version
Deploy the latest tag and push a new latest when you release. Automatic updates are on by default for a tag that moves: every night around 4:00 UTC blitz.cloud checks where latest points and puts a newer build online. If the new version doesn't start within ten minutes, the one before comes back. To ship right away, press Restart on the app. A tag named latest is fetched again every time the app starts, so the restart picks up the new build. The tag won't tell you which build is running; the app's Activity tab lists each version that went live.
If your code is on GitHub, you can skip Docker Hub altogether. Connect your GitHub account in blitz.cloud, pick the repository, private ones included, and the branch. Every push then builds the Dockerfile above and puts the new version online. See deploy a GitHub project.
Is this a good fit?
For a small JSON API, a webhook receiver or a Discord bot with an HTTP endpoint, yes. Each app gets 128 MB of memory reserved and up to 512 MB, and a share of a CPU. For a busy API that needs several cores, or for serverless functions that scale to zero and bill per request, it isn't what blitz.cloud does.
Read next
Keep an app from GitHub online around the clock
Put a Python or Node.js app, an API or a bot from GitHub online in the EU and keep it answering at any hour, with a new version on every push. From 2 EUR a month.
How to deploy a Docker container for free
Run a public Docker image online for free with HTTPS and no credit card. Tested steps for blitz.cloud, the image requirements, and what the free plan covers.
Host a static site (Astro, Hugo, Next.js) with its own subdomain
Build an Astro, Hugo, Vite or Next.js static export into a non-root nginx image on port 8080 and host it on blitz.cloud with HTTPS on your own subdomain.
Put your first app online today.
Start on the free plan, no credit card. Move to Pro when an app has to stay online.
Create your account