GuiderUtveckling och driftDriftsätt Node eller Python

Driftsätt en webbapp i Node.js eller Python

Din app i Node.js eller Python på Debian 13, under en egen användare som en härdad systemd-tjänst bakom Caddy med HTTPS, driftsatt med en git push och omladdad utan att tappa några förfrågningar.

Testad på Node.js 24.21.0 och Python 3.13.5 på Debian 13 (trixie) på en server hos Melonslab Uppdaterad 2 oktober 2026

Rekommenderad server för guiden

VC-S Micro · 2 vCPU · 8 GB Minne · 250 GB Lagring

Månadsvis, ingen bindningstid 7 dagars öppet köp

90 kr/mån

Beställ nu
På den här sidan

Det här sätter du upp

Du har en webbapp som fungerar på din laptop. Den här guiden lägger den på en server med HTTPS, så att git push live main från laptopen är allt som behövs för att driftsätta en ny version.

Appen körs under en egen användare, som en systemd-tjänst som startar när servern startar, startas om om den kraschar och inte kan skriva till sin egen kod. Caddy står framför, hämtar HTTPS-certifikatet och skickar varje besökare vidare till appen, som bara lyssnar på servern. Ingen Docker och ingen processhanterare: Debians egna verktyg räcker.

Välj appens språk. Varje steg som skiljer sig visar det du väljer:

Körmiljö

Med Node.js

Exemplet är en liten Express-app. Node.js utvecklas av sina bidragsgivare som ett projekt inom OpenJS Foundation och är öppen källkod under MIT-licensen. Paketen kommer från NodeSources paketförråd, deb.nodesource.com, som servern kontaktar vid varje apt update. npm hämtar appens beroenden från registry.npmjs.org och kollar där som standard också efter en nyare npm, vilket driftsättningen nedan stänger av.

Med Python

Exemplet är en liten Flask-app som körs av gunicorn, med en kommentar för FastAPI. Python utvecklas av sin community under Python Software Foundation, en amerikansk ideell organisation, och är öppen källkod under PSF-licensen. Det kommer från Debians egna paket. pip hämtar appens beroenden från PyPI, pypi.org, som Python Software Foundation driver.

Varje steg nedan har körts på en nyinstallerad Melonslab VC-P Alloy (2 vCPU, 8 GB) med Debian 13:

  • En git push från en dator någon annanstans driftsatte en ny version på ungefär 3 sekunder, och en push där beroendena inte gick att installera lämnade den körande versionen orörd.
  • Under varje driftsättning fick en slinga med 20 förfrågningar i sekunden via Caddy svar på varenda en: över 220 i rad, ingen misslyckades.
  • Appen såg varje besökares riktiga adress över IPv4 och IPv6, och en förfalskad X-Forwarded-For-header utifrån ignorerades.
  • Den härdade tjänsten kunde inte skriva till sin egen kod och inte läsa sin fil med hemligheter, men kunde fortfarande anropa andra HTTPS-webbplatser.
  • Appen, Caddy och brandväggen startade igen av sig själva efter en omstart av servern, och en uppdatering av Node.js installerades av sig själv via Debians automatiska uppdateringar.

Exempelappen i Node.js använde omkring 70 MB minne, Python-appen omkring 90 MB för gunicorn med två workers, och Caddy omkring 20 MB.

Innan du börjar

Du behöver:

  • en server med Debian 13, uppsatt som i säkerhetsguiden: inloggning med nyckel, automatiska säkerhetsuppdateringar och ufw;
  • en A-post och en AAAA-post för app.example.com som pekar på din server;
  • inget annat på port 80 och 443 på servern, eftersom Caddy tar dem;
  • din app i ett Git-repo på din egen dator.

Exemplen använder app.example.com för appen, 203.0.113.10 för servern, 198.51.100.7 för din egen adress och myapp för appens användare. Byt ut dem genomgående.

1. Installera körmiljön

Med Node.js

Debian 13 levereras med Node.js 20, som Node.js-projektet slutade stödja i april 2026. NodeSource bygger varje versionsserie av Node.js som Debian-paket, så du får den aktuella versionen med långtidsstöd, 24, och dess uppdateringar kommer via apt som allt annat. En versionshanterare som nvm installerar Node.js i en användares hemkatalog och uppdaterar det aldrig av sig själv: bra på en laptop, inte på en server.

