Astro.js & Docker: Dein Weg zur Live-Webseite (Statisch & Dynamisch auf dem VPS)
Astro.js mit Docker auf deinem VPS: Statisch (Nginx) oder dynamisch (Node.js). Komplettes Tutorial mit aktuellen Dockerfiles, Docker Compose und Deployment-Workflow.

Inhalt
Astro 7.4@astrojs/node 11.1Node.js 26 (node:lts)Nginx 1.31Caddy 2.11Docker 29.9Ubuntu 24.04Du möchtest eine moderne, schnelle Webseite mit Astro.js bauen und sie auf deinem eigenen Server bereitstellen? Docker ist dafür das passende Werkzeug, inzwischen setzen 92% der IT-Branche auf Container (Docker State of App Dev Report, 2025). In diesem Beitrag führe ich durch den gesamten Ablauf: von der Entwicklungsumgebung bis zum Live-Deployment der Astro-Anwendung mit Docker und Docker Compose auf einem VPS.
Kernpunkte auf einen Blick
- Astro wuchs von 29 Domains (2021) auf über 38.800 (2025), das 1.340-fache (TechnologyChecker.io, 2025).
- Statische Builds per Nginx: schlank (~94 MB Image), extrem schnell.
- Dynamische Builds per Node.js-Adapter: flexibel, für SSR-Routen nötig.
- Multi-Stage Dockerfiles halten die Produktions-Images klein und sicher.
- Docker Compose orchestriert den Start mit einem Befehl.
Wie bereitest du deine Entwicklungsumgebung vor?
Drei Werkzeuge bilden das Fundament für die Astro- und Docker-Entwicklung.
-
Text-Editor: Visual Studio Code (VS Code) ist kostenlos und bietet eine riesige Erweiterungsbibliothek. Die Astro-Erweiterung für VS Code bringt Syntax-Highlighting und IntelliSense.
-
Node.js: Astro basiert auf Node.js. Statt einer direkten Installation empfehle ich einen Versionsmanager, um flexibel zwischen Versionen zu wechseln.
- Windows: nvm-windows vermeidet Rechteprobleme.
- Linux/macOS: Das originale
nvm(Node Version Manager) lässt sich per curl- oder wget-Skript installieren. - Astro 7 verlangt Node.js ab Version 22.23.4. Die LTS-Version erfüllt das.
Terminalbashnvm install lts # Neueste LTS-Version installieren nvm use lts # LTS-Version aktivieren -
Git: Unverzichtbar für Versionskontrolle und den einfachen Transfer zum Server. Git herunterladen.
Terminalbash# Während der Installation unter Windows: VS Code als Standard-Editor wählen # Default Branch Name auf "main" setzen
Öffne dein Terminal (in VS Code: Strg+Shift+Ö) und prüfe die Installationen:
node -v
npm -v
git --version
nvm versionWie erstellst du dein erstes Astro-Projekt?
Wechsle in deinen Projektordner und starte die Astro-CLI. Das Framework generiert grundlegende Dateien und installiert alle Abhängigkeiten automatisch.
# Projekt mit minimalem Template erstellen
npm create astro@latest -- --template minimal mein-astro-projekt
# In den Projektordner wechseln
cd mein-astro-projekt
# Abhängigkeiten installieren (oft schon während der Erstellung erledigt)
npm install
# oder pnpm install
# Entwicklungs-Server starten
npm run dev
# oder pnpm devStandardmäßig findet man nun unter http://localhost:4321 die Astro-Standardseite.
Änderungen in src/pages/index.astro werden per Hot Module Replacement sofort sichtbar.
Astro verstehen: Seiten, Layouts und Komponenten
Astro-Projekte folgen einer klaren Struktur. Wer diese einmal verstanden hat, kann Projekte jeder Größe aufbauen.
src/pages/: Jede.astro-Datei hier wird zu einer Route.index.astrowird zu/,about.astrozu/about.src/layouts/: Wiederverwendbare Seitenstrukturen. Ein typisches Layout (BaseLayout.astro) enthält das HTML-Gerüst mit einem<slot />, den jede nutzende Seite füllt.src/components/: Wiederverwendbare UI-Elemente wie Header, Footer oder Karten. Importierbar in Seiten und Layouts.- Styling: Globale CSS-Dateien importierst du in Layouts. Innerhalb einer
.astro-Datei sind<style>-Tags standardmäßig scoped, gelten also nur für die jeweilige Komponente.
---
// Beispiel: src/layouts/BaseLayout.astro
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import "../styles/global.css";
interface Props {
title: string;
description?: string;
}
const { title, description = "Meine Astro Seite" } = Astro.props;
---
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title} | Astro Demo</title>
</head>
<body>
<Header />
<main class="container">
<slot />
</main>
<Footer />
</body>
</html>
<style>
.container {
max-width: 1100px;
margin-left: auto;
margin-right: auto;
padding-left: 1rem;
padding-right: 1rem;
flex-grow: 1;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>Statisch oder dynamisch: Welcher Build passt zu dir?
Nginx liefert statische Dateien unter Last etwa 4.874-mal schneller aus als ein Node.js-Server, mit einer medianen Antwortzeit von 0,05 ms gegenüber 253 ms (Benchmark, Adam Jones, 2024). Für reine Auslieferung ist der Nginx-Weg also klar überlegen. Doch nicht jedes Projekt kommt mit statischen Dateien aus.
Ich wollte die Zahlen nicht nur zitieren und habe selbst gemessen. Auf einem Hetzner-Server (2 vCPU, 4 GB RAM) lief jede Variante in einem Container, und ich habe mit ab -n 20000 -c 50 -k je 20.000 Anfragen abgeschickt. Das Messwerkzeug lief auf derselben Maschine und nimmt dem Server etwas Rechenzeit, die absoluten Werte sind also niedriger als bei einer Messung von außen. Das Verhältnis bleibt aussagekräftig:
| Variante | Anfragen pro Sekunde | Zeit pro Anfrage | RAM nach dem Test |
|---|---|---|---|
| Nginx, statische Seite | 15.150 bis 15.880 (2 Läufe) | 3,1 bis 3,3 ms | 8 MiB |
| Node.js, vorgerenderte Seite | 5.871 | 8,5 ms | - |
| Node.js, SSR-Route | 1.838 | 27,2 ms | 153 MiB |
Selbst eine vorgerenderte Seite liefert Nginx rund 2,7-mal so schnell aus wie der Node-Server, und eine SSR-Route braucht etwa das 8,6-Fache an Zeit. Dafür kostet SSR nur Rechenzeit, wenn die Seite wirklich dynamisch sein muss.
Der statische Build (Standard)
Das ist der Normalfall bei Astro. npm run build generiert reines HTML, CSS und JavaScript im dist/ Ordner. Kein Node.js-Server zur Laufzeit nötig.
npm run buildDen dist/ Ordner kannst du auf jedem statischen Webhost bereitstellen.
Dynamische Routen mit dem Node-Adapter
Braucht eine Seite dynamische Daten, die sich bei jedem Aufruf ändern (z.B. Nutzerinfos aus Request-Headern)? Dann kommt Server-Side Rendering (SSR) ins Spiel.
-
Adapter installieren:
Terminalbashnpx astro add nodeDas passt deine
astro.config.mjsan und installiert die nötigen Pakete. -
Prerendering deaktivieren: Für die dynamische Seite explizit ausschalten:
astro--- // src/pages/request-info.astro export const prerender = false; const userAgent = Astro.request.headers.get("user-agent") || "Unbekannt"; import BaseLayout from "../layouts/BaseLayout.astro"; --- <BaseLayout title="Request Info"> <h1>Deine Request Info</h1> <p>Dein User-Agent: {userAgent}</p> </BaseLayout> -
Build-Ergebnis: Der
dist/Ordner enthält jetzt neben statischen Assets (client/) auch Server-Code (server/entry.mjs), der zur Laufzeit mit Node.js ausgeführt werden muss.
Docker als Fundament deines Deployments
Docker verpackt deine Anwendung samt aller Abhängigkeiten in einen isolierten Container. Das Resultat: deine App läuft in derselben Umgebung, lokal wie auf dem Server. Docker verzeichnet in der IT-Branche eine Adoptionsrate von 92% im Jahr 2025, ein Plus von 12 Prozentpunkten gegenüber dem Vorjahr (Docker State of App Dev Report, 2025). Auch in der Self-Hosting Community wird es aus mehreren Gründen sehr gerne verwendet. Containerisierung ist kein Nischenthema, sondern Standard.
Die .dockerignore: Was nicht ins Image gehört
Ähnlich wie .gitignore teilt diese Datei Docker mit, welche Dateien nicht kopiert werden sollen. Das beschleunigt den Build und hält das Image klein.
# .dockerignore
.DS_Store
node_modules
npm-debug.log
dist
.astro
.env*
*.env
.git
.vscode
Dockerfile*
docker-compose*
compose*
README.mdWie containerisierst du eine statische Astro-Seite?
Für rein statische Seiten nutze ich einen zweistufigen Docker Build. Die Build-Stage erzeugt die Dateien mit Node.js, die Runtime-Stage liefert sie per Nginx aus. Das entspricht dem offiziellen Astro-Docker-Rezept.
Dockerfile (statische Seite):
# ---- Build Stage ----
FROM node:lts-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Runtime Stage ----
FROM nginx:alpine AS runtime
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80node:lts-alpineist die schlanke Variante des Node-Images. Für den Build der Seite reicht sie, und der Download ist deutlich kleiner.npm ciinstalliert exakt die Versionen aus derpackage-lock.jsonund bricht ab, wenn Lockfile undpackage.jsonnicht zusammenpassen. Ein Build liefert so auf jedem Rechner dasselbe Ergebnis. Nutzt du pnpm, ersetze die Zeilen durchcorepack enableundpnpm install --frozen-lockfile.- Nur die Runtime-Stage landet im fertigen Image. Node.js und
node_modulesbleiben in der Build-Stage zurück.
src/pages/404.astro:
Damit Nginx unbekannte Adressen sauber beantworten kann, braucht die Seite eine eigene 404-Seite. Astro baut daraus die Datei 404.html.
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Seite nicht gefunden">
<h1>404: Seite nicht gefunden</h1>
<p><a href="/">Zurück zur Startseite</a></p>
</BaseLayout>nginx/nginx.conf:
Lege die Konfigurationsdatei im Ordner nginx/ im Projektroot ab. Sie aktiviert Gzip, setzt Cache-Header für die Dateien mit Hash im Namen und sperrt versteckte Dateien.
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml+rss
application/atom+xml image/svg+xml;
# Astro hängt Hashes an Dateien in /_astro/, die dürfen ewig gecacht werden
location /_astro/ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location ~ /\. {
deny all;
}
location / {
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
}
}Drei Entscheidungen in dieser Datei:
try_files $uri $uri/ =404: Früher stand hier ein Fallback auf dieindex.html. Das passt zu Single-Page-Apps, aber nicht zu einer statischen Astro-Seite: Jede falsche Adresse liefert dann die Startseite mit Status 200 aus, und kaputte Links fallen nicht auf. Jetzt antwortet Nginx mit einem echten 404 und zeigt deine404.html.- Cache nur für
/_astro/: Astro hängt an diese Dateien einen Hash an. Ändert sich der Inhalt, ändert sich der Name, deshalb darf der Browser sie ein Jahr lang behalten. Alles andere (HTML, Favicon) wird bei jedem Besuch geprüft. worker_processes auto: Im Container sieht Nginx alle CPU-Kerne des Hosts.autostartet pro Kern einen Worker, das macht unter Last einen spürbaren Unterschied.
Das fertige Image ist auf meinem Testserver 94 MB groß.
Wie containerisierst du eine dynamische Astro-Seite?
Mit dem Node-Adapter brauchst du Nginx nicht. Die Dockerfile folgt dem Multi-Stage-Pattern aus den Astro-Docs, das Produktions-Abhängigkeiten von Build-Abhängigkeiten trennt. Das beschleunigt spätere Builds, wenn sich nur der Quellcode geändert hat.
Dockerfile (dynamische Seite mit Node.js):
# ---- Base Stage ----
FROM node:lts-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./
# ---- Production Dependencies ----
FROM base AS prod-deps
RUN npm ci --omit=dev
# ---- Build Dependencies + Build ----
FROM base AS build-deps
RUN npm ci
FROM build-deps AS build
COPY . .
RUN npm run build
# ---- Runtime Stage ----
FROM base AS runtime
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
USER node
CMD ["node", "./dist/server/entry.mjs"]- Cache: Ändert sich nur dein Quellcode, nicht aber
package.json, überspringt Docker die beidennpm ci-Schritte und geht direkt zum Build. Bei großen Projekten spart das Minuten. HOST=0.0.0.0: Ohne diese Zeile lauscht der Server nur auflocalhostim Container, und von außen kommt nichts an.USER node: Der Server läuft nicht als root. Das Imagenode:lts-alpinebringt dafür den Benutzernodeschon mit. Im Container zeigtiddanachuid=1000(node).- Größe: Mit
node:lts-alpineist das Image 443 MB groß. Das gleiche Dockerfile mitnode:lts(Debian) kommt auf 1,97 GB. Einen großen Teil der 443 MB machtnode_modulesaus, weil Astro selbst zur Laufzeit gebraucht wird.
Docker Compose: Container orchestrieren
docker compose vereinfacht das Starten und Konfigurieren enorm. Auch für einen einzelnen Container lohnt es sich.
compose.yml:
services:
astro-app:
build:
context: .
dockerfile: Dockerfile
container_name: mein-astro-container
ports:
# Statische Seite (Nginx auf Port 80 im Container)
- "80:80"
# Dynamische Seite (Node.js auf Port 4321 im Container)
# - "80:4321"
restart: unless-stopped
healthcheck:
# Bei der dynamischen Seite: http://127.0.0.1:4321/
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/"]
interval: 30s
timeout: 5s
retries: 3- Dateiname:
compose.yml,compose.yamloderdocker-compose.ymlfunktionieren alle, Docker Compose v2 erkennt jede Variante. ports: Wähle das passende Mapping:80:80für Nginx,80:4321für den Node-Adapter. Die linke Zahl ist der Port auf dem Server, den du im Browser aufrufst.healthcheck: Docker fragt alle 30 Sekunden die Seite ab und zeigt indocker compose psden Zustandhealthyoderunhealthy.wgetbringtalpinebereits mit, du musst nichts nachinstallieren.image:-Zeile: Ich lasse sie weg. Compose benennt das Image dann nach dem Projekt, und du hast keinlatest-Tag im Spiel.
Wie deployest du deine Astro-App auf den VPS?
Jetzt bringen wir alles zusammen. Ich habe jeden Schritt auf einem Server mit Ubuntu 24.04 durchgespielt, einmal mit der statischen und einmal mit der dynamischen Variante. Du brauchst:
- einen VPS mit Ubuntu 24.04 oder einem anderen Docker-kompatiblen System. Wenn du noch keinen hast, hilft dir mein VPS-Leitfaden für Anfänger bei Auswahl und Absicherung. Mit dem Cloud-Init-Konfigurator kannst du einen Hetzner-Server samt Docker und SSH-Key direkt beim Anlegen einrichten lassen und dir die Installation aus Schritt 1 sparen.
- ein Git-Repository mit deinem Projekt, zum Beispiel auf GitHub.
- optional eine Domain, wenn du die Seite per HTTPS ausliefern willst.
Der Ablauf im Überblick:
- Docker auf dem Server installieren
- Code auf den Server holen
- Container bauen und starten
- Status prüfen und Seite aufrufen
- Firewall bedenken
- Domain und HTTPS mit Caddy
- Updates einspielen
Wenn dir die Befehle im Terminal noch fremd sind, hilft dir mein Linux-Terminal-Guide.
1. Docker auf dem Server installieren
Verbinde dich per SSH mit deinem Server:
ssh root@{{server_ip}}Installiere dann Docker inklusive Compose-Plugin. Die Befehle gelten für Ubuntu:
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin gitPrüfe danach, ob alles da ist:
docker --version
docker compose versionDocker version 29.9.3, build 5457199
Docker Compose version v5.7.0Die Compose-Unterstützung steckt als Plugin schon drin, du rufst sie mit docker compose (ohne Bindestrich) auf. Eine ausführlichere Einrichtung eines Servers mit Docker findest du in meinem Beitrag zum Ubuntu Homeserver.
2. Code auf den Server holen
Klone dein Repository, das du vorher gepusht hast:
git clone DEINE_REPOSITORY_URL mein-astro-projekt
cd mein-astro-projektIst das Repository privat, brauchst du auf dem Server einen Zugang: entweder einen Deploy Key oder einen Access Token für die HTTPS-URL. Falls du Git noch nicht kennst, erklärt mein Git-Tutorial die Grundlagen.
Du kannst auch ohne Git arbeiten und den Projektordner von deinem Rechner kopieren. rsync überträgt dabei nur, was sich geändert hat:
rsync -az --exclude node_modules --exclude dist --exclude .astro --exclude .git \
./ root@{{server_ip}}:/root/mein-astro-projekt/3. Container bauen und starten
Im Projektordner auf dem Server:
sudo docker compose up --build -ddocker compose up startet die Services aus der compose.yml. --build erzwingt, dass das Image neu gebaut wird, falls sich Code oder Dockerfile geändert haben. -d startet alles im Hintergrund. Der erste Build dauert, weil Docker die Basis-Images herunterladen muss. Bei mir lagen auf dem Server zwischen dem Befehl und dem laufenden Container 13 Sekunden.
4. Status prüfen und Seite aufrufen
Direkt nach dem Start zeigt docker compose ps den Zustand health: starting. Nach dem ersten erfolgreichen Healthcheck, also nach rund 30 Sekunden, steht dort healthy:
sudo docker compose psNAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
mein-astro-container mein-astro-projekt-astro-app "/docker-entrypoint.…" astro-app 35 seconds ago Up 35 seconds (healthy) 0.0.0.0:80->80/tcp, [::]:80->80/tcpOb der Server antwortet, prüfst du direkt auf dem Server mit curl:
curl -I http://localhostHTTP/1.1 200 OK
Server: nginx/1.31.8
Content-Type: text/htmlÖffne nun http://SERVER-IP in deinem Browser. Deine Astro-Webseite ist live.

