1 22/04/2026 12 min

Mettere MongoDB e una web UI nello stesso impianto Docker funziona bene quando separi subito tre cose: persistenza dei dati, rete interna e punto di accesso umano. Se confondi questi livelli, finisci con un database che riparte ma perde i dati, oppure con una console esposta male su Internet. La strada pulita è semplice: un container per MongoDB con volume dedicato, un container per l’interfaccia web collegato alla stessa rete Docker e, fuori dal cluster, solo il proxy o la porta che decidi davvero di pubblicare.

Il caso tipico è questo: vuoi un MongoDB locale o su un server, più una GUI web per amministrazione veloce. La GUI può essere utile per controllare database, collezioni e documenti senza aprire shell a ogni intervento; però non deve diventare il punto debole dell’installazione. In pratica, il database resta privato, la UI parla solo con MongoDB sulla rete interna, e l’esposizione esterna passa da una decisione esplicita: porta pubblica, reverse proxy o accesso limitato da VPN.

Architettura minima che non ti complica la vita

La configurazione base prevede due servizi: mongo e mongo-express oppure una UI equivalente. MongoDB ascolta solo sulla rete Docker privata; la UI usa le credenziali del database e, se la esponi, lo fai con criterio. Per un ambiente di test può bastare una porta pubblicata direttamente, ma in produzione conviene quasi sempre mettere davanti un reverse proxy con TLS e autenticazione aggiuntiva.

Il vantaggio del modello a rete interna è che riduci la superficie d’attacco. La UI non deve parlare con l’host su una porta arbitraria; deve vedere il nome servizio Docker, per esempio mongo, e basta. Se domani cambi container o host, il nome logico resta uguale e non devi inseguire IP o mapping fragili.

Step 1: prepara directory, rete e dati persistenti

Prima di scrivere il compose, crea una directory dedicata e una rete esplicita. La persistenza non va lasciata al caso: il volume deve sopravvivere al riavvio dei container e anche al redeploy del file Compose.

Struttura consigliata:

mkdir -p /srv/mongodb-stack
cd /srv/mongodb-stack
mkdir -p mongo-data

Se usi Docker Compose moderno, la rete può essere dichiarata nel file. Se preferisci creare una rete a mano per riutilizzarla tra stack diversi, va bene anche così:

docker network create mongo-internal

La verifica minima è banale ma utile: la directory deve esistere, la rete deve comparire tra quelle Docker e il volume non deve essere un bind improvvisato in una path temporanea. Un errore classico è appoggiarsi a una cartella sotto /tmp: al reboot sparisce tutto e poi si dà la colpa a MongoDB.

Step 2: definisci il file Compose con persistenza e rete privata

Un file Compose pulito evita il 90% dei problemi iniziali. Qui sotto c’è una base solida per MongoDB con autenticazione e una UI web collegata allo stesso backend. Le password vanno messe in variabili d’ambiente o in un file .env, non in chiaro nel corpo del documento operativo se poi questo finisce in un repository condiviso.

services:
  mongo:
    image: mongo:7
    container_name: mongo
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
    volumes:
      - ./mongo-data:/data/db
    networks:
      - mongo-internal
    healthcheck:
      test: ["CMD", "mongosh", "--quiet", "--eval", "db.runCommand({ ping: 1 }).ok"]
      interval: 10s
      timeout: 5s
      retries: 5

  mongo-express:
    image: mongo-express:1
    container_name: mongo-express
    restart: unless-stopped
    depends_on:
      mongo:
        condition: service_healthy
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_ROOT_PASSWORD}
      ME_CONFIG_MONGODB_SERVER: mongo
      ME_CONFIG_BASICAUTH_USERNAME: adminui
      ME_CONFIG_BASICAUTH_PASSWORD: ${MONGO_UI_PASSWORD}
    ports:
      - "8081:8081"
    networks:
      - mongo-internal

networks:
  mongo-internal:
    external: true

Se non vuoi una rete esterna, puoi dichiararla interna al Compose. In quel caso Docker la crea da solo e i servizi la condividono senza passare per l’host. La scelta dipende da quanto vuoi riusare la rete tra stack diversi. Per un laboratorio isolato, la rete interna nel file è più semplice. Per ambienti con più stack che devono parlare tra loro, la rete esterna è più controllabile.

Nota pratica: il nome dell’immagine mongo-express:1 è un esempio, ma la tag esatta può cambiare nel tempo. Prima di fissarla in produzione, verifica il tag disponibile e la compatibilità con la versione di MongoDB che stai usando. Se trovi mismatch tra client e server, il primo posto da guardare è il log del container UI, non il firewall.

Step 3: crea il file .env e proteggi i segreti