Som root lägger du till NodeSources signeringsnyckel och paketförråd:

apt update
apt install -y curl
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /etc/apt/keyrings/nodesource.asc

Skapa /etc/apt/sources.list.d/nodesource.sources:

Types: deb
URIs: https://deb.nodesource.com/node_24.x
Suites: nodistro
Components: main
Signed-By: /etc/apt/keyrings/nodesource.asc

Installera Node.js, som har npm med sig, och Caddy och Git från Debian:

apt update
apt install -y nodejs caddy git
node -v
v24.21.0

Debians automatiska uppdateringar installerar bara från Debian. För att de också ska installera uppdateringar av Node.js skapar du /etc/apt/apt.conf.d/51unattended-upgrades-nodesource:

Unattended-Upgrade::Origins-Pattern {
    "site=deb.nodesource.com";
};

Kontrollera att filen läses in:

unattended-upgrade --dry-run --debug 2>&1 | grep 'Allowed origins'

Raden slutar med site=deb.nodesource.com.

Med Python

Debians Python får säkerhetsuppdateringar från Debians säkerhetsteam, och de installeras av sig själva på en server uppsatt som i säkerhetsguiden. Appens egna paket hamnar i en virtuell miljö (venv), en egen katalog, så att de aldrig blandas med Debians.

Som root:

apt update
apt install -y python3-venv caddy git curl
python3 -V
Python 3.13.5

2. Skapa appens användare

Appen får en egen användare, och koden ligger i användarens hemkatalog. Användarens skal är git-shell, så nyckeln som kan pusha kod dit kan inte göra något annat: inget skal, inga andra kommandon.

Som root:

useradd -m -s /usr/bin/git-shell myapp
install -d -m 700 -o myapp -g myapp /home/myapp/.ssh
install -m 600 -o myapp -g myapp /root/.ssh/authorized_keys /home/myapp/.ssh/authorized_keys
runuser -u myapp -- git init --bare -b main /home/myapp/app.git
runuser -u myapp -- mkdir /home/myapp/app

Andra och tredje raden låter nycklarna som loggar in som root pusha kod som myapp. app.git är ett bart repo: det tar emot dina pushar. app innehåller den utcheckade koden som körs.

Med Python

Skapa appens venv:

runuser -u myapp -- python3 -m venv /home/myapp/venv

3. Förbered appen

Tre saker i appen gör att den passar in här: den lyssnar bara på 127.0.0.1, så att inget når den utom via Caddy; den litar på besökarens adress som Caddy skickar vidare; och den gör klart öppna förfrågningar när den ombeds stänga.

Med Node.js

Exempelappen, server.js:

const express = require('express');

const app = express();
// Caddy on this server passes the visitor's address in X-Forwarded-For.
app.set('trust proxy', 'loopback');

app.get('/', (req, res) => {
  res.send(`Hello from Node.js ${process.version}. Your address: ${req.ip}, via ${req.protocol}\n`);
});

const port = process.env.PORT || 3000;
const server = app.listen(port, '127.0.0.1', () => {
  console.log(`Listening on 127.0.0.1:${port}`);
});

// On a restart, finish the requests that are open before exiting.
process.on('SIGTERM', () => {
  console.log('SIGTERM: finishing open requests');
  server.close(() => process.exit(0));
});

Med trust proxy satt till loopback blir req.ip besökarens adress när förfrågan kommer från Caddy på samma server, och headern ignoreras om den kommer från något annat håll. npm ci på servern installerar exakt det som står i package-lock.json, så checka in den filen.

Med Python

Exempelappen, app.py:

import sys

from flask import Flask, request
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
# Caddy on this server passes the visitor's address in X-Forwarded-For.
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)


@app.get("/")
def index():
    return f"Hello from Python {sys.version.split()[0]}. Your address: {request.remote_addr}, via {request.scheme}\n"

Och requirements.txt:

flask==3.1.*
gunicorn==26.*

