Static Sites
3 min read
Teploy has two ways to serve a static site. For almost everyone, type: static is the right one — no Dockerfile, no image build, just rsync + Caddy.
Native static deploy (type: static)
Build your site locally (or let Teploy run the build for you), and Teploy rsyncs the output directory straight to the server, symlinks it live, and writes the Caddy route. No Docker involved.
app: mysite
type: static
domain: mysite.com
server: production
source: dist # local directory of already-built files to ship
build: # optional — shell commands run locally before rsync
- npm run buildDeploy:
teploy deploybuild runs locally by default (set build_remote: true to run it on the server instead), then source is rsynced up. Your site is live at https://mysite.com with auto-provisioned SSL.
See the full field list — SPA fallback, cache headers, release retention, custom Caddy directives — in the teploy.yml reference.
Supported generators
Any static site generator works — point source: at wherever it writes output:
- Astro (
dist/) - Next.js static export (
out/) - Vite / React / Vue (
dist/) - Hugo (
public/) - Jekyll (
_site/) - Eleventy (
_site/) - Plain HTML/CSS/JS
SPA routing
For single-page applications that handle routing client-side:
spa: true
spa_fallback: /index.html # default, only needed to overrideCaching
cache:
"/assets/*": "public, max-age=31536000, immutable"Asset bridging
For zero-downtime static asset serving (old assets stay available for in-flight requests during a deploy transition):
assets:
path: /srv/assets
keep_days: 7Optional: CDN via Cloudflare
For global edge caching, put Cloudflare in front of your server:
- Add your domain to Cloudflare
- Point to your VPS IP with proxy enabled (orange cloud)
- Static assets are cached at 300+ edge locations
See Cloudflare Setup for details.
Docker-based static deploy
If you need more control over the serving layer — a custom base image, non-Caddy tooling baked into the build, or you're already running everything else as containers — you can serve a static site as a regular container deploy instead. This is the older, more manual path; reach for type: static above unless you have a specific reason not to.
Create a Dockerfile for your static site:
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM caddy:alpine
COPY --from=build /app/dist /srvThen configure teploy.yml as a normal container deploy (no type: static):
app: mysite
domain: mysite.com
server: production
port: 80teploy deployCustom Caddyfile
For advanced routing, add a Caddyfile to your project:
:80 {
root * /srv
file_server
header /assets/* Cache-Control "public, max-age=31536000, immutable"
handle_errors {
rewrite * /404.html
file_server
}
}Include it in your Dockerfile:
FROM caddy:alpine
COPY dist /srv
COPY Caddyfile /etc/caddy/CaddyfileSPA routing with try_files:
:80 {
root * /srv
try_files {path} /index.html
file_server
}