Deploy Astro Starlight on Docker
This guide documents the deployment of this documentation site at docs.example.com using Astro Starlight, Docker, and Traefik.
Tested With
Section titled “Tested With”- Astro 6.0.6
- Starlight 0.38.1
- Node 22
- Express 4.x
Last updated: 19.mar.2026
Prerequisites
Section titled “Prerequisites”- Debian server with Docker and Docker Compose installed
- Traefik reverse proxy running
- DNS A record pointing to your server IP
Architecture
Section titled “Architecture”┌─────────────────┐│ Traefik │ Port 80/443│ (reverse │ SSL via Let's Encrypt│ proxy) │└────────┬────────┘ │ ▼┌─────────────────┐│ node:22-alpine │ Express serves static files│ + Express │ dist/ baked into image└─────────────────┘Project Structure
Section titled “Project Structure”/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 siteStep 1 - Create DNS Record
Section titled “Step 1 - Create DNS Record”Add an A record pointing to your server:
Type: AName: docsValue: <your_server_ip>Verify DNS propagation:
dig +short docs.<your_domain>.com AStep 2 - Create Directory Structure
Section titled “Step 2 - Create Directory Structure”mkdir -p /opt/docker/docs/src/content/docsmkdir -p /opt/docker/docs/public/downloadsStep 3 - Create package.json
Section titled “Step 3 - Create package.json”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" }}Step 4 - Create astro.config.mjs
Section titled “Step 4 - Create astro.config.mjs”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', }), ],});Step 5 - Create Content Config
Section titled “Step 5 - Create Content Config”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 }),};Step 6 - Create Build Script
Section titled “Step 6 - Create Build Script”Create /opt/docker/docs/build.sh:
#!/bin/shdocker run --rm \ -v "$(dirname "$0")":/app \ -w /app \ node:22-alpine \ sh -c "npm install && npm run build"Make it executable:
chmod +x /opt/docker/docs/build.shStep 7 - Create Index Page
Section titled “Step 7 - Create Index Page”Create /opt/docker/docs/src/content/docs/index.mdx:
---title: Your Site Titledescription: Site description---
Welcome to the documentation.Step 8 - Build the Site
Section titled “Step 8 - Build the Site”cd /opt/docker/docs./build.shThis pulls node:22-alpine and generates the dist/ folder.
Step 9 - Create Dockerfile
Section titled “Step 9 - Create Dockerfile”Create /opt/docker/docs/Dockerfile:
FROM node:22-alpine
WORKDIR /app
RUN npm install express && echo '{"type":"module"}' > package.json
COPY dist ./distCOPY server.js .
EXPOSE 4322
CMD ["node", "server.js"]Step 10 - Create Express Server
Section titled “Step 10 - Create Express Server”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}`);});Step 11 - Create Docker Compose
Section titled “Step 11 - Create Docker Compose”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: trueStep 12 - Start the Container
Section titled “Step 12 - Start the Container”cd /opt/docker/docsdocker compose up -dStep 13 - Verify
Section titled “Step 13 - Verify”Visit https://docs.<your_domain>.com in your browser.
Adding New Content
Section titled “Adding New Content”- Create new
.mdxfile insrc/content/docs/ - Run
./build.sh - Rebuild and restart container:
Terminal window docker compose build --no-cache && docker compose up -d
Adding Downloadable Files
Section titled “Adding Downloadable Files”- Copy files to
public/downloads/ - Run
./build.sh - Rebuild and restart container:
Terminal window docker compose build --no-cache && docker compose up -d - Link to files:
/downloads/filename.ext
Troubleshooting
Section titled “Troubleshooting”Build fails with content config error
Section titled “Build fails with content config error”Astro 6 requires src/content.config.ts (not src/content/config.ts). Ensure you’re using the new format with docsLoader().
404 errors
Section titled “404 errors”Verify dist/ folder exists and contains index.html.
SSL certificate issues
Section titled “SSL certificate issues”Check Traefik logs:
docker logs traefikEnsure DNS is properly configured and propagating.