gunicorn litar som standard på X-Forwarded-Proto från 127.0.0.1, så appen vet att besökaren kom via HTTPS, men tar inte besökarens adress från X-Forwarded-For. Utan ProxyFix ser appen varje besökare som 127.0.0.1. gunicorn gör redan klart öppna förfrågningar när den stängs.

FastAPI: kör den i gunicorn med uvicorns worker. Lägg till uvicorn-worker i requirements.txt, och byt i steg 5 ut app:app mot -k uvicorn_worker.UvicornWorker main:app. uvicorn litar som standard på headrarna från 127.0.0.1, så du behöver ingen ProxyFix. Det testades med FastAPI 0.142 i samma tjänst, omladdningen i steg 10 inräknad.

4. Lägg hemligheter i en miljöfil

Lösenord och API-nycklar hålls utanför Git, i en fil som bara root kan läsa. systemd läser den när appen startar och skickar värdena vidare som miljövariabler. Som root:

Med Node.js

install -m 600 /dev/null /etc/myapp.env
printf 'PORT=3000\nSESSION_SECRET=%s\n' "$(openssl rand -hex 32)" >> /etc/myapp.env

Med Python

install -m 600 /dev/null /etc/myapp.env
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 32)" >> /etc/myapp.env

Lägg till egna rader i samma form, NAMN=värde. Appen läser dem som vanligt (process.env.NAMN i Node.js, os.environ["NAMN"] i Python), men användaren myapp kan inte öppna själva filen.

5. Kör appen som en tjänst

Med Node.js

Skapa /etc/systemd/system/myapp.service:

[Unit]
Description=myapp (Node.js)
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/home/myapp/app
EnvironmentFile=/etc/myapp.env
Environment=NODE_ENV=production
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=2

# Hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
LockPersonality=yes
CapabilityBoundingSet=
SystemCallArchitectures=native
SystemCallFilter=@system-service
UMask=0077

[Install]
WantedBy=multi-user.target

Hoppa över MemoryDenyWriteExecute=yes, som finns med i andra listor över härdning: Node.js kompilerar JavaScript till maskinkod medan det körs och kraschar direkt med den.

Med Python

Skapa /etc/systemd/system/myapp.service:

[Unit]
Description=myapp (Python)
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/home/myapp/app
EnvironmentFile=/etc/myapp.env
ExecStart=/home/myapp/venv/bin/gunicorn --bind 127.0.0.1:8000 --workers 2 --no-control-socket app:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=2

# Hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
CapabilityBoundingSet=
SystemCallArchitectures=native
SystemCallFilter=@system-service
UMask=0077

[Install]
WantedBy=multi-user.target

app:app betyder objektet app i app.py. --no-control-socket stänger av den kontrollsocket som gunicorn 26 öppnar i användarens hemkatalog, och som den skrivskyddade hemkatalogen annars gör till ett fel vid varje start. ExecReload låter systemctl reload ladda om appen utan avbrott, som steg 10 visar.

Restart=on-failure startar appen igen om den kraschar: när den dödades med flit var den tillbaka 2 sekunder senare. Härdningen tar inget som appen behöver, och systemd-analyze security myapp ger tjänsten 1,7 till 1,8 i stället för 9,2 av 10, där lägre betyder mindre exponerad:

  • ProtectSystem=strict och ProtectHome=read-only gör hela filsystemet skrivskyddat för appen, dess egen kod inräknad, så att ett säkerhetshål i appen inte kan plantera kod som överlever en omstart. Om appen behöver skriva filer skapar du en katalog som /home/myapp/data som ägs av myapp och lägger till ReadWritePaths=/home/myapp/data.
  • NoNewPrivileges, den tomma CapabilityBoundingSet och SystemCallFilter hindrar appen från att skaffa sig fler rättigheter eller använda systemanrop som är till för administration.
  • RestrictAddressFamilies tillåter fortfarande vanliga nätverksanslutningar, så appen kan anropa API:er på internet.

Aktivera tjänsten utan att starta den än, eftersom det inte finns någon kod förrän efter första pushen:

systemctl daemon-reload
systemctl enable myapp

6. Driftsätt med git push

En hook i det bara repot körs efter varje push till main: den checkar ut koden, installerar beroendena och startar om appen.

