Italiano · English
Pannello di deploy self-hosted. Distribuisce da GitHub, da uno ZIP o da un Dockerfile, con l'output del build in diretta, database creati su richiesta e una manutenzione di Docker che il disco lo libera davvero.
Cosa fa · Requisiti · Avvio rapido · Configurazione · Distribuire un progetto · Database e servizi · Accesso di rete · Backup e ripristino · Avvio automatico · Registri privati · Il pannello, giorno per giorno · Sicurezza · Architettura · Sviluppo · Limiti noti
- Distribuisce qualsiasi cosa — Node.js, siti statici, Docker, Compose, o un
runtime personalizzato con i tuoi comandi. Un repository può dichiarare il
proprio contratto di deploy in
runpanel.json. - Deploy in diretta — l'output del build arriva al browser mentre il deploy gira, su una sola connessione SSE per progetto.
- Torna a una versione precedente — scegli un commit qualsiasi dalla cronologia del branch e distribuisci quello. Il progetto ci resta fermo, e l'auto-deploy viene sospeso invece di riportarlo avanti in silenzio.
- Database su richiesta — PostgreSQL, MySQL, Redis e MongoDB creati come container etichettati con i propri volumi, e la loro connection URL iniettata nell'app tramite un interruttore esplicito.
- Backup che si ripristinano — dump programmati dei database, dello store del pannello e della configurazione e dei volumi dei progetti, in un unico zip verificabile, su disco locale o su un bucket S3-compatibile.
- Torna su dopo un riavvio — genera e installa la unit systemd (o una riga
cron
@reboot), controlla che Docker stesso parta all'avvio, e rimette su i progetti e i servizi che hai indicato, nell'ordine che hai scelto. - Limita una porta alle reti che indichi — qualunque database o app può essere ristretto a indirizzi specifici, con le reti della macchina proposte come caselle da spuntare.
- Manutenzione — retention delle immagini per progetto, rilevamento degli orfani, e pulizia dei volumi che chiede prima di distruggere dati.
- Diagnostica — una pagina che dice cosa non va in questa installazione e cosa premere per sistemarlo.
| Node.js | 20 o superiore |
| Docker | per i runtime a container, i database creati dal pannello e i loro backup |
| PM2 | per i progetti con runtime nativo (node, static, custom) |
PM2 non è incluso: installalo globalmente con npm i -g pm2, oppure resta
sui runtime Docker. La pagina Diagnostica ti dice in quale dei due casi sei, e
cosa manca.
Il pannello gira su Linux, macOS e Windows. L'installazione automatica all'avvio è disponibile solo su Linux; le differenze di piattaforma sono trattate dove contano — l'avvio dei processi, il rilevamento dei binari, i permessi dei file.
git clone https://github.com/AlessTRV/RunPanel.git
cd RunPanel
npm install
npm run build
npm startApri http://localhost:3000. Alla prima visita ti viene chiesto di impostare una
password di amministratore, insieme al token di setup che il pannello scrive
nel proprio log all'avvio:
[RunPanel] Not set up yet. Enter this token on the setup screen:
3f9c1a...
Finché non esiste una password quell'endpoint deve restare aperto: il token è
ciò che impedisce a chi trova la porta per primo di prendersi il pannello, e qui
un amministratore ha una shell sull'host. A ogni riavvio ne viene emesso uno
nuovo, a meno che non lo fissi con RUNPANEL_SETUP_TOKEN.
Tutto sta in .env — vedi .env.example per la versione commentata.
L'ambiente viene validato all'avvio, così un errore fallisce subito con un
messaggio invece che alla prima richiesta che per caso ne ha bisogno.
| Variabile | Default | Cosa fa |
|---|---|---|
RUNPANEL_SECRET |
generata in data/.secret |
Chiave di cifratura, esadecimale, ≥64 caratteri |
RUNPANEL_DATA_DIR |
./data |
Store, repository, log, archivi |
PORT |
3000 |
Porta del pannello |
RUNPANEL_DB_DRIVER |
sqlite |
sqlite o postgres |
RUNPANEL_DB_FILE |
<data>/runpanel.db |
Solo con sqlite |
RUNPANEL_DATABASE_URL |
— | Solo con postgres |
RUNPANEL_PG_HOST _PORT _USER _PASSWORD _DATABASE |
— | Alternativa all'URL, a credenziali separate |
RUNPANEL_PG_SSL |
disable |
disable, require, no-verify |
RUNPANEL_PG_POOL_MAX |
10 |
Connessioni massime del pool |
RUNPANEL_TRUSTED_PROXY_HOPS |
0 |
Quanti reverse proxy stanno davanti — vedi Metterlo su internet |
RUNPANEL_SETUP_TOKEN |
generato a ogni avvio | Fissa il token del primo accesso |
RUNPANEL_DISABLE_SCHEDULERS |
spento | Silenzia i timer di fondo e i gate d'accesso |
RUNPANEL_DEV_ORIGINS |
— | Origini aggiuntive accettate da next dev |
Nessuna delle variabili
RUNPANEL_*raggiunge i progetti che distribuisci. Vengono rimosse dall'ambiente dei processi figli, perchéRUNPANEL_SECRETè la chiave con cui sono cifrati i segreti dei progetti stessi.
RUNPANEL_DB_DRIVER=postgres
RUNPANEL_DATABASE_URL=postgresql://user:pass@host:5432/runpanelLo schema e ogni query sono identici sui due driver — la suite di test gira su entrambi proprio per tenerli tali. Le migrazioni vengono applicate all'avvio, prima che la prima richiesta arrivi.
SQLite è il default e va benissimo: è il caso per cui il pannello è progettato. Postgres serve se lo store deve stare fuori dalla macchina, o se vuoi backup gestiti dalla tua infrastruttura invece che da qui.
- GitHub — collega un token nella pagina GitHub e scegli il repository da un
elenco, con i branch caricati dall'API. In alternativa incolla un URL
https://pubblico. - Upload ZIP — per il codice che non sta su GitHub. L'archivio viene spacchettato in-process, rifiutando le voci con traversal e i link, con un limite sia sull'archivio sia sul decompresso.
| Runtime | Cosa fa | Chi lo esegue |
|---|---|---|
node |
Rileva il package manager dal lockfile e usa gli script | PM2 |
static |
Serve una cartella di build | PM2 |
custom |
Solo i comandi che scrivi tu — qualsiasi linguaggio | PM2 |
docker |
Costruisce il Dockerfile del repository | Docker |
compose |
Avvia lo stack descritto dal compose file | Docker |
I preset sono punti di partenza per una forma di repository comune: Dockerfile, Next.js server, Vite/SPA statica, Python (uvicorn/gunicorn), Go. Ognuno porta con sé un runtime e i comandi che gli servono.
- In fase di creazione il pannello ne rileva uno guardando i file del repository, e puoi sceglierlo a mano per un repository la cui forma non si vede da fuori.
- In Impostazioni → Build e avvio puoi selezionare un preset e premere Applica i comandi: i tre campi vengono riempiti con i suoi, insieme al runtime, che con quei comandi viaggia sempre in coppia. Niente viene scritto finché non salvi.
La precedenza, dal basso: il preset rilevato, poi il runpanel.json del
repository, poi quello che hai impostato nel pannello.
Quello che a RunPanel serve sapere per distribuire un progetto. I campi sono neutri rispetto al runtime; ogni runtime li traduce a modo suo.
| Campo | Docker | PM2 / nativo |
|---|---|---|
buildEnv |
un --build-arg per voce |
ambiente durante install e build |
envFile |
scritto 0600 e montato in sola lettura | scritto nella directory di lavoro |
commands.release |
container usa-e-getta prima dell'avvio | comando singolo nella cartella del repo |
healthcheck |
sondato da RunPanel dopo l'avvio | identico |
runtime.restartPolicy |
--restart |
autorestart di PM2 |
runtime.memory / cpus / shmSize |
limiti del container | max_memory_restart dove applicabile |
docker.network / hostname / capAdd |
flag di docker run |
non applicabile |
Un repository può dichiarare come vuole essere distribuito:
{
"version": 1,
"commands": { "release": "npx prisma migrate deploy" },
"envFile": { "enabled": true, "path": "/app/.env" },
"healthcheck": { "path": "/api/health", "startPeriodSec": 45, "timeoutSec": 120 },
"runtime": { "restartPolicy": "unless-stopped", "memory": "2g" }
}Dove entrambi indicano un valore vince l'impostazione del pannello: l'operatore vede la macchina di destinazione, il repository no.
Alcuni campi sono solo del pannello e vengono ignorati se arrivano da un
repository: docker.mounts, docker.capAdd, docker.network,
docker.extraHosts e envFile.path. Il resto del contratto descrive come
costruire e avviare l'app, che è affare del repository; questi descrivono cosa
può raggiungere fuori dal proprio container, che è affare tuo. Scegliere un
runtime Docker è una scelta di isolamento, e un runpanel.json non deve poter
consegnare a sé stesso l'host. Quando ci prova, il log del deploy nomina i campi
che ha scartato.
Le variabili del progetto si gestiscono nella scheda Variabili e sono cifrate
a riposo. Vengono passate al processo o al container, e — se envFile è
attivo — scritte anche in un file che l'app può leggere da sola.
Le variabili con prefisso NEXT_PUBLIC_, VITE_, PUBLIC_ o REACT_APP_
vengono passate anche al build, non solo al runtime. I frontend le incorporano
nel bundle client, quindi fornirle solo a runtime spedisce il valore sbagliato —
o fa fallire un Dockerfile che le pretende.
Ogni progetto ha un URL webhook con un segreto proprio. Attivando Deploy automatico, un push sul branch configurato avvia un deploy. Le firme sono verificate con HMAC-SHA256 e confronto a tempo costante; le consegne, accettate o rifiutate, restano nello storico con il motivo.
Con un account GitHub collegato il webhook si registra da solo: attivare
l'interruttore lo crea sul repository con l'URL, il segreto, il content type
application/json e il solo evento push già impostati. Nessuno dei quattro è
una scelta — seguono tutti dal progetto — e sbagliarne uno a mano falliva in
silenzio. Spegnere l'auto-deploy lo disattiva senza cancellarlo, così lo storico
delle consegne su GitHub resta.
La sezione mostra anche cosa non va: token assente, repository non riconosciuto, indirizzo del pannello che GitHub non può raggiungere, webhook disallineato, ultima consegna rifiutata. Il pulsante Invia ping chiede a GitHub una consegna vera — passa da DNS, firewall, TLS e firma, cioè le parti che si rompono davvero.
Perché il pannello sappia quale indirizzo scrivere su GitHub, imposta Indirizzo pubblico in Account → Preferenze. Lasciato vuoto viene dedotto dalla richiesta, il che vale finché apri il pannello dallo stesso indirizzo da cui lo raggiunge GitHub.
Pannello raggiungibile solo via VPN o Tailscale? Allora i webhook non arrivano: GitHub consegna da internet e non fa parte della tua rete privata. Un indirizzo
100.64–100.127.x.xviene rifiutato subito; un nome MagicDNS*.ts.netviene segnalato, perché funziona solo se lo pubblichi contailscale funnel. Non serve esporre niente: usa il controllo periodico qui sotto.
Un webhook ha bisogno che GitHub apra una connessione verso questa macchina, e per moltissime installazioni self-hosted questo non può succedere: dietro NAT senza porte aperte, su una rete Tailscale o WireGuard, su un portatile. Lì non c'è niente da configurare meglio — la consegna fallisce prima di arrivare, e nei log del pannello non resta nulla, perché il problema è la direzione della connessione.
Quindi il pannello può anche chiedere invece di farsi avvisare. Nelle impostazioni del progetto, Deploy automatico → Come parte il deploy:
- Webhook — GitHub avvisa a ogni push, il deploy parte subito. Richiede un pannello raggiungibile da internet.
- Controllo periodico — RunPanel guarda il branch a intervalli e distribuisce quando il commit cambia. Una richiesta in uscita, nessuna in entrata: funziona ovunque ci sia una connessione a internet.
L'intervallo è in Account → Preferenze, da 30 secondi a 30 minuti (predefinito 5
minuti). Un branch fermo risponde 304 Not Modified grazie all'ETag, e GitHub
non conta le 304 nel limite orario di richieste: anche 30 secondi costano
praticamente nulla. Il ritardo massimo di un deploy è un intervallo.
Il primo giro dopo l'attivazione registra il commit corrente senza
distribuirlo — «distribuisci quello che arriva», non «distribuisci quello che
c'è adesso» — e i deploy così avviati compaiono nello storico con trigger
poll. Scegliendo il controllo periodico il webhook eventuale viene
disattivato, perché due trasporti attivi distribuirebbero lo stesso commit due
volte.
Il webhook resta configurabile a mano — URL e Secret sono lì da copiare — per i repository che il token non amministra o per un pannello senza account collegato.
Il pulsante accanto a Deploy apre la cronologia del repository: si sceglie il branch, si sceglie il commit, e il progetto viene ricostruito da lì. Serve per quando il commit appena distribuito è quello che ha rotto l'app, e l'alternativa sarebbe un revert su GitHub e un altro push — una correzione che ha bisogno del repository proprio mentre la produzione è ferma.
La scelta resta. Il progetto si ferma su quel commit: ogni deploy ricostruisce quello, l'header lo dice con un'etichetta, e l'auto-deploy viene sospeso invece di riportare il progetto in avanti al primo push. Le consegne che arrivano nel frattempo restano nello storico come ignorate, con il motivo — un webhook che non distribuisce deve dire perché. Torna all'ultimo commit scioglie il blocco e distribuisce di nuovo la testa del branch.
Scegliere un branch diverso qui cambia il branch del progetto: da lì in avanti è quello che webhook e controllo periodico seguono. L'elenco dei commit arriva dall'API di GitHub, quindi vuole un account collegato; senza, o per un commit più vecchio degli ultimi cento, c'è un campo dove incollare lo SHA.
Un avvertimento che il pannello ripete prima di procedere, perché è l'unico modo in cui questa funzione rompe un'app senza che si veda: le migrazioni del database non tornano indietro. Se la versione ripristinata si aspetta uno schema più vecchio di quello che c'è, può non partire.
- Coda — i deploy dello stesso progetto sono serializzati, e quelli che si accavallano vengono accorpati: una raffica di push produce un deploy, non sei.
- Sorgente — clone o pull del branch, con il commit registrato; oppure, se il progetto è fermo su un commit, il ripristino di quello.
- Contratto — preset rilevato,
runpanel.json, impostazioni del pannello. - Build — install e build secondo il runtime, con l'output in streaming.
- Release command — eseguito una volta prima dell'avvio, in un container usa-e-getta o nella cartella del repository. Se fallisce, la nuova versione non parte: è il posto giusto per le migrazioni.
- Avvio — PM2 o Docker, con la policy di riavvio e i limiti del contratto.
- Health check — RunPanel sonda l'app finché non risponde. Senza, un'app che parte e muore un secondo dopo risulterebbe distribuita con successo.
Il Re-Build è la variante che pulisce prima: rimuove node_modules, .next,
venv e simili secondo il runtime, e poi rifà tutto dal codice già presente.
Un servizio è un database gestito dal pannello: un container etichettato, con il proprio volume nominato e le credenziali cifrate.
| Motore | Versioni offerte |
|---|---|
| PostgreSQL | 18, 17, 16, 15, 14 |
| MySQL | 9, 8 |
| Redis | 8, 7, 6 |
| MongoDB | 8.0, 7.0 |
Sono le major che l'immagine ufficiale supporta oggi, meno i canali di anteprima e quelli a cadenza breve. Un servizio già esistente non viene toccato quando l'elenco cambia: la versione è salvata per riga e le ricreazioni usano quella.
Un servizio può essere collegato a un progetto, e allora gli fornisce la propria connection URL in una variabile d'ambiente. Il collegamento ha un interruttore esplicito:
- Acceso, il valore iniettato vince su una variabile che avessi definito a mano, e il pannello lo scrive nel log del deploy.
- Spento, il progetto usa le proprie variabili e il pannello non tocca niente.
La variabile ha un nome modificabile (DATABASE_URL per default, derivato dal
tipo). Due collegamenti attivi nello stesso progetto non possono rispondere alla
stessa chiave: il secondo viene rifiutato nominando il primo e proponendo un nome
alternativo, invece di sovrascriverlo in silenzio.
L'host dipende da chi si collega, e il pannello mostra la riga giusta per
ognuno: un container sulla rete del progetto raggiunge il servizio per nome del
container e sulla porta interna; tutto il resto — un processo PM2, un container
su bridge, il tuo psql — passa dalla porta pubblicata sull'host.
Un servizio può anche essere autonomo, senza progetto: allora non inietta niente e resta un database che gestisci dal pannello.
Un server di database ne ospita più d'uno. La pagina del servizio li elenca leggendoli dal motore, non da una lista del pannello, e permette di crearli ed eliminarli con la connection URL pronta per ognuno.
Di default RunPanel pubblica una porta come fa Docker — -p 5433:5432, senza
indirizzo di bind, su tutte le interfacce. È comodo, e vuol dire che un database
creato dal pannello risponde a tutto ciò che sta sulla LAN, e a internet se la
macchina ha un indirizzo pubblico.
Accendendo Chi può collegarsi, su un servizio o su un progetto, cambia così:
- il container viene ricreato (o l'app riavviata) pubblicando su
127.0.0.1e su una porta che il pannello alloca; - il pannello occupa la porta che i tuoi client già conoscono, e inoltra solo le connessioni dagli indirizzi che hai elencato;
127.0.0.1e::1sono sempre consentiti e non si possono togliere: da lì arrivano la sonda di health check, i dumper dei backup e qualsiasipsqlsulla macchina.
Le regole sono singoli indirizzi o intervalli CIDR, IPv4 o IPv6. Il pannello
legge le interfacce della macchina e le propone come caselle da spuntare,
etichettate: la LAN, gli intervalli VPN (la 100.64.0.0/10 di Tailscale è
riconosciuta, visto che la sua interfaccia dichiara una /32 inutile), e gli
switch virtuali. Chi viene respinto compare nella pagina con un pulsante
Consenti, perché altrimenti una connessione rifiutata e un database fermo si
somigliano troppo visti dall'altra parte.
Due cose da sapere prima di accenderlo:
- Fallisce in chiusura. La porta è tenuta aperta dal processo del pannello. Se il pannello non gira, la porta è chiusa. Per un controllo di sicurezza è la direzione giusta in cui rompersi, ma è un cambiamento: prima il database di un'app restava raggiungibile anche a pannello spento.
- Un'app sulla rete del progetto non è toccata. Raggiunge il proprio database
per nome del container su
runpanel-net-<slug>, che non passa mai dal gate. Ci passa invece il traffico che arriva dahost.docker.internal, ed è il motivo per cui fra i suggerimenti ci sono anche le sottoreti virtuali.
Non viene offerto dove non potrebbe essere onesto: un progetto Compose
pubblica le porte da un file che è tuo e RunPanel non lo riscrive, e un container
su network: host non ha una porta pubblicata da spostare. In entrambi i casi lo
dice, invece di mostrare un interruttore che non farebbe niente.
Per un processo nativo il pannello passa anche HOST/HOSTNAME e, per le CLI di
cui conosce la sintassi, il flag di bind. Un'app che ignora tutto questo resta su
tutte le interfacce alla porta spostata — quindi il pannello lo verifica, e lo
scrive nella pagina invece di mostrare una restrizione che non è tale.
Una policy dice cosa salvare, ogni quanto, e quanto tenerne.
| Target | Cosa comprende |
|---|---|
| Un servizio | il dump del database, per intero o di un singolo database |
| Tutti i servizi | selettore: comprende quelli che creerai domani |
| Un progetto | configurazione, volumi, repository — a scelta |
| Tutti i progetti | stesso criterio |
| Il pannello | lo store di RunPanel, con o senza la chiave di cifratura |
I target sono selettori invece che elenchi fissi, così «ogni database» continua a significare quelli che esistono quando il backup parte.
Ogni dump gira dentro il container a cui appartiene, che è l'unico modo di
garantire che client e server siano della stessa versione: un pg_dump indietro
di una major produce un file che pg_restore rifiuta, e lo produce senza
lamentarsi. Lo store SQLite di RunPanel viene catturato con VACUUM INTO e poi
verificato con PRAGMA integrity_check, mai copiato — una copia presa sotto WAL
omette in silenzio le scritture più recenti.
- Disco locale —
data/backups/archives/<anno>/<mese>, con permessi 0600. - S3-compatibile — AWS S3, Cloudflare R2, MinIO, Backblaze B2. La firma è
SigV4 calcolata in casa, senza SDK. L'endpoint accetta
https://ovunque ehttp://solo verso un indirizzo privato: un archivio contiene ogni variabile d'ambiente del pannello, e verso internet è il TLS a proteggerlo.
L'archivio è un normale zip con un manifest.json e un checksums.txt in
formato sha256sum -c, così si può verificare e spacchettare senza RunPanel. Le
variabili d'ambiente e le credenziali dei servizi al suo interno sono ricifrate
con la chiave di questo pannello; includere la chiave stessa è una scelta a
parte, ed esplicita.
| Pianificazione | cron a cinque campi più la famiglia @daily, nel fuso che scegli |
| Retention | numero, età e dimensione totale, insieme — l'archivio valido più recente non viene mai raccolto |
| Ripristino | guidato, con un backup automatico pre-ripristino che interrompe il ripristino se fallisce |
Il ripristino mostra il contenuto dell'archivio e ti fa scegliere voce per voce. Lo store del pannello è l'unica cosa che non viene ripristinata a caldo: un file che questo processo tiene aperto non può essere sostituito sotto di lui, quindi il database ripristinato viene messo da parte e entra in servizio al riavvio successivo, con il precedente conservato accanto.
La pagina Autostart riferisce cosa questa macchina fa già e genera quello che non
fa: una unit systemd con percorsi assoluti, ordinata dopo docker.service e che
lo richiede, oppure uno script @reboot supervisionato quando systemd non c'è.
Se RunPanel gira da root la installa; altrimenti produce un unico blocco da
incollare. Non esegue mai systemctl start: metterebbe un secondo pannello sulla
porta accanto a quello in esecuzione.
Controlla anche le cose che rendono inutile l'avvio automatico quando mancano: se
Docker stesso è abilitato all'avvio, e se pm2 save è mai stato eseguito — senza,
PM2 non rimette su niente dopo un riavvio, anche quando il pannello torna.
Dentro il pannello, ogni progetto e ogni servizio ha un interruttore, un ordine, un ritardo, e se aspettare che risponda prima di avviare il successivo. Il riconciliatore che applica tutto questo all'avvio è una passata di riparazione: aspetta che i riavvii automatici di Docker si assestino e avvia solo ciò che è ancora giù, e non innesca mai un build.
Le credenziali dei registri Docker si inseriscono dal pannello, sono cifrate a
riposo e riscritte nella configurazione di Docker all'avvio — la directory dei
dati può sopravvivere a una ricostruzione del container, e un file di
autenticazione mancante si manifesta come un pull access denied che non dice
niente di utile.
| Pagina | A cosa serve |
|---|---|
| Panoramica | stato di tutto, in una schermata |
| Progetti | deploy, log in diretta, storico, variabili, file, terminale, impostazioni |
| Servizi | database gestiti, i loro database interni, collegamenti ai progetti |
| Monitor | CPU, memoria, carico, uptime dell'host e dei container |
| Storage | cosa occupa il disco: immagini, volumi, archivi, repository |
| Backup | policy, esecuzioni, archivi, ripristini |
| Autostart | cosa torna su dopo un riavvio |
| Diagnostica | cosa manca a questa installazione e cosa premere |
| GitHub | token, repository, branch |
| Account | password, sessioni per dispositivo, preferenze |
Dettagli che tornano utili:
- Terminale — una shell vera sulla cartella del progetto o dentro il suo container, con le sessioni inattive raccolte automaticamente.
- File — un browser confinato alla cartella del progetto, che risolve i symlink prima di aprire qualsiasi cosa.
- Log — in diretta via SSE mentre il deploy gira, e su file dopo.
- Sessioni — una per dispositivo, revocabili singolarmente dall'account.
- Preferenze — intervallo di aggiornamento (2, 5, 10 secondi), fuso orario, cinque temi di accento.
- Palette comandi e navigazione da tastiera; su mobile una barra in basso e il menu che entra da destra.
- Password con hash bcrypt; le sessioni sono per dispositivo e memorizzate come SHA-256 del cookie, così una copia del database non può essere riusata
- Il setup iniziale richiede il token stampato all'avvio, così un pannello non ancora reclamato non può esserlo da chi arriva per primo
- Il limite sui tentativi di accesso sopravvive a un riavvio, conta in modo atomico, ed è legato a un indirizzo solo dove un proxy configurato lo garantisce
- Ogni route
/apiviene rifiutata senza sessione daproxy.tsprima ancora di essere raggiunta, in aggiunta al controllo di ciascun handler - Variabili d'ambiente dei progetti, credenziali dei servizi e dei registri cifrate a riposo (AES-256-GCM)
- La configurazione di RunPanel non raggiunge mai i progetti distribuiti
- Firme dei webhook verificate con HMAC-SHA256 e confronto a tempo costante
- Le operazioni sui file risolvono i symlink e restano confinate al progetto; gli upload ZIP sono spacchettati in-process, rifiutando voci con traversal e link, con un limite sia sull'archivio sia sul decompresso
- Gli URL dei repository devono essere
https://pubblici, e il token GitHub è allegato soltanto alle richieste verso GitHub - I comandi esterni passano sempre da un array di argomenti, mai da una stringa di shell
- Qualunque porta pubblicata può essere limitata a reti indicate, davanti a un ascoltatore spostato su loopback perché non ci sia modo di aggirarla
Termina il TLS davanti al pannello e digli dietro quanti proxy si trova:
RUNPANEL_TRUSTED_PROXY_HOPS=1Senza, il pannello non può credere a nessun indirizzo del client —
X-Forwarded-For viene esteso da ogni hop, quindi la prima voce è quella scelta
dal client — e ripiega su un unico limite di accesso valido per tutto l'account.
Il cookie di sessione è marcato Secure nelle build di produzione, e i browser
lo accettano solo su HTTPS o su localhost.
app/
(auth)/login setup iniziale e accesso
(panel)/ panoramica, progetti, servizi, monitor, storage, backup, impostazioni
api/ handler REST; /projects/:id/stream è il canale SSE
lib/
db/ schema Kysely, migrazioni, entrambi i dialetti
deploy-contract.ts il contratto, il suo parser e i controlli preliminari
ip-access.ts confronto CIDR, e le reti della macchina
service-versions.ts quali versioni dei motori sono offerte
hooks/ useProjectStream (SSE), useResource (polling)
services/
deploy-pipeline.ts l'orchestratore del deploy
deploy-queue.ts serializzazione e accorpamento per progetto
access-gate.ts il gate TCP davanti a una porta limitata
backup/ policy, dump, archivi, destinazioni, ripristino
autostart/ rilevamento, generazione della unit, riconciliazione
docker/ cli, etichette di proprietà, immagini, volumi, statistiche, gc
builders/ node, docker, static, compose, custom
process-drivers/ pm2, docker, compose
service-templates/ postgresql, mysql, redis, mongodb
tests/ suite end-to-end, un server isolato ciascuna
data/ stato a runtime (in gitignore)
Tutte le route /api richiedono una sessione, tranne l'accesso e i webhook. Le
uniche superfici pensate per l'esterno sono il webhook GitHub per progetto e i
canali SSE, che comunque richiedono la sessione.
npm run dev # server di sviluppo
npm run typecheck
npm run lint
npm test # suite completa
npm run test:quick # salta le suite che richiedono DockerIl runner dichiara cosa la macchina è in grado di fare e salta il resto invece
di fallire: le suite che richiedono un daemon Docker, e quella sul runtime nativo
che richiede un PM2 vero (npm i -g pm2). Entrambe compaiono come SKIP nel
riepilogo, così una corsa verde non nasconde mai terreno non testato.
Ogni suite riceve un server tutto suo su una directory dati temporanea. Alcune sono standalone: caricano direttamente il modulo da testare, senza server né daemon, e coprono le regole pure — confronto CIDR, contratto di deploy, firma SigV4, iniezione delle variabili, versioni dei motori.
Le suite Postgres richiedono un database:
docker run -d --name rp-test-pg -e POSTGRES_PASSWORD=test \
-e POSTGRES_USER=runpanel -e POSTGRES_DB=runpanel_test \
-p 55432:5432 postgres:18-alpine
RUNPANEL_TEST_PG_URL=postgresql://runpanel:test@127.0.0.1:55432/runpanel_test npm testLe icone sono raccolte in un sottoinsieme generato. Dopo averne aggiunta una,
npm run icons — anche se predev e prebuild lo eseguono già per te, e il
build fallisce su un nome di icona che non esiste.
- Il tema chiaro non è incluso; il livello dei token è strutturato per accoglierlo.
- Le restrizioni sulle porte sono applicate dal processo del pannello, quindi non sopravvivono al suo arresto — la porta semplicemente si chiude. Un insieme di regole che deve valere anche a pannello spento richiede in più un firewall sull'host.
- Il ripristino dello store Postgres di RunPanel è rifiutato dal pannello:
l'archivio contiene il dump e il comando
pg_restoreesatto, da eseguire con il pannello fermo. - Il ripristino non confronta le versioni: un dump preso da una major e ripristinato su un'altra viene accettato e fallisce durante l'esecuzione, con la copia di sicurezza già presa.
- L'installazione automatica all'avvio è solo per Linux; altrove il pannello mostra cosa fare a mano.
MIT