From 5af2d78328aa868d9e4f3b8705237061587cbeff Mon Sep 17 00:00:00 2001 From: Felix Klement Date: Sun, 4 Oct 2026 19:49:09 +0200 Subject: [PATCH] docker --- .dockerignore | 19 +++++++++++++++++ Dockerfile | 19 +++++++++++++++++ README.md | 15 ++++++++++++++ docker/nginx.conf | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker/nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d64a1dd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.gitignore +.DS_Store +**/.DS_Store +node_modules +dist +.astro +product_repo +.ds-sync +ds-bundle +.design-sync/.cache +.design-sync/learnings +.design-sync/node_modules +docs +Dockerfile +.dockerignore +*.log +.env +.env.* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9e24cae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# syntax=docker/dockerfile:1 + +# Build the static site. +FROM node:22-bookworm-slim AS build +WORKDIR /app +ENV ASTRO_TELEMETRY_DISABLED=1 \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 +COPY package.json package-lock.json ./ +RUN npm ci --no-audit --no-fund +COPY . . +RUN npm run build + +# Serve dist/ with nginx. +FROM nginx:1.27-alpine AS runtime +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://127.0.0.1/healthz || exit 1 diff --git a/README.md b/README.md index 84c4f33..08c2434 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,21 @@ npm run preview # serve dist/ npm run check # Astro + TypeScript check ``` +## Docker / Dokploy + +The `Dockerfile` builds the site with Node and serves `dist/` from nginx on port 80 +(config in `docker/nginx.conf`: clean URLs without trailing slash, 404 page, gzip, long caching for +`/_astro/`, health check at `/healthz`). + +```sh +docker build -t ciphercell . +docker run --rm -p 8080:80 ciphercell # http://localhost:8080 +``` + +In Dokploy: create an Application from this repository, set Build Type to **Dockerfile** +(path `Dockerfile`), and under Domains add the domain with container port **80** and HTTPS on. +TLS is handled by Dokploy's Traefik. + ## Where things live | Path | Purpose | diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..e7ed5a4 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,53 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # TLS terminates at Dokploy's Traefik; keep redirects relative so they stay on https. + absolute_redirect off; + server_tokens off; + + gzip on; + gzip_comp_level 6; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml application/xml application/javascript application/json image/svg+xml; + + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Frame-Options "SAMEORIGIN" always; + + location = /healthz { + access_log off; + default_type text/plain; + return 200 "ok\n"; + } + + # trailingSlash: 'never' — /team is served from /team/index.html; /team/ redirects to /team. + location ~ ^(.+)/$ { + return 301 $1$is_args$args; + } + + location / { + try_files $uri $uri.html $uri/index.html =404; + } + + # Astro's hashed build assets never change. + location ^~ /_astro/ { + add_header Cache-Control "public, max-age=31536000, immutable" always; + add_header X-Content-Type-Options "nosniff" always; + try_files $uri =404; + } + + location ~* \.(?:woff2|mp4|webm|avif|webp|jpg|jpeg|png|svg)$ { + add_header Cache-Control "public, max-age=2592000" always; + add_header X-Content-Type-Options "nosniff" always; + try_files $uri =404; + } + + error_page 404 /404.html; + location = /404.html { + internal; + } +}