För att hooken ska kunna starta om appen utan root låter du myapp köra exakt ett kommando. Skapa /etc/sudoers.d/myapp:

myapp ALL=(root) NOPASSWD: /usr/bin/systemctl reload-or-restart myapp.service
chmod 440 /etc/sudoers.d/myapp
visudo -c

visudo -c slutar med /etc/sudoers.d/myapp: parsed OK.

Med Node.js

Skapa /home/myapp/app.git/hooks/post-receive:

#!/bin/sh
# Deploy every push to main: check out the code, install dependencies, restart.
set -e
while read -r old new ref; do
  [ "$ref" = refs/heads/main ] || continue
  git --work-tree="$HOME/app" checkout -f -q main
  cd "$HOME/app"
  npm ci --omit=dev --no-audit --no-fund --no-update-notifier
  sudo systemctl reload-or-restart myapp.service
  echo "Deployed $new"
done

Med Python

Skapa /home/myapp/app.git/hooks/post-receive:

#!/bin/sh
# Deploy every push to main: check out the code, install dependencies, reload.
set -e
while read -r old new ref; do
  [ "$ref" = refs/heads/main ] || continue
  git --work-tree="$HOME/app" checkout -f -q main
  cd "$HOME/app"
  "$HOME/venv/bin/pip" install -q -r requirements.txt
  sudo systemctl reload-or-restart myapp.service
  echo "Deployed $new"
done
chown myapp:myapp /home/myapp/app.git/hooks/post-receive
chmod 755 /home/myapp/app.git/hooks/post-receive

reload-or-restart startar appen vid första pushen och laddar om eller startar om den efter det. set -e stoppar hooken vid första felet, så om beroendena inte går att installera fortsätter den körande versionen som förut.

Lägg nu till servern som remote på din egen dator, i appens repo, och pusha:

git remote add live myapp@app.example.com:app.git
git push live main

Med Node.js

remote: added 68 packages in 2s
remote: Deployed 6f3dbefd446c5710ef8d159e346792a021949777
To app.example.com:app.git
 * [new branch]      main -> main

Med Python

remote: Deployed 5bf1717456588772fdf3bdd2f91182806d035cb4
To app.example.com:app.git
 * [new branch]      main -> main

Om din gren heter master pushar du den med git push live master:main. Från och med nu är git push live main hela driftsättningen.

Kontrollera på servern att appen svarar lokalt:

Med Node.js

systemctl is-active myapp
curl -s http://127.0.0.1:3000/
active
Hello from Node.js v24.21.0. Your address: 127.0.0.1, via http

Med Python

systemctl is-active myapp
curl -s http://127.0.0.1:8000/
active
Hello from Python 3.13.5. Your address: 127.0.0.1, via http

7. Ställ Caddy framför

Ersätt /etc/caddy/Caddyfile med det här. E-postadressen är den som Let's Encrypt skriver till om ett certifikat får problem:

Med Node.js

{
    email you@example.com
}

app.example.com {
    reverse_proxy 127.0.0.1:3000 {
        # While the app restarts, hold requests for up to 5 seconds instead of answering 502.
        lb_try_duration 5s
    }
}

Med Python

{
    email you@example.com
}

app.example.com {
    reverse_proxy 127.0.0.1:8000 {
        # While the app restarts, hold requests for up to 5 seconds instead of answering 502.
        lb_try_duration 5s
    }
}
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy

Caddy hämtar certifikatet inom några sekunder, och journalctl -u caddy | grep 'certificate obtained' visar det. Fler appar på samma server får varsitt eget block, med eget namn och egen port.

Brandväggen behöver ha port 80 och 443 öppna, och HTTP/3 använder UDP 443. Om du följde säkerhetsguiden listar ufw status dem redan. Annars:

ufw allow 22/tcp
ufw allow 80,443/tcp
ufw allow 443/udp
ufw enable

Öppna inte appens egen port: den lyssnar på 127.0.0.1 och går inte att nå utifrån i alla fall.

8. Kontrollera besökarens adress

På din egen dator:

curl https://app.example.com/

Med Node.js

Hello from Node.js v24.21.0. Your address: 198.51.100.7, via https