Le credenziali vanno separate dalla configurazione. Un file .env locale va bene, ma non deve finire in git. Se il progetto è condiviso, usa secret manager o almeno permessi stretti sul filesystem.

cat > .env <<'EOF'
MONGO_ROOT_PASSWORD=una_password_lunga_e_random
MONGO_UI_PASSWORD=un'altra_password_lunga_e_random
EOF
chmod 600 .env

Se devi ruotare le credenziali, non modificare solo la UI: aggiorna anche l’utente root o l’utente applicativo che usa MongoDB. In un sistema con dati reali, la rotazione va pianificata con una finestra di manutenzione e un test di accesso successivo. Il controllo minimo è verificare che il file .env non sia leggibile da altri utenti e che non venga incluso accidentalmente in backup o repository.

Step 4: avvio e verifica iniziale del database

Avvia lo stack e guarda subito lo stato dei container. Non partire dalla UI: prima devi sapere che MongoDB è davvero sano e che il volume è montato come previsto.

docker compose up -d
docker compose ps
docker logs mongo --tail 50

La condizione attesa è healthy per MongoDB e un container UI in stato Up. Se MongoDB resta in Restarting o unhealthy, il primo sospetto è quasi sempre il volume o i permessi della directory dati. Se la UI non parte ma MongoDB sì, guarda la configurazione delle variabili d’ambiente e la raggiungibilità del nome servizio mongo dalla rete Docker.

Una verifica più precisa del database si fa con mongosh dentro al container o da un container temporaneo sulla stessa rete. Il ping deve rispondere con ok: 1.

docker exec -it mongo mongosh -u root -p --authenticationDatabase admin --eval 'db.runCommand({ ping: 1 })'

Se il comando fallisce, non dare per scontato che il problema sia MongoDB. Potrebbe essere la password sbagliata, un inizialization già avvenuto con credenziali diverse o un volume preesistente che contiene utenti creati in precedenza. Questo è un punto delicato: il container MongoDB inizializza root solo al primo avvio su un data directory vuoto.

Step 5: capire il comportamento del volume dati

La persistenza in MongoDB non è un optional decorativo. Se il volume non è montato bene, il container può sembrare perfetto finché non lo ricrei. Poi scopri che i database spariscono. Il controllo minimo è verificare che i file esistano sotto la directory montata e che il container stia scrivendo davvero lì.

docker inspect mongo --format '{{ json .Mounts }}' | jq
ls -la ./mongo-data

Se non hai jq, puoi leggere l’output grezzo di docker inspect. Quello che conta è vedere il mount da ./mongo-data verso /data/db. Se il mount manca, il database sta usando un filesystem effimero del container e ogni ricreazione ti azzera i dati.

In ambienti più seri conviene anche monitorare spazio disco e inode sull’host. MongoDB soffre quando il disco si riempie: puoi vedere errori di scrittura, rallentamenti o blocchi operativi. Un check rapido è:

df -h /srv/mongodb-stack
sudo du -sh /srv/mongodb-stack/mongo-data

Step 6: esponi la web UI senza aprire più del necessario

La UI web è comoda, ma non va trattata come se fosse innocua. Se la lasci esposta sulla porta 8081 di un server pubblico senza autenticazione aggiuntiva, stai regalando una superficie d’attacco inutile. La soluzione minima è proteggere l’accesso con Basic Auth integrata nella UI, ma meglio ancora è metterla dietro reverse proxy con TLS e un secondo livello di controllo.

Se devi usarla solo in amministrazione occasionale, puoi anche non pubblicare alcuna porta e accedere tramite tunnel SSH:

ssh -L 8081:127.0.0.1:8081 user@server

In questo modo la UI resta raggiungibile solo localmente sul tuo client. È la scelta più prudente quando non hai bisogno di accesso multiutente. Il controllo finale è semplice: da una rete esterna la porta non deve risultare aperta, mentre dal tunnel locale la pagina deve caricarsi correttamente.

Step 7: aggiungi un database applicativo invece di usare solo root

Per una prova veloce si usa spesso l’utente root del database anche nella UI. Per un setup che dura, è una cattiva abitudine. Crea un utente applicativo con privilegi limitati sul database che ti serve e lascia root fuori dall’uso quotidiano.

Un esempio di inizializzazione può essere gestito con uno script nella cartella /docker-entrypoint-initdb.d/. Questo approccio funziona solo al primo avvio, quando il volume è vuoto. Se il database è già inizializzato, lo script non parte di nuovo, quindi non usarlo come meccanismo di provisioning continuo.

