Comparison

Astro and Hugo Sites on an nginx Container Versus the Pages Hosts: When a Container Wins

SnapDeploy Team 2026-11-02 Updated 2026-11-02 7 min read
astrohugonginxstatic-sitesfree-tier

A static site built with Astro, Hugo or any other generator has a natural home, and it is not a container. GitHub Pages, Cloudflare Pages and Netlify serve static files for free, from a CDN, without ever sleeping. So why would anyone put one in a container? Because a handful of situations break the Pages model: private repositories on a free GitHub account, a site that needs a password, custom headers and redirects without platform-specific config, or a front end that must live on the same host as its API. This post lays out both sides with the actual limits, and gives a Dockerfile for the cases where the container wins.

What the Pages hosts give you, precisely

Host Free tier The limit that matters
GitHub PagesUnlimited sites, custom domain with HTTPSPublic repositories only on a free account; soft limit of 100 GB bandwidth a month; no server logic.
Cloudflare PagesUnlimited bandwidth and requests, 500 builds a month, custom domainsFunctions run on the Workers free plan: 100,000 requests a day and 10 ms of CPU per request.
Netlify300 credits a month (accounts created since September 2025), custom domains, deploy previewsBandwidth costs 20 credits per GB, so about 15 GB a month if the credits all go to traffic.

None of them sleeps, all of them are on a CDN, and all of them give you a custom domain for free. For a public site whose only job is to be fast and always there, one of these three is the answer, and the rest of this post is about the exceptions.

When a container wins

  • The repository is private and the account is free. GitHub Pages needs a public repository on a free plan. A container host builds from a private repository without asking.
  • The site needs a password. Internal documentation, a client preview, a staging copy. nginx does HTTP basic auth in three lines; Pages hosts either cannot or charge for it.
  • Headers, redirects and rewrites beyond what a platform's _headers or _redirects file allows: a real nginx config, versioned with the site.
  • Same host as the API. Serving / from nginx and proxying /api to a backend removes CORS from your life. On SnapDeploy the front end and the API can be two of the four free containers, or one container with nginx proxying to a local process.
  • No build-minute meter. Container builds count as deploys (10 a day on the free tier), not minutes.

And the honest cost: a free container sleeps after 15 minutes without traffic and takes about a minute to wake. For a public marketing site that is disqualifying; for internal docs, a preview or a demo it is fine, and Always-On ($12/month) removes it if the site graduates.

The Dockerfile: build the site, serve it with nginx

Astro, or any Node-based generator (the same shape works for Eleventy, VitePress and Docusaurus):

FROM node:22-alpine AS build
WORKDIR /site
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /site/dist /usr/share/nginx/html
EXPOSE 80

Hugo is a single binary, so the build stage is smaller; the community hugomods/hugo images ship it with the extended features, or install the release tarball into an Alpine stage. Either way the output folder (public/ for Hugo, dist/ for Astro) is what gets copied into the nginx image.

# nginx.conf
server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    location = /health { return 200 "ok"; }

    location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; }

    location /api/ { proxy_pass http://127.0.0.1:8000/; }   # remove if the API is elsewhere

    location / {
        try_files $uri $uri/ $uri.html /404.html;
        add_header Cache-Control "public, max-age=300";
    }
}

Add auth_basic "Private"; auth_basic_user_file /etc/nginx/.htpasswd; inside the root location to password-protect the whole site; generate the file with htpasswd and copy it in the Dockerfile.

Without a Dockerfile, SnapDeploy treats a repository whose root contains plain HTML as a static site and generates an nginx image with a single-page fallback (try_files … /index.html) on port 80. That works for a hand-written site or for a generator whose output you commit to the repository; for Astro or Hugo you want the build stage above, so keep the Dockerfile.

What it costs to run

nginx serving a static site idles at a few megabytes of memory and is limited by network, not by CPU, so the smallest container is more than enough. Image size is the nginx base plus your files: about 15 MB plus the site. Builds are the slow part on a Node generator (dependency install and the generator itself), a few minutes per deploy; Hugo builds in seconds.

Deploying

  1. Add the Dockerfile and nginx.conf; commit a .dockerignore with node_modules and the output folder.
  2. Push. Containers → Deploy from GitHub → pick the repository. Port 80.
  3. Deploy, open the container URL, then /health.
  4. For a preview per branch, deploy the branch to a second container; the free tier has four.

Decision in one paragraph

Public site, no auth, no special routing: GitHub Pages if the repository is public, Cloudflare Pages if bandwidth might matter, Netlify if you value its build and preview tooling. Private repository, password, custom nginx rules, or a front end that belongs next to its API: a container, accepting the sleep on the free tier or paying $12/month to remove it. Pick by the shape of the site, and keep the build in a Dockerfile either way so the choice is reversible.

Cache headers do real work here

Container URLs on SnapDeploy sit behind Cloudflare, which caches static assets at the edge and honours the Cache-Control headers nginx sends. With the one-year immutable rule on hashed assets above, repeat visitors and most crawlers are served from the edge without reaching the container at all, which both speeds up the site and keeps the container from waking for asset requests. The short five-minute rule on HTML pages keeps deploys visible quickly while still absorbing bursts.

Numbers used: GitHub Pages, Cloudflare Pages and Netlify limits from their documentation and pricing pages as of September 2026; SnapDeploy free tier from the free tier docs (4 containers, 512 MB, 0.25 vCPU, 100 container-hours a month, 10 deploys a day, no card).

Ready to Deploy?

Deploy free. 10 deploys a day, 100 hours a month, no credit card.

Run this yourself: Get a dedicated NVIDIA T4 (16 GB) for a flat $499/mo or A10G (24 GB) for $999/mo — never shared, never spot, no hourly metering. See dedicated GPU hosting →

One-click Ollama, vLLM, ComfyUI, Whisper, PyTorch & more — or deploy your own GitHub repo or Docker image. Compare plans.

FIRST OF ITS KIND

Sprint Pack — ₹99 for 24 hours

The first managed container PaaS to offer a one-time 24-hour Always-On pass under $5. Unlimited deploys + Always-On for one Small (512 MB) container — including WebSockets & real-time apps. No subscription, no auto-renewal. Stack two for 48 hours.

Need a managed database too? DB Sprint Pack — ₹99 / 12h for Postgres, MySQL, MariaDB, Mongo, Redis, or RabbitMQ.

Tip: Need 24 hours of Always-On for a demo, weekend, or quick test — or WebSockets & real-time apps? Sprint Pack is ₹99 one-time — no subscription, no auto-renewal.

Need a managed Postgres / MySQL / Mongo / Redis instead? DB Sprint Pack — ₹99 / 12h.

Take SnapDeploy with you

Deploy, monitor and wake your containers from your iPhone.

Download on the App Store

Get DevOps Tips & Updates

Container deployment guides, platform updates, and DevOps best practices. No spam.

Unsubscribe anytime. We respect your privacy.

More Articles