Med Python

Hello from Python 3.13.5. Your address: 198.51.100.7, via https

Din egen adress och https betyder att appen litar på Caddys headrar. Över IPv6 visar appen din IPv6-adress. Försök nu förfalska headern:

curl -H 'X-Forwarded-For: 192.0.2.1' https://app.example.com/

Appen visar fortfarande din riktiga adress: Caddy ersätter headern som kommer utifrån i stället för att skicka den vidare.

9. Läs loggarna

Allt som appen skriver ut hamnar i journalen. Som root:

journalctl -u myapp -f

-f följer nya rader när de kommer, Ctrl+C avbryter. journalctl -u myapp --since today visar dagens rader och journalctl -u myapp -b allt sedan senaste uppstarten, driftsättningar och krascher inräknade. journalctl -u caddy innehåller Caddys loggar, certifikaten inräknade.

10. Driftsätt utan avbrott

En omstart stoppar appen och startar den igen. Mellan de två lyssnar inget på porten, och Caddy svarar 502 Bad Gateway. För att mäta det skickade vi 20 förfrågningar i sekunden via Caddy under driftsättningen.

Med Node.js

En ensam Node.js-process kan inte lämna över sin port till en ny, så varje driftsättning är en omstart. Det du kan göra är att göra den osynlig:

  • Utan lb_try_duration i Caddyfile fick 8 av 228 förfrågningar 502 under en driftsättning: ett glapp på ungefär 0,4 sekunder.
  • Med lb_try_duration 5s håller Caddy kvar nya förfrågningar tills den nya processen lyssnar: 222 av 222 fick svar, ingen misslyckades.
  • SIGTERM-hanteraren i server.js låter förfrågningar som redan pågår bli klara. Utan den avslutas Node.js direkt: en POST-förfrågan på 3 sekunder som skickades strax före en omstart fick 502. Caddy skickar själv om en avbruten GET, men inte en POST. Med hanteraren blev samma POST klar som vanligt.

Om appen håller anslutningar öppna länge, som WebSockets eller server-sent events, väntar server.close på dem, och systemd avslutar processen efter 90 sekunder, sitt standardvärde (inte testat här). De anslutningarna bryts vid varje driftsättning, och klienten måste ansluta igen.

Flera processer som turas om skulle stänga även det sista glappet, men det kräver en processhanterare eller en andra tjänst och lastbalansering, vilket är mer än de flesta appar behöver.

Med Python

gunicorn kan ladda om utan glapp. Vid systemctl reload myapp, som hooken kör, får gunicorns huvudprocess signalen HUP, startar nya workers med den nya koden och låter de gamla bli klara med sina förfrågningar innan de avslutas. Porten är öppen hela tiden.

  • En systemctl restart myapp, utan lb_try_duration i Caddyfile: 11 av 126 förfrågningar fick 502.
  • En systemctl reload myapp: 140 av 140 fick svar.
  • En git push av en ny version: 245 av 245 fick svar, och den nya texten syntes direkt efteråt.

Journalen visar omladdningen:

[INFO] Hang up: Master
Reloaded myapp.service - myapp (Python).
[INFO] Booting worker with pid: 16461
[INFO] Booting worker with pid: 16463
[INFO] Worker exiting (pid: 15393)
[INFO] Worker exiting (pid: 15394)

En omladdning läser in appens kod och paket på nytt, men inte gunicorn själv, som körs i huvudprocessen. När du har uppdaterat gunicorn, eller Python som i steg 12, använder du systemctl restart myapp. lb_try_duration i Caddyfile döljer även den omstarten.

För att mäta själv kör du det här på servern medan du pushar från din egen dator. Det räknar svaren per statuskod:

for i in $(seq 300); do curl -s -o /dev/null -w '%{http_code}\n' https://app.example.com/ & sleep 0.05; done | sort | uniq -c

11. Testa en omstart

systemctl reboot

Logga in igen och kontrollera:

systemctl is-active myapp caddy
curl -s https://app.example.com/

Båda är active, och appen svarar via Caddy. ufw startar av sig själv med sina regler.

12. Uppdatera körmiljön

Med Node.js