Läuft der Container nicht, siehst du die Ursache in den Logs:
sudo docker compose logs -f astro-app5. Firewall: Docker umgeht ufw
Wer auf dem Server die Firewall ufw benutzt, erlebt bei Docker eine Überraschung. Ich habe ufw aktiviert und nur Port 22 freigegeben. Der Container war trotzdem von außen erreichbar. Docker trägt veröffentlichte Ports direkt in die Firewall-Regeln des Kernels ein, und ufw sieht diese Regeln nicht.
Zwei Wege helfen:
- Veröffentliche Ports, die nicht öffentlich sein sollen, nur lokal:
"127.0.0.1:8080:80"statt"8080:80". - Setze zusätzlich die Cloud-Firewall deines Anbieters ein. Sie sitzt vor dem Server, und Docker kann sie nicht umgehen. Mehr zur Absicherung steht im VPS-Leitfaden.
6. Domain und HTTPS mit Caddy
Eine Seite unter http://SERVER-IP ist für einen Test in Ordnung. Für echte Besucher brauchst du eine Domain und HTTPS. Ich nutze dafür Caddy als Reverse Proxy: Er holt das Zertifikat bei Let’s Encrypt selbst und erneuert es automatisch.
- Lege bei deinem Domain-Anbieter einen A-Record für astro.domain.de an, der auf SERVER-IP zeigt. Warte, bis die Namensauflösung funktioniert.
- Erweitere die
compose.yml. Die Astro-App veröffentlicht keinen Port mehr, nur Caddy ist von außen erreichbar:
services:
astro-app:
build:
context: .
dockerfile: Dockerfile
container_name: mein-astro-container
ports:
- "80:80"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/"]
interval: 30s
timeout: 5s
retries: 3
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- astro-app
volumes:
caddy_data:
caddy_config:- Lege die
Caddyfileneben diecompose.yml. Bei der statischen Seite zeigt sie auf Port 80 des Containers, bei der dynamischen auf 4321:
{{domain}} {
reverse_proxy astro-app:80
}Dabei ist astro-app der Service-Name aus der compose.yml. Compose-Services im selben Projekt erreichen sich über diesen Namen. Wenn du dein Setup lieber per Formular zusammenklickst, hilft dir der Caddyfile-Generator. Wer Traefik statt Caddy einsetzen will, findet das in meiner Anleitung zu Traefik als Reverse Proxy.
- Starte alles neu:
sudo docker compose up -dCaddy holt das Zertifikat beim ersten Aufruf. Bei mir stand es nach rund fünf Sekunden bereit. Im Log erscheint die Zeile certificate obtained successfully. Prüfe beide Adressen:
curl -I https://{{domain}}
curl -I http://{{domain}}HTTP/2 200
...
HTTP/1.1 308 Permanent Redirect
Location: https://{{domain}}/HTTP leitet Caddy also von selbst auf HTTPS um. So sieht die dynamische Seite aus, wenn du /request-info aufrufst:

