Skip to content

Deploy Astro Starlight on Docker

This guide documents the deployment of this documentation site at docs.example.com using Astro Starlight, Docker, and Traefik.

  • Astro 6.0.6
  • Starlight 0.38.1
  • Node 22
  • Express 4.x

Last updated: 19.mar.2026

  • Debian server with Docker and Docker Compose installed
  • Traefik reverse proxy running
  • DNS A record pointing to your server IP
┌─────────────────┐
│ Traefik │ Port 80/443
│ (reverse │ SSL via Let's Encrypt
│ proxy) │
└────────┬────────┘
┌─────────────────┐
│ node:22-alpine │ Express serves static files
│ + Express │ dist/ baked into image
└─────────────────┘
/opt/docker/docs/
├── compose.yaml # Express container config
├── Dockerfile # Builds custom image with dist/
├── server.js # Express static file server
├── build.sh # docker run node:22-alpine build
├── package.json
├── astro.config.mjs
├── src/
│ ├── content.config.ts
│ └── content/docs/
│ └── *.mdx
├── public/
│ └── downloads/
└── dist/ # generated static site

Add an A record pointing to your server:

Type: A
Name: docs
Value: <your_server_ip>

Verify DNS propagation:

Terminal window
dig +short docs.<your_domain>.com A
Terminal window
mkdir -p /opt/docker/docs/src/content/docs
mkdir -p /opt/docker/docs/public/downloads

Create /opt/docker/docs/package.json:

{
"name": "astro-docs-debian",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^6.0.0",
"@astrojs/starlight": "^0.38.0"
}
}

Create /opt/docker/docs/astro.config.mjs:

import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
export default defineConfig({
site: 'https://docs.<your_domain>.com',
integrations: [
starlight({
title: 'Astro docs on Debian',
}),
],
});

Create /opt/docker/docs/src/content.config.ts:

import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { schema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema }),
};

Create /opt/docker/docs/build.sh:

#!/bin/sh
docker run --rm \
-v "$(dirname "$0")":/app \
-w /app \
node:22-alpine \
sh -c "npm install && npm run build"

Make it executable:

Terminal window
chmod +x /opt/docker/docs/build.sh

Create /opt/docker/docs/src/content/docs/index.mdx:

---
title: Your Site Title
description: Site description
---
Welcome to the documentation.
Terminal window
cd /opt/docker/docs
./build.sh

This pulls node:22-alpine and generates the dist/ folder.

Create /opt/docker/docs/Dockerfile:

FROM node:22-alpine
WORKDIR /app
RUN npm install express && echo '{"type":"module"}' > package.json
COPY dist ./dist
COPY server.js .
EXPOSE 4322
CMD ["node", "server.js"]

Create /opt/docker/docs/server.js:

import express from 'express';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import fs from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const port = process.env.PORT || 4322;
app.use(express.static(join(__dirname, 'dist')));
app.use((req, res) => {
const htmlPath = join(__dirname, 'dist', req.path + '.html');
const indexPath = join(__dirname, 'dist', 'index.html');
if (fs.existsSync(htmlPath)) {
res.sendFile(htmlPath);
} else {
res.sendFile(indexPath);
}
});
app.listen(port, '0.0.0.0', () => {
console.log(`Docs server running on port ${port}`);
});

Create /opt/docker/docs/compose.yaml:

services:
docs:
build: .
image: docs-starlight
container_name: docs
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.docs.rule=Host(`docs.<your_domain>.com`)"
- "traefik.http.routers.docs.entrypoints=websecure"
- "traefik.http.routers.docs.tls.certresolver=letsencrypt"
- "traefik.http.services.docs.loadbalancer.server.port=4322"
networks:
- proxy
deploy:
resources:
limits:
memory: 128M
networks:
proxy:
external: true
Terminal window
cd /opt/docker/docs
docker compose up -d

Visit https://docs.<your_domain>.com in your browser.

  1. Create new .mdx file in src/content/docs/
  2. Run ./build.sh
  3. Rebuild and restart container:
    Terminal window
    docker compose build --no-cache && docker compose up -d
  1. Copy files to public/downloads/
  2. Run ./build.sh
  3. Rebuild and restart container:
    Terminal window
    docker compose build --no-cache && docker compose up -d
  4. Link to files: /downloads/filename.ext

Astro 6 requires src/content.config.ts (not src/content/config.ts). Ensure you’re using the new format with docsLoader().

Verify dist/ folder exists and contains index.html.

Check Traefik logs:

Terminal window
docker logs traefik

Ensure DNS is properly configured and propagating.