Infrastructure-as-Code per trasformare un'istanza OCI Ampere A1 in una piattaforma DevOps completa con reverse proxy HTTPS, registry Docker privato, Git server con CI/CD, monitoring e backup automatico. Tutto orchestrato da script Bash idempotenti e container Docker.
Panoramica · Architettura · Servizi · Script · Guide · Avvio Rapido · Struttura · CI/CD · License
Questo repository contiene tutto il necessario per ricostruire da zero un server DevOps su OCI ARM64 (Ampere A1, Always Free). Dopo l'esecuzione degli script, il server ospita:
- Reverse proxy con certificati HTTPS automatici (Traefik + Let's Encrypt)
- Docker Registry privato con autenticazione
- Git server con CI/CD integrato (Forgejo + 2 runner ARM64)
- Monitoring real-time con allarmi e notifiche Telegram (Netdata v2)
- Piattaforma applicativa pronta per ospitare progetti (CineBase già in produzione)
- Backup automatico su OCI Object Storage
Tutto su risorse Always Free (4 OCPU, 24 GB RAM, ARM Ampere A1, ~100 GB boot volume).
Configurare manualmente un server con reverse proxy, certificati, registry, Git, CI/CD e monitoring richiede ore di lavoro manuale soggetto a errori. Questo repository automatizza l'intero processo con script Bash testati, documentati in italiano, con pattern di idempotenza e gestione centralizzata delle variabili d'ambiente.
- ARM64 nativo: tutte le immagini Docker pinnate sono multi-arch, testate su Ampere A1
- Script idempotenti: ogni script può essere eseguito N volte senza rompere nulla
- Nessun segreto hardcodato: tutte le credenziali in
.env(gitignorato), template in.env.example - Documentazione in italiano: ogni script è commentato, ogni concetto è spiegato
- Pattern testati in produzione: Traefik con file provider per basic auth, Docker network a due livelli, runner CI/CD con automount
graph TD
NET[Internet] --> TF[Traefik v3<br/>:80 → :443<br/>Let's Encrypt]
TF --> LAND[Landing page<br/>nginx :80<br/><DOMINIO> / www.<DOMINIO>]
TF --> PORT[Portainer CE<br/>:9000]
TF --> FORG[Forgejo v15<br/>git.<DOMINIO> :3000]
TF --> NETD[Netdata v2<br/>monitor.<DOMINIO> :19999 🔒]
TF --> CINE[CineBase<br/>www.<DOMINIO_APP><br/>api.<DOMINIO_APP> :8080]
TF --> ANAL[Analytics island<br/>comments.<SITO_WEB><br/>analytics.<SITO_WEB>]
FORG --> PGFORGE[(PostgreSQL 16<br/>forgejo)]
FORG --> RUN1(Runner 1)
FORG --> RUN2(Runner 2)
NETD --> TG(Telegram Bot)
CINE --> MARIA[(MariaDB 10.11)]
ANAL --> WAL[Waline :8360]
ANAL --> UMA[Umami :3000]
WAL --> PGANA[(PostgreSQL 16<br/>waline + umami)]
UMA --> PGANA
REG[Registry :5000 🔒] --> BKP
PGFORGE --> BKP[backup.sh<br/>cron 3:00]
PGANA --> BKP
BKP --> OCI[OCI Object Storage<br/>s1-backup 10 GB]
style TF fill:#1a365d,color:#fff
style BKP fill:#744210,color:#fff
style OCI fill:#1a4731,color:#fff
| Layer | Tecnologia | Versione |
|---|---|---|
| Cloud | OCI Always Free ARM64 | Ampere A1, 4 OCPU, 24 GB RAM |
| OS | Ubuntu Server | 24.04.4 LTS |
| Container Runtime | Docker Engine | 28+ |
| Reverse Proxy | Traefik | 3.7.4 |
| Containers UI | Portainer CE | 2.39.3-alpine |
| Registry | Docker Registry | 3.1.1 |
| Git Server | Forgejo | 15 |
| CI/CD Runners | Forgejo Runner | 12 |
| Database | PostgreSQL 16 + MariaDB 10.11 | - |
| Monitoring | Netdata | v2 stable |
| Notifiche | Telegram Bot API | - |
| Backup | OCI CLI (script Oracle) | - |
| Application | CineBase (.NET 10) | github.com/malafronte/cinebase |
| Comment System | Waline (PostgreSQL) | waline.js.org |
| Web Analytics | Umami (PostgreSQL) | umami.is |
| Landing | Nginx | malafronte.eu welcome page |
| Dominio | Servizio | Container | Porta | Accesso |
|---|---|---|---|---|
traefik.<DOMINIO> |
Dashboard Traefik | traefik |
80/443 | 🔒 Basic auth |
portainer.<DOMINIO> |
Portainer | portainer |
9000 | 🔒 Login Portainer |
registry.<DOMINIO> |
Docker Registry | registry |
5000 | 🔒 docker login |
registry-ui.<DOMINIO> |
Registry UI | registry-ui |
80 | 🔒 Basic auth |
git.<DOMINIO> |
Forgejo | forgejo |
3000 | Pubblico (registrazione disabilitata) |
monitor.<DOMINIO> |
Netdata | netdata |
19999 | 🔒 Basic auth |
www.<DOMINIO_APP> |
CineBase Frontend | cinebase-web |
8080 | Pubblico |
api.<DOMINIO_APP> |
CineBase API | cinebase-filmapi |
8080 | Pubblico (health check) |
comments.<SITO_WEB> |
Waline (commenti) | analytics-waline |
8360 | Pubblico (login obbligatorio) |
analytics.<SITO_WEB> |
Umami (analytics) | analytics-umami |
3000 | 🔒 Login admin |
<DOMINIO> |
Landing page | landing |
80 | Pubblico |
www.<DOMINIO> |
Redirect → <DOMINIO> |
landing |
80 | Pubblico (301) |
Gli script vanno eseguiti in ordine. Ogni script è idempotente (puoi rieseguirlo N volte) e carica le variabili da .env.
| # | Script | Cosa fa | Prerequisiti |
|---|---|---|---|
| 01 | 01-prerequisiti.sh |
Pacchetti di sistema (ca-certificates, curl, apache2-utils, jq) | Nessuno |
| 02 | 02-installa-docker.sh |
Docker Engine + Compose plugin su ARM64 | 01 |
| 03 | 03-crea-struttura.sh |
Directory ~/docker/ e sottocartelle |
01 |
| 04 | 04-setup-traefik.sh |
Traefik con Let's Encrypt, dashboard protetta, file provider | 02, 03 |
| 05 | 05-setup-portainer.sh |
Portainer CE per gestione container | 04 |
| 06 | 06-setup-registry.sh |
Docker Registry privato con htpasswd | 04 |
| 06b | 06b-setup-registry-ui.sh |
Registry UI con autenticazione condivisa | 06 |
| 07 | 07-setup-forgejo.sh |
Forgejo + PostgreSQL 16 + 2 runner | 04 |
| 07b | 07b-setup-forgejo-runners.sh |
Registrazione e avvio runner CI/CD | 07 |
| 08 | 08-setup-netdata.sh |
Netdata v2 + allarmi + Telegram + check backup | 04 |
| 09 | 09-setup-cinebase.sh |
Stack CineBase (primo deploy) | 04, 06, 07 |
| 10 | 10-setup-backup.sh |
Backup automatico su OCI Object Storage | OCI CLI |
| 11 | 11-setup-analytics.sh |
Waline + Umami + PostgreSQL (commenti e analytics) | 04 |
| 12 | 12-setup-landing.sh |
Landing page malafronte.eu con redirect www | 04 |
# Copia .env.example in .env e compila con i valori reali
cp tenant/servers/s1/.env.example tenant/servers/s1/.env
nano tenant/servers/s1/.env
# Copia sul server ed esegui
scp -i ${S1_SSH_KEY} tenant/servers/s1/scripts/08-setup-netdata.sh ${S1_SSH_USER}@${S1_IP}:~/scripts/
ssh -i ${S1_SSH_KEY} ${S1_SSH_USER}@${S1_IP} "bash ~/scripts/08-setup-netdata.sh"# Genera un secret da 40 caratteri hex
SECRET=$(openssl rand -hex 20)
# Registra il runner (da dentro il container Forgejo)
UUID=$(docker exec -u 1000:1000 forgejo forgejo forgejo-cli actions register \
--name runner1 \
--secret "$SECRET")
# UUID e token vanno in runner-config.yml
echo "UUID: $UUID Secret: $SECRET"| Guida | Descrizione |
|---|---|
guida-server-completo.md |
Guida operativa completa: tutti i servizi, configurazione, comandi |
guida-traefik-completa.md |
Traefik dalla A alla Z: router, services, middlewares, certs, basic auth |
guida-cicd-forgejo-actions.md |
CI/CD con Forgejo Actions: runner, workflow, secrets, errori, fix |
guida-primo-deploy-cinebase.md |
Primo deploy CineBase: merge .env, build manuale, fix post-avvio |
guida-deploy-waline-umami.md |
Deploy Waline + Umami + PostgreSQL: DNS, script, post-deploy, backup |
guida-backup-oci.md |
Backup automatico su OCI Object Storage: bucket, IAM, script, retention, restore |
guida-telegram-bot.md |
Bot Telegram: creazione, curl, emoji, Netdata, check-backup |
lessons-learned.md |
Lezioni apprese: cosa rifarei diversamente se ricominciassi da zero |
oci-always-free-risorse.md |
Risorse OCI Always Free: limiti, quote, strategia |
sicurezza-oci-firewall.md |
Firewall OCI: security list, porte, regole |
rotazione-chiavi-ssh.md |
Rotazione chiavi SSH |
rotazione-chiavi-oci-cli.md |
Rotazione chiavi API OCI |
.
├── tenant/
│ ├── .secrets/ # 🔒 Gitignorato — chiavi SSH, API key OCI
│ │ ├── s1/
│ │ │ ├── oci-s1-ed25519 # Chiave privata SSH (con passphrase)
│ │ │ ├── oci-s1-deploy-ed25519 # Chiave deploy CI/CD (senza passphrase)
│ │ │ └── oci-s1-ed25519.pub
│ │ └── oci-cli/
│ │ └── oci_api_key.pem # Chiave API OCI
│ │
│ ├── docs/ # 📚 Documentazione
│ │ ├── guida-server-completo.md
│ │ ├── guida-traefik-completa.md
│ │ ├── guida-cicd-forgejo-actions.md
│ │ ├── guida-primo-deploy-cinebase.md
│ │ ├── guida-telegram-bot.md
│ │ ├── lessons-learned.md
│ │ ├── analisi-preliminare-server-lab/
│ │ └── ...
│ │
│ └── servers/s1/ # 🖥️ Configurazione server s1
│ ├── .env # 🔒 Gitignorato — variabili reali
│ ├── .env.example # Template con placeholder
│ ├── scripts/ # 🔧 Script di setup (01-12)
│ │ ├── 01-prerequisiti.sh
│ │ ├── 02-installa-docker.sh
│ │ ├── ...
│ │ ├── 09-setup-cinebase.sh
│ │ ├── 11-setup-analytics.sh
│ │ └── 12-setup-landing.sh
│ ├── analytics/ # Template .env per Waline + Umami
│ │ └── .env.example
│ ├── cinebase/ # Template .env per CineBase
│ │ └── .env.example
│ └── oci-setup/ # Configurazione OCI (bucket, IAM)
│ └── README.md
│
├── .gitignore
├── LICENSE # MIT License
└── README.md
**/.secrets/ # Chiavi SSH, chiavi API OCI
.env # Password, token, OCID reali
*.key, *.pem # Chiavi private
id_* # Chiavi SSH
*.log, *.tmp # Log temporanei
**/data/, **/lib/ # Volumi Docker.env.example è versionato ma contiene solo placeholder. I valori reali sono in .env (gitignorato) e nei secrets Forgejo.
Il server esegue una pipeline CI/CD completa per il progetto CineBase:
git push forgejo main
│
▼
Forgejo (git.<DOMINIO>)
│
▼
Runner ARM64 su s1
├── Checkout
├── Install Docker CLI + SSH client
├── Login registry.<DOMINIO>
├── docker build (3 immagini .NET 10)
├── docker push
└── Deploy via SSH → docker compose up -d
| Secret | Descrizione |
|---|---|
REGISTRY_USER |
Utente del registry privato |
REGISTRY_PASSWORD |
Password del registry |
S1_SSH_HOST |
IP del server |
S1_SSH_USER |
Utente SSH |
S1_SSH_KEY |
Chiave privata ed25519 senza passphrase |
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Docker CLI and SSH client
run: apt-get update && apt-get install -y docker.io openssh-client
- name: Login to registry
run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.<DOMINIO> --username "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: Build and push
run: |
docker build -t registry.<DOMINIO>/project/app:latest .
docker push registry.<DOMINIO>/project/app:latest
- name: Deploy via SSH
env:
SSH_KEY: ${{ secrets.S1_SSH_KEY }}
run: |
mkdir -p ~/.ssh && echo "$SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
ssh -o StrictHostKeyChecking=no ${{ secrets.S1_SSH_USER }}@${{ secrets.S1_SSH_HOST }} \
"cd ~/docker/project && docker compose pull && docker compose up -d --remove-orphans"- Un'istanza OCI ARM64 Ampere A1 con Ubuntu 24.04
- Un dominio DNS configurato
- Accesso SSH alla VM
# 1. Clona il repository sul tuo PC
git clone https://github.com/<UTENTE>/oracle-servers.git
cd oracle-servers
# 2. Configura le variabili d'ambiente
cp tenant/servers/s1/.env.example tenant/servers/s1/.env
# Modifica .env con i tuoi valori (dominio, password, OCID)
# 3. Copia gli script sul server
scp -i tua-chiave tenant/servers/s1/scripts/*.sh ubuntu@<IP>:~/scripts/
# 4. Esegui gli script in ordine (01 → 10)
ssh -i tua-chiave ubuntu@<IP> "cd ~/scripts && bash 01-prerequisiti.sh"
ssh -i tua-chiave ubuntu@<IP> "cd ~/scripts && bash 02-installa-docker.sh"
# ... e così via- Crea
~/docker/nuovo-progetto/docker-compose.ymlsul server - Aggiungi le label Traefik per il dominio
- Crea record A nel DNS
- Avvia:
docker compose up -d
# ~/docker/nuovo-progetto/docker-compose.yml
services:
web:
image: registry.<DOMINIO>/progetto/app:latest
networks:
- internal
- traefik-net
labels:
- "traefik.enable=true"
- "traefik.http.routers.progetto.rule=Host(`progetto.<DOMINIO>`)"
- "traefik.http.routers.progetto.entrypoints=websecure"
- "traefik.http.routers.progetto.tls.certresolver=letsencrypt"
- "traefik.http.services.progetto.loadbalancer.server.port=3000"
networks:
internal:
driver: bridge
traefik-net:
external: true# Aggiorna tutte le immagini infrastrutturali
cd ~/docker/traefik && docker compose pull && docker compose up -d
cd ~/docker/registry && docker compose pull && docker compose up -d
cd ~/docker/forgejo && docker compose pull && docker compose up -d
cd ~/docker/netdata && docker compose pull && docker compose up -d
# Deploy di un progetto applicativo
cd ~/docker/cinebase && docker compose pull && docker compose up -d
# Backup manuale
~/docker/backup.shNetdata è accessibile su https://monitor.<DOMINIO> (protetto da basic auth).
Allarmi attivi:
- CPU > 80% per 10 minuti
- RAM < 10% libera
- Disco > 80% usato (warn) / > 95% (crit)
- Container fermo o servizio down
- Backup > 9 GB (quota OCI 10 GB) → notifica Telegram
Notifiche Telegram:
- Alert automatici da Netdata (CPU, RAM, disco, container)
- Alert orario da
check-backup-size.sh(superamento soglia 9/9.5 GB)
Backup notturno (cron alle 3:00) su OCI Object Storage (s1-backup, 10 GB Always Free). Lo script ~/docker/backup.sh è generato da 10-setup-backup.sh; la procedura completa di setup è in guida-backup-oci.md.
Cosa viene backuppato (dump consistente + tar):
| Cosa | Strategia | Perché |
|---|---|---|
PostgreSQL Forgejo (forgejo-db) |
pg_dumpall via docker exec |
Repo Git, utenti, issue — non ricostruibili |
PostgreSQL Analytics (analytics-postgres) |
pg_dumpall via docker exec |
Commenti Waline + dati Umami |
MariaDB CineBase (cinebase-mariadb) |
mariadb-dump via docker exec |
Catalogo, utenti, ordini |
forgejo/data/ |
tar | Allegati, avatar, LFS |
registry/auth/ |
tar | Credenziali htpasswd |
traefik/certificates/ |
tar | acme.json (evita rate limit Let's Encrypt) |
cinebase_*media-uploads (volume) |
tar via container helper | Cover immagini caricate |
Non backuppato (ricostruibile):
registry/data/— immagini Docker, rebuildate dal CI/CDpostgres/data/raw — sostituito dal dump SQL consistenteforgejo/runner*/data/— config runner CI/CD- Configurazioni Traefik (
traefik.yml,dashboard.yml) — versionate su Forgejo
Retention: 3 giorni in locale (~/backup/), 30 giorni su OCI via lifecycle policy del bucket. Allarme Netdata/Telegram se la dimensione supera 9 GB (quota 10 GB).
| Repository | Relazione |
|---|---|
| github.com/malafronte/cinebase | Progetto CineBase, deployato su questo server. Contiene il workflow CI/CD (.forgejo/workflows/deploy.yml) e la documentazione OCI (infra/oci/README.md) |
MIT — vedi LICENSE.
Questo repository è il risultato di un processo iterativo di setup infrastrutturale documentato nelle guide. Se qualcosa non funziona, consulta prima lessons-learned.md.