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.
To host a static site on blitz.cloud, you build it into plain files, copy them into the unprivileged nginx image, push that image to Docker Hub, and deploy it. The site gets its own subdomain like blog.yourname.blitz.cloud with HTTPS. This works for any generator that outputs a folder of HTML: Astro, Hugo, Eleventy, a Vite app, or Next.js with output: "export".
Honest comparison first. Netlify, Cloudflare Pages and GitHub Pages build static sites straight from a Git repository and serve them from a global CDN, and that is less work than this guide. blitz.cloud is worth it if you want the site next to your other apps under one address, served from Germany, without a US provider involved. It serves from one location, not a CDN.
The Dockerfile
This is the static-site example, an Astro site. Two stages: Node builds the site, nginx serves it.
# Build stage: turn the Astro project into plain files in dist/.
FROM node:22.23.2-alpine3.24 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Serve stage: nginx without root, listening on 8080.
FROM nginxinc/nginx-unprivileged:1.30.4-alpine3.24
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
# blitz.cloud runs every app as uid 1000. nginx-unprivileged keeps its pid
# file and temp folders in /tmp, which any uid can write to.
USER 1000
EXPOSE 8080
The regular nginx image listens on port 80 and starts as root, so it doesn't run on blitz.cloud. nginxinc/nginx-unprivileged is the official variant built for exactly this situation.
The nginx config
server {
# The unprivileged nginx image listens on 8080 instead of 80, so it runs
# without root.
listen 8080;
server_name _;
root /usr/share/nginx/html;
# Behind blitz.cloud's HTTPS proxy nginx only sees plain HTTP on 8080.
# Relative redirects keep /about -> /about/ on the address the visitor used.
absolute_redirect off;
location / {
try_files $uri $uri/ $uri.html =404;
}
# Astro puts fingerprinted assets in /_astro/, safe to cache for a year.
location /_astro/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
error_page 404 /404.html;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
}
absolute_redirect off is the line people miss. Without it, a visit to /about gets redirected to http://blog.yourname.blitz.cloud:8080/about/, because nginx builds the redirect from what it sees, and behind the HTTPS proxy it sees plain HTTP on port 8080. We hit this while testing the example.
Other generators
Only the build stage changes. Point the COPY --from=build line at the generator's output folder.
| Generator | Build command | Output folder |
|---|---|---|
| Astro | npm run build | dist |
| Vite (React, Vue, Svelte) | npm run build | dist |
Next.js with output: "export" | npm run build | out |
| Hugo | hugo --minify | public |
For Next.js, a static export only covers pages that don't need a server. If your app uses server components that fetch per request, API routes or middleware, use the standalone setup from Move an app from Vercel to an EU host instead.
For a single-page app with client-side routing, replace =404 in try_files with /index.html so deep links load the app.
Build, test and push
docker build --platform linux/amd64 -t yourname/blitz-example-static-site:1.0.0 -t yourname/blitz-example-static-site:latest .
docker run --rm --user 1000:1000 --cap-drop ALL --security-opt no-new-privileges \
-p 8080:8080 yourname/blitz-example-static-site:1.0.0
In our run, / and /about/ returned 200, /about returned a 301 to /about/, and a missing page returned a real 404. Then push:
docker push --all-tags yourname/blitz-example-static-site
Put it online with your subdomain
- In the dashboard, click Host something new and choose "An app that is already packaged up".
- Search for your image, pick the version, click Continue.
- Give it an address. Type
blogand the site is athttps://blog.yourname.blitz.cloud. You can also put one app on your main address itself,https://yourname.blitz.cloud, which suits a personal homepage. - Click Put it online.
The certificate covers your main address and every subdomain under it, and renews on its own. See Addresses and HTTPS.
Can I use my own domain?
Not yet. Connecting a domain you own, like example.com, is planned and not live today. Until then the site lives under yourname.blitz.cloud.
Put your first app online today.
Free plan, no credit card, no waiting list.
Create a free account