7. Updates einspielen
Hast du lokal Änderungen gemacht, läuft das Update immer gleich ab:
- Code committen und pushen
- Auf dem Server den neuen Stand holen und neu bauen:
cd mein-astro-projekt
git pull
sudo docker compose up --build -d- Alte Image-Schichten entfernen, damit der Server nicht vollläuft:
sudo docker image prune -fDocker baut das neue Image und ersetzt den Container. Bei der dynamischen Seite lassen sich unveränderte npm ci-Schritte aus dem Cache wiederverwenden, das Update geht dann schneller.
Ist ein Update schiefgegangen, wechsle auf dem Server zum letzten funktionierenden Commit und baue neu: git log --oneline zeigt dir die Stände, git checkout COMMIT_ID setzt den Code zurück, danach wieder sudo docker compose up --build -d. Mit git checkout main kehrst du später zum aktuellen Stand zurück.
Wenn etwas nicht klappt
Bind for :::80 failed: port is already allocated: Ein anderer Container oder Dienst belegt schon Port 80.sudo docker pszeigt dir, welcher. Stoppe ihn oder wähle für die Astro-App einen anderen Host-Port, zum Beispiel"8080:80".- 502 von Caddy: Der Proxy erreicht den Container nicht. Prüfe, ob der Port in der
Caddyfilezum Container passt (80 bei Nginx, 4321 bei Node.js) und ob du Caddy nach einer Änderung neu gestartet hast. [NoAdapterInstalled]beim Build: Eine Seite setztprerender = false, aber der Node-Adapter fehlt. Installiere ihn mitnpx astro add nodeund committe die geänderteastro.config.mjssamtpackage.jsonund Lockfile.- Container startet nicht oder startet ständig neu: Die Ursache steht fast immer in
sudo docker compose logs astro-app.
Häufige Fragen
Kann ich Astro auch ohne Docker deployen?
Ja. Astro generiert statische Dateien, die auf jedem Webserver hostbar sind. Mit dem Node.js-Adapter erzeugt Astro außerdem einen Server, der auf jedem Rechner mit Node.js läuft. Docker macht beides reproduzierbar und lässt sich mit einem Befehl starten.
Wann brauche ich Server-Side Rendering?
SSR ist nötig, wenn Seiten bei jedem Aufruf aktuelle Daten brauchen, etwa nutzerspezifische Inhalte oder Echtzeit-Abfragen. Für Blogs, Portfolios und Dokumentationsseiten reicht statisches Rendering völlig aus. Es ist schneller und braucht keinen laufenden Serverprozess.
Wie viel Speicher und Rechenleistung braucht eine Astro-Seite im Container?
In meinem Test auf einem Server mit 2 vCPU und 4 GB RAM belegte der Nginx-Container nach 20.000 Anfragen 8 MiB. Der Node.js-Container kam im Leerlauf auf 70 MiB und nach dem Test auf 153 MiB. Das Nginx-Image ist 94 MB groß, das Node.js-Image mit node:lts-alpine 443 MB.
Warum ist mein Node.js-Image so groß?
Das Basis-Image node:lts (Debian) allein wiegt rund 1,8 GB. Mit node:lts-alpine schrumpft das fertige Image von 1,97 GB auf 443 MB. Bei einer rein statischen Seite umgehst du das Problem, weil nur Nginx im fertigen Image landet.
Wie aktualisiere ich meine Webseite auf dem Server?
Lokal änderst du den Code, testest und pushst per Git. Auf dem Server führst du git pull und danach docker compose up --build -d aus. Das baut das Image neu und ersetzt den Container. Mit docker image prune -f räumst du alte Images weg.
Wie bekomme ich HTTPS für meine Astro-Seite?
Setze einen Reverse Proxy wie Caddy vor den Container. Du legst einen A-Record für deine Domain an, ergänzt Caddy in der compose.yml und trägst die Domain in die Caddyfile ein. Das Zertifikat holt Caddy bei Let's Encrypt selbst. Die genaue Konfiguration steht im Abschnitt zu Domain und HTTPS.
Warum ist mein Container trotz ufw von außen erreichbar?
Docker trägt veröffentlichte Ports direkt in die Firewall-Regeln des Kernels ein, und ufw sieht diese Regeln nicht. Veröffentliche Ports, die privat bleiben sollen, deshalb mit 127.0.0.1 davor (zum Beispiel 127.0.0.1:8080:80) oder sperre sie in der Cloud-Firewall deines Anbieters.
Kann ich mehrere Astro-Projekte auf einem VPS hosten?
Ja. Jedes Projekt bekommt seinen eigenen Container. Davor setzt du einen Reverse Proxy wie Caddy oder Traefik, der anhand der Domain auf den richtigen Container verteilt. Alternativ veröffentlichst du jedes Projekt auf einem eigenen Port.
Nginx oder Apache für statische Astro-Seiten?
Ich nutze Nginx: Es steht im offiziellen Astro-Docker-Rezept, das Image ist klein und die Konfiguration kurz. Apache liefert statische Dateien genauso aus.
Fazit
Astro.js und Docker ergänzen sich gut. Astro liefert die Performance und Entwicklerfreundlichkeit, Docker sorgt für konsistente, portable Deployments. Mit den Dockerfiles, der compose.yml und der Caddy-Konfiguration aus diesem Beitrag kommt deine Seite in wenigen Minuten auf einen eigenen Server, per HTTPS unter deiner Domain.
Wenn du mehr Dienste auf demselben Server betreiben willst, lohnt sich ein gemeinsamer Reverse Proxy: Wie das mit Traefik funktioniert, zeigt mein Beitrag Server mit Traefik als Reverse Proxy einrichten. Bei Fragen oder Feedback schreib gern einen Kommentar.
Änderungsprotokoll
Auf Astro 7 getestet. Deployment-Abschnitt neu geschrieben, Dockerfiles auf node:lts-alpine und npm ci umgestellt, Nginx-Konfiguration aktualisiert (echtes 404, Cache-Header), Messwerte und Screenshots ergänzt.
Dockerfiles aktualisiert (node:lts, optimiertes Multi-Stage-Pattern), Text ergänzt, FAQ-Sektion hinzugefügt.