Setup

Installa immagine Debian 12

ssh root@xxx.xxx.xxx.xxx

Aggiorna e pkg base

# update base system
sudo apt update && sudo apt upgrade -y
 
# install essentials for Docker repository
sudo apt install ca-certificates curl gnupg lsb-release -y

Puk user

Prerequisites: ~/.ssh.config in locale:

Host vps_root
	User root
 
Host vps_puk
	User puk
 
Host vps*
	HostName xxx.xxx.xxx # IP server
	IdentityFile /home/luca/.ssh/id_pukvps

Create User

adduser --gecos "" puk          # imposta una password temporanea
# oppure, se vuoi saltare le domande:
# adduser --disabled-password --gecos "" puk

Root privilege

usermod -aG sudo puk

Verifica:

grep '^%sudo' /etc/sudoers       # dovrebbe mostrare "%sudo  ALL=(ALL:ALL) ALL"

Docker

Se docker è installato, aggiungere l’utente al gruppo

sudo usermod -aG docker puk

Ssh-key

Server:

# crea la directory e imposta i permessi stretti
mkdir -p /home/puk/.ssh
chmod 700 /home/puk/.ssh
 
# copia la chiave pubblica (scegli uno dei due metodi)
 
## Metodo rapido con ssh-copy-id (dalla tua macchina locale, ancora loggato come luca):
ssh-copy-id -i ~/.ssh/id_pukvps.pub puk@xxx.xxx.xxx
 
## Oppure manualmente (se non hai ssh-copy-id):
cat <<'EOF' > /home/puk/.ssh/authorized_keys
# incolla qui tutto il contenuto di ~/.ssh/id_pukvps.pub
EOF
chmod 600 /home/puk/.ssh/authorized_keys
chown -R puk:puk /home/puk/.ssh

Security

Server: /etc/ssh/sshd_config

PubkeyAuthentication yes
PasswordAuthentication no          # dopo aver verificato che l’accesso con chiave funziona!
PermitRootLogin prohibit-password  # o "no"
Caution

L’utente root non usa la chiave ssh così, bisogna aggiungerlo: da locale:

ssh-copy-id -i ~/.ssh/id_pukvps.pub root@xxx.xxx.xxx

da VPS loggato come puk

sudo mkdir -p /root/.ssh
sudo chmod 700 /root/.ssh
sudo tee -a /root/.ssh/authorized_keys < ~/.ssh/authorized_keys
sudo chmod 600 /root/.ssh/authorized_keys

Reload deamon

systemctl restart sshd

Docker Base

Docker engine GPG

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
 
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/debian bookworm stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

Docker engine

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

Docker no sudo

sudo usermod -aG docker $USER
# logout/login o `newgrp docker` per applicare il gruppo

Test

docker run --rm hello-world

Docker Core stack

External network

docker network create proxy      # reverse‑proxy / HTTP(S)
docker network create db         # rete DB condivisa

Vengono restituiti degli hash

Base Dirs

sudo mkdir -p /srv/infra/letsencrypt
cd /srv/infra
nano db.env

db.env

MYSQL_ROOT_PASSWORD=SuperRootPW!
# le seguenti variabili non servono al core‑stack, ma possono essere utili
# MYSQL_DATABASE=
# MYSQL_USER=
# MYSQL_PASSWORD=

Core Stack

  • traefik
  • mariadb
  • phpmyadmin
  • redis
nano /srv/infra/docker-compose.yml

/srv/infra/docker-compose.yml

version: "3.9"
 
services:
  traefik:
    image: traefik:v2.11          # reverse‑proxy
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    command:
      # static config
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.lets.acme.httpchallenge=true"
      - "--certificatesresolvers.lets.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.lets.acme.email=dev@lpuk.it"
      - "--certificatesresolvers.lets.acme.storage=/letsencrypt/acme.json"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
 
  mariadb:
    image: mariadb:11
    container_name: mariadb
    restart: unless-stopped
    env_file: ./db.env            # root password here
    volumes:
      - mariadb_data:/var/lib/mysql
    networks:
      - db
 
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: pma
    restart: unless-stopped
    environment:
      - PMA_HOST=mariadb
    labels:
      - traefik.enable=true
      - traefik.http.routers.pma.rule=Host(`vpsma.lpuk.it`)
      - traefik.http.routers.pma.entrypoints=websecure
      - traefik.http.routers.pma.tls.certresolver=lets
    networks:
      - proxy
      - db
 
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - db
 
volumes:
  mariadb_data:
  redis_data:
 
networks:
  proxy:
    external: true
  db:
    external: true

Errori e appunti

  • Gateway Timeout andando in https su il sottodominio di phpmyadmin non riuscivo ad accedere. Analizzando docker inspect pma (e dandolo a chatGPT) il problema era che il container pma era avviato in modalità network_mode: db. Dopo aver fermato e rimosso i container con docker compose down, rimuovere immagini e cache precedenti con docker image prune -f
  • acme.json ricordare che il file in /srv/infra/letsencrypt/acme.json deve avere permessi 600 (chmod 600 acme.json)

Docker mini-stack

I mini stack stanno nella cartella /srv/[nome-app], con:

  • cartella src dove risiede l’app
  • cartella docker/php dove abbiamo il Dockerfile
  • file docker-compose.yml del mini stack

Nell’esempio, abbiamo laravel-app come nome progetto, e sottodominio api.tuodominio.com. L’esempio utilizza laravel 10 e php 8.2, per modificare la versione php, nel Dockerfile :

-FROM php:8.2-fpm-alpine
+FROM php:8.4-fpm-alpine

Base dir

cd /srv
mkdir -p laravel-app/{src,docker}

Laravel init

# se hai già un repo remoto
git clone https://github.com/<tu-user>/<tu-repo>.git laravel-app/src
# oppure, se vuoi iniziare da zero:
# cd laravel-app/src && composer create-project laravel/laravel .

Docker-compose mini-stack

version: "3.9"
 
services:
  app:
    build:
      context: .                 # root della cartella mini‑stack
      dockerfile: docker/php/Dockerfile
      args:
        uid: 1000                # UID del tuo utente puk
        user: puk
    container_name: laravel_app
    restart: unless-stopped
    working_dir: /var/www/html
    volumes:
      - ./src:/var/www/html      # codice Laravel
    env_file:
      - ./src/.env               # variabili dell’app
    labels:
      - traefik.enable=true
      - traefik.http.routers.laravel-app.rule=Host(`api.tuodominio.com`)
      - traefik.http.routers.laravel-app.entrypoints=websecure
      - traefik.http.routers.laravel-app.tls.certresolver=lets
    networks:
      - proxy
      - db
 
networks:
  proxy:
    external: true
  db:
    external: true

Dockerfile

# PHP‑FPM 8.2 with Composer
FROM php:8.2-fpm-alpine
 
# System dependencies
RUN apk add --no-cache bash git unzip curl icu-dev libpng-dev \
             libxml2-dev oniguruma-dev zlib-dev libzip-dev
 
# PHP extensions
RUN docker-php-ext-install pdo pdo_mysql mbstring intl exif pcntl bcmath gd
 
# Install Composer v2
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
 
# Non‑root user (matches host UID/GID)
ARG user
ARG uid
RUN addgroup -g ${uid} ${user} && \
    adduser -D -u ${uid} -G ${user} ${user}
USER ${user}
 
WORKDIR /var/www/html