build: add Fly.io deployment (nginx static serving)
- Dockerfile: serve the static PWA via nginx:alpine on port 8080 - deploy/nginx.conf: mirror .htaccess (jsx/mjs MIME, no-cache for shell/sw/manifest/meta/dist, long-immutable cache for assets, security headers, SPA fallback) - fly.toml: internal_port 8080, force_https, scale-to-zero - .dockerignore: exclude .git/node_modules/tests from the image
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
node_modules
|
||||||
|
tools
|
||||||
|
tests
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
fly.toml
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
**/.DS_Store
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# SecureBit.chat is a static PWA (no backend). The committed build artifacts in
|
||||||
|
# dist/ are served as-is by nginx — matching the project's release workflow,
|
||||||
|
# where dist/ is rebuilt and committed for every release.
|
||||||
|
FROM nginx:1.27-alpine
|
||||||
|
|
||||||
|
# Replace the default nginx config with our static-serving config.
|
||||||
|
COPY deploy/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Serve the repository (src/, assets/, libs/, dist/, config/, logo/, sw.js, ...).
|
||||||
|
COPY . /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Fly.io health checks and routing target this port.
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# nginx config for serving SecureBit.chat (static PWA) on Fly.io.
|
||||||
|
# Mirrors the behavior of the Apache .htaccess: correct JS MIME for ES modules
|
||||||
|
# (.jsx/.mjs), no-cache for the app shell / service worker / versioning files,
|
||||||
|
# long-immutable cache for hashed/static assets, security headers, SPA fallback.
|
||||||
|
|
||||||
|
worker_processes auto;
|
||||||
|
events { worker_connections 1024; }
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# ES modules must be served as JavaScript. nginx's default mime.types maps
|
||||||
|
# .js but not .mjs/.jsx — declare them explicitly (this overrides .js too).
|
||||||
|
types {
|
||||||
|
application/javascript js mjs jsx;
|
||||||
|
text/css css;
|
||||||
|
application/json json map;
|
||||||
|
application/manifest+json webmanifest;
|
||||||
|
font/woff2 woff2;
|
||||||
|
font/woff woff;
|
||||||
|
image/svg+xml svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_types text/plain text/css application/javascript application/json image/svg+xml font/woff2;
|
||||||
|
|
||||||
|
# Decide Cache-Control from the request path. Keeping all add_header calls at
|
||||||
|
# one level avoids nginx's header-inheritance reset between blocks.
|
||||||
|
map $uri $sb_cache {
|
||||||
|
default "public, max-age=31536000, immutable";
|
||||||
|
~^/index\.html$ "no-cache, no-store, must-revalidate";
|
||||||
|
~^/$ "no-cache, no-store, must-revalidate";
|
||||||
|
~^/sw\.js$ "no-cache, no-store, must-revalidate";
|
||||||
|
~^/manifest\.json$ "no-cache, no-store, must-revalidate";
|
||||||
|
~^/meta\.json$ "no-cache, no-store, must-revalidate";
|
||||||
|
~^/dist/ "no-cache, no-store, must-revalidate";
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
listen [::]:8080;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Security headers (frame-ancestors complements the in-page CSP meta tag).
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header Content-Security-Policy "frame-ancestors 'none';" always;
|
||||||
|
add_header Cache-Control $sb_cache always;
|
||||||
|
add_header Service-Worker-Allowed "/" always;
|
||||||
|
|
||||||
|
# SPA-style fallback so unknown routes still load the app shell.
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Fly.io configuration for SecureBit.chat (static PWA served by nginx).
|
||||||
|
# Set `app` to your chosen Fly app name and `primary_region` to the closest
|
||||||
|
# region (e.g. fra=Frankfurt, ams=Amsterdam, waw=Warsaw, iad=US East).
|
||||||
|
# Run `fly platform regions` to list them.
|
||||||
|
|
||||||
|
app = "securebit-chat"
|
||||||
|
primary_region = "fra"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
|
||||||
|
[http_service]
|
||||||
|
internal_port = 8080
|
||||||
|
force_https = true # matches the app's upgrade-insecure-requests CSP
|
||||||
|
auto_stop_machines = "stop"
|
||||||
|
auto_start_machines = true
|
||||||
|
min_machines_running = 0 # scale to zero when idle (free-tier friendly)
|
||||||
|
|
||||||
|
[http_service.concurrency]
|
||||||
|
type = "requests"
|
||||||
|
soft_limit = 200
|
||||||
|
hard_limit = 250
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
size = "shared-cpu-1x"
|
||||||
|
memory = "256mb"
|
||||||
Reference in New Issue
Block a user