Uppdateringar inom Node.js 24 installeras av sig själva, tillsammans med Debians, tack vare filen från steg 1. Den körande appen behåller den gamla versionen tills den startas om. Så här ser du om den behöver det:

ls -l /proc/$(systemctl show -p MainPID --value myapp)/exe
lrwxrwxrwx 1 myapp myapp 0 Oct  2 19:05 /proc/17446/exe -> /usr/bin/node (deleted)

(deleted) betyder att en nyare Node.js är installerad. Starta om appen:

systemctl restart myapp

För att gå över till nästa serie med långtidsstöd, till exempel 26, ändrar du node_24.x till node_26.x i /etc/apt/sources.list.d/nodesource.sources och kör:

apt update
apt install -y nodejs

Pusha sedan en tom commit från din egen dator, så att npm ci installerar om beroendena för den nya versionen och appen startas om:

git commit --allow-empty -m "Redeploy on Node.js 26"
git push live main

Bytet från 24 till 26 och tillbaka testades på det här sättet.

Med Python

Säkerhetsuppdateringar av Python installeras av sig själva tillsammans med Debians. Den körande appen behåller den gamla versionen tills den startas om. Så här ser du om den behöver det:

ls -l /proc/$(systemctl show -p MainPID --value myapp)/exe

Om länken slutar med (deleted) är en nyare Python installerad. Starta om appen:

systemctl restart myapp

Appens egna paket uppdateras när du ändrar requirements.txt och pushar.

Nästa Debian-version har en ny Python-version, och en venv fungerar bara med den version den skapades med. Skapa om venv:en som root efter uppgraderingen:

rm -rf /home/myapp/venv
runuser -u myapp -- python3 -m venv /home/myapp/venv

Pusha sedan en tom commit från din egen dator, så att pip installerar paketen igen:

git commit --allow-empty -m "Rebuild venv"
git push live main

Avsluta med systemctl restart myapp på servern, eftersom en omladdning inte byter ut gunicorn själv. Att skapa om venv:en och pusha testades. En uppgradering till nästa Debian-version testades inte.

Felsökning

Pushen skriver ut sudo: unable to resolve host och ett servernamn. Serverns namn saknas i /etc/hosts. Driftsättningen fungerar ändå. Som root rättar du det med sed -i "s/^127\.0\.1\.1.*/127.0.1.1 $(hostname)/" /etc/hosts.

Pushen tas emot, men raden Deployed saknas och webbplatsen visar den gamla versionen. Något i hooken misslyckades, oftast installationen av beroendena, och raderna ovanför visar felet. Den gamla versionen fortsätter att köras, men den nya koden är redan utcheckad, så rätta felet och pusha igen innan appen startas om av någon annan anledning.

fatal: Interactive git shell is not enabled. Du loggade in som myapp med ssh. Den användaren tar bara emot Git. Logga in som root för allt annat.

Webbplatsen svarar 502 Bad Gateway i mer än några sekunder. Appen körs inte. systemctl status myapp och journalctl -u myapp -n 50 visar varför. En app som kraschar om och om igen syns som restart counter is at med ett stigande tal.

Appen kraschar med Read-only file system. Den försöker skriva någon annanstans än i /tmp. Ge den en katalog med ReadWritePaths=, som i steg 5, och kör sedan systemctl daemon-reload och systemctl restart myapp.

gunicorn loggar Control server error: [Errno 30] Read-only file system: '/home/myapp/.gunicorn'. --no-control-socket saknas i ExecStart. gunicorn 26 öppnar en kontrollsocket i användarens hemkatalog, som tjänsten inte kan skriva till.

Appen ser varje besökare som 127.0.0.1. Den litar inte på Caddys headrar: trust proxy i Express, ProxyFix i Flask, som i steg 3.

Kör det på din egen server

VC-S Micro

90 kr/mån

vCPU
2
Minne
8 GB
Lagring
250 GB
Trafik
10 TB
Standard
HDD · RAID 10
  • Full root-åtkomst
  • Nativt /64 IPv6
  • RAID-skyddad lagring
  • Malmö, Sverige
  • Månadsvis, ingen bindningstid
  • 7 dagars öppet köp
Alla guider