mkdir -p initdb
cat > initdb/01-users.js <<'EOF'
db = db.getSiblingDB('appdb')
db.createUser({
  user: 'appuser',
  pwd: 'cambia_questa_password',
  roles: [{ role: 'readWrite', db: 'appdb' }]
})
EOF

Per farlo eseguire, monta la directory nello stack. Il vantaggio è che documenti meglio l’intento: il database applicativo esiste e l’utente ha solo i privilegi necessari. Il controllo finale è verificare che appuser possa leggere e scrivere in appdb, ma non amministrare l’intero server.

Step 8: reverse proxy e TLS se la UI deve stare online

Se l’interfaccia web serve da remoto, non pubblicarla in chiaro. La soluzione corretta è un reverse proxy che termina TLS e filtra l’accesso. Puoi usare Nginx, Traefik o Caddy; la scelta dipende dalla tua infrastruttura, non dalla moda del momento. Il punto è che il container della UI non deve essere l’endpoint pubblico finale.

Con Nginx, un blocco base potrebbe inoltrare verso la porta locale della UI e aggiungere autenticazione o allowlist IP. In un ambiente esposto, questa è una protezione minima sensata:

server {
    listen 443 ssl;
    server_name mongo-ui.example.com;

    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Il controllo da fare dopo il deploy è doppio: la UI deve rispondere in HTTPS e la porta diretta 8081, se non necessaria, dovrebbe essere chiusa dal firewall o non pubblicata affatto. Se il proxy mostra 502, il problema è quasi sempre il backend non raggiungibile, non il certificato. Controlla prima il container UI e poi i log del proxy.

Step 9: osservabilità minima che ti evita ore perse

Un impianto Docker piccolo non ha bisogno di un sistema di monitoraggio pesante, ma almeno tre segnali vanno guardati: stato container, spazio disco e log applicativi. Se MongoDB rallenta o cade, spesso il sintomo arriva prima nei log che nel front-end della UI.

Comandi minimi utili:

docker compose ps
docker stats --no-stream
journalctl -u docker --since "1 hour ago"
docker logs mongo --tail 100
docker logs mongo-express --tail 100

Se vuoi una metrica concreta, tieni d’occhio la latenza percepita della UI e il tempo di risposta del ping database. Un aumento anomalo di TTFB nella UI o errori di connessione verso MongoDB sono segnali utili per capire se il collo di bottiglia è rete, storage o carico sul database. Per ambienti più seri, aggiungi alert su disco pieno e restart frequenti.

Errori tipici e come riconoscerli subito

Ci sono alcuni fallimenti ricorrenti. Il primo è il volume non montato correttamente: il container parte, ma i dati non persistono. Il secondo è la password errata nella UI: il database è vivo, ma la console non si autentica. Il terzo è la rete sbagliata: i container esistono, ma non si vedono per nome.

Una diagnostica rapida può essere questa:

docker exec -it mongo getent hosts mongo
docker exec -it mongo-express getent hosts mongo
docker exec -it mongo sh -lc 'test -d /data/db && echo OK'

Se il nome host mongo non risolve dal container della UI, la rete è sbagliata o i servizi non sono sulla stessa network. Se /data/db non esiste o non è scrivibile, il problema è lato mount o permessi. Se la UI mostra errori di autenticazione ma il ping al database funziona, hai quasi certamente credenziali o database di autenticazione errati.

Quando questa soluzione va bene e quando no

Questo schema è adatto per sviluppo, test, piccoli ambienti interni e amministrazione rapida. Va bene anche come base per un server singolo ben tenuto, purché tu accetti che la resilienza sia limitata al nodo host. Se ti serve alta disponibilità vera, replica set, backup consistenti e controllo accessi più articolato, il singolo Compose non basta più.

Il limite principale non è Docker in sé, ma il fatto che stai concentrando database, storage locale e UI sullo stesso host. Se il nodo cade, cade tutto. Se il disco si corrompe, perdi il backend e la console insieme. Per questo il backup dei dati e il test di restore vanno considerati parte del progetto, non un compito opzionale da rimandare.

Checklist operativa finale

Prima di dire che l’installazione è pronta, verifica questi punti in ordine:

  • MongoDB risponde a db.runCommand({ ping: 1 }) con ok: 1.
  • Il mount dati punta a ./mongo-data o a un volume equivalente persistente.
  • La UI raggiunge il backend tramite nome servizio, non tramite IP hardcoded.
  • Le credenziali non sono in chiaro nel repository e il file .env ha permessi stretti.
  • La porta della UI non è esposta senza una decisione precisa di sicurezza.
  • Hai un backup e hai provato almeno una volta il restore su un ambiente separato.

Assunzione: lo stack è pensato per Linux recente con Docker Compose v2, rete Docker privata e accesso amministrativo al server ospitante.