popotamo.cat)# Connectar al VPS
ssh root@IP_DEL_VPS
# Actualitzar
apt update && apt upgrade -y
# Instal·lar eines bàsiques
apt install -y curl wget git vim nano htop net-tools dnsutils
# Crear usuari
adduser jordi
# Afegir a sudoers
usermod -aG sudo jordi
# Canviar a aquest usuari
su - jordi
# Instal·lar dependències
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
# Afegir clau GPG de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Afegir repositori Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Instal·lar Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Afegir usuari al grup docker (opcional)
sudo usermod -aG docker $USER
# Reiniciar sessió per aplicar canvis
exit
# Tornar a connectar
docker --version
docker compose version
A Nominalia (o el teu proveïdor), afegeix aquests registres DNS:
A popotamo.cat → IP_DEL_VPS
A www.popotamo.cat → IP_DEL_VPS
A wordpress.popotamo.cat → IP_DEL_VPS
A jordi.popotamo.cat → IP_DEL_VPS
A db.popotamo.cat → IP_DEL_VPS
A traefik.popotamo.cat → IP_DEL_VPS
Espera 15-30 minuts per propagació DNS.
Verificar:
dig +short popotamo.cat
dig +short wordpress.popotamo.cat
mkdir -p ~/traefik/config
cd ~/traefik
# Instal·lar apache2-utils
sudo apt install -y apache2-utils
# Generar hash de contrasenya
htpasswd -nb admin VostraContrasenya
# Exemple sortida:
# admin:$apr1$ruca1234$abcdefghijklmnop
Guarda aquesta sortida! (recorda duplicar els $ al docker-compose.yml)
docker network create proxy
Fitxer ~/traefik/traefik.yml:
cat > ~/traefik/traefik.yml << 'EOFTRAEFIK'
api:
dashboard: true
debug: true
insecure: true
entryPoints:
http:
address: ":80"
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: proxy
file:
directory: /config
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: el_teu_email@gmail.com
storage: acme.json
httpChallenge:
entryPoint: http
log:
level: DEBUG
EOFTRAEFIK
mkdir -p ~/traefik/config
cat > ~/traefik/config/middlewares-cors.yml << 'EOFCORS'
http:
middlewares:
cors-headers:
headers:
accessControlAllowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
- PATCH
accessControlAllowHeaders:
- "*"
accessControlAllowOriginList:
- "https://jordi.popotamo.cat"
- "https://wordpress.popotamo.cat"
- "https://www.popotamo.cat"
accessControlMaxAge: 100
addVaryHeader: true
accessControlAllowCredentials: true
security-headers:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
EOFCORS
cat > ~/traefik/docker-compose.yml << 'EOFCOMPOSE'
services:
traefik:
image: traefik:v2.11
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- "80:80"
- "443:443"
- "8080:8080"
environment:
- TZ=Europe/Madrid
- DOCKER_API_VERSION=1.44
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./acme.json:/acme.json
- ./config:/config:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik-dashboard.rule=Host(\`traefik.popotamo.cat\`)"
- "traefik.http.routers.traefik-dashboard.entrypoints=https"
- "traefik.http.routers.traefik-dashboard.tls=true"
- "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik-dashboard.service=api@internal"
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$ruca1234$$abcdefghijklmnop"
networks:
proxy:
external: true
EOFCOMPOSE
IMPORTANT: Substitueix $$apr1$$ruca1234$$abcdefghijklmnop pel hash generat al pas 3.2.
cd ~/traefik
touch acme.json
chmod 600 acme.json
docker compose up -d
docker compose logs -f
Accedeix a: https://traefik.popotamo.cat (usuari: admin, password: la que has posat)
mkdir -p ~/wordpress-popotamo/{backups,scripts}
cd ~/wordpress-popotamo
uploads.ini)cat > ~/wordpress-popotamo/uploads.ini << 'EOFINI'
file_uploads = On
memory_limit = 512M
upload_max_filesize = 256M
post_max_size = 256M
max_execution_time = 300
max_input_time = 300
max_input_vars = 3000
EOFINI
cat > ~/wordpress-popotamo/docker-compose.yml << 'EOFWP'
services:
mariadb:
image: mariadb:11.4
container_name: wordpress-popotamo-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: RootPassword123!
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: WpPassword123!
volumes:
- db-data:/var/lib/mysql
- ./backups:/backups
networks:
- wordpress-net
command: >
--max_allowed_packet=256M
--innodb_buffer_pool_size=512M
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
wordpress:
image: wordpress:latest
container_name: wordpress-popotamo
restart: unless-stopped
depends_on:
- mariadb
environment:
WORDPRESS_DB_HOST: mariadb:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: WpPassword123!
WORDPRESS_TABLE_PREFIX: wp_
WORDPRESS_CONFIG_EXTRA: |
/* SSL/HTTPS fixes per reverse proxy */
if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$$_SERVER['HTTPS'] = 'on';
}
define('FORCE_SSL_ADMIN', true);
define('WP_MEMORY_LIMIT', '512M');
define('WP_MAX_MEMORY_LIMIT', '512M');
volumes:
- wp-content:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
networks:
- wordpress-net
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.wordpress-popotamo.rule=Host(\`wordpress.popotamo.cat\`)"
- "traefik.http.routers.wordpress-popotamo.entrypoints=https"
- "traefik.http.routers.wordpress-popotamo.tls=true"
- "traefik.http.routers.wordpress-popotamo.tls.certresolver=letsencrypt"
- "traefik.http.services.wordpress-popotamo.loadbalancer.server.port=80"
- "traefik.http.middlewares.wordpress-security.headers.frameDeny=true"
- "traefik.http.middlewares.wordpress-security.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.wordpress-security.headers.browserXssFilter=true"
- "traefik.http.routers.wordpress-popotamo.middlewares=wordpress-security"
backup-cron:
image: mariadb:11.4
container_name: wordpress-popotamo-backup-cron
restart: unless-stopped
depends_on:
- mariadb
environment:
MYSQL_HOST: mariadb
MYSQL_ROOT_PASSWORD: RootPassword123!
volumes:
- ./backups:/backups
networks:
- wordpress-net
entrypoint: /bin/sh
command: -c "while true; do sleep 86400; echo 'Iniciant backup...'; mysqldump -h mariadb -u root -p$${MYSQL_ROOT_PASSWORD} wordpress 2>/dev/null | gzip > /backups/wordpress_db_$$(date +\%Y\%m\%d_\%H\%M\%S).sql.gz && echo 'Backup completat'; find /backups -name 'wordpress_db_*.sql.gz' -mtime +7 -delete; done"
networks:
wordpress-net:
driver: bridge
proxy:
external: true
volumes:
db-data:
driver: local
wp-content:
driver: local
EOFWP
Punts importants:
- db-data i wp-content són volums Docker persistents
- Els backups es guarden a ~/wordpress-popotamo/backups/
- Backup automàtic diari a les 00:00 (mantén 7 dies)
- Neteja automàtica de backups > 7 dies
cat > ~/wordpress-popotamo/backup-manual.sh << 'EOFBACKUP'
#!/bin/bash
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
echo "=== Backup WordPress Popotamo ==="
echo "Data: $(date)"
# Backup de la base de dades
echo "Fent backup de la base de dades..."
docker compose exec -T mariadb mysqldump -u root -pRootPassword123! wordpress | gzip > $BACKUP_DIR/wordpress_db_$DATE.sql.gz
if [ $? -eq 0 ]; then
echo "✓ Base de dades: wordpress_db_$DATE.sql.gz"
else
echo "✗ Error en el backup de la base de dades"
exit 1
fi
# Backup dels fitxers de WordPress
echo "Fent backup dels fitxers..."
docker run --rm -v wordpress-popotamo_wp-content:/data -v $(pwd)/backups:/backup alpine tar czf /backup/wordpress_files_$DATE.tar.gz -C /data .
if [ $? -eq 0 ]; then
echo "✓ Fitxers: wordpress_files_$DATE.tar.gz"
else
echo "✗ Error en el backup dels fitxers"
exit 1
fi
# Mostrar mida total
echo ""
echo "=== Backups completats ==="
du -sh $BACKUP_DIR/wordpress_db_$DATE.sql.gz
du -sh $BACKUP_DIR/wordpress_files_$DATE.tar.gz
echo ""
echo "Total backups:"
du -sh $BACKUP_DIR
EOFBACKUP
chmod +x ~/wordpress-popotamo/backup-manual.sh
cat > ~/wordpress-popotamo/restore.sh << 'EOFRESTORE'
#!/bin/bash
BACKUP_DIR="./backups"
echo "=== Restauració WordPress ==="
echo ""
echo "Backups de base de dades disponibles:"
ls -lh $BACKUP_DIR/wordpress_db_*.sql.gz
echo ""
read -p "Nom del fitxer DB (sense path): " DB_FILE
echo ""
echo "Backups de fitxers disponibles:"
ls -lh $BACKUP_DIR/wordpress_files_*.tar.gz
echo ""
read -p "Nom del fitxer FILES (sense path): " FILES_FILE
# Confirmar
echo ""
echo "Es restaurarà:"
echo " DB: $BACKUP_DIR/$DB_FILE"
echo " Files: $BACKUP_DIR/$FILES_FILE"
read -p "Continuar? (s/N): " CONFIRM
if [ "$CONFIRM" != "s" ]; then
echo "Cancel·lat."
exit 0
fi
# Restaurar base de dades
echo ""
echo "Restaurant base de dades..."
gunzip -c $BACKUP_DIR/$DB_FILE | docker compose exec -T mariadb mysql -u root -pRootPassword123! wordpress
if [ $? -eq 0 ]; then
echo "✓ Base de dades restaurada"
else
echo "✗ Error restaurant la base de dades"
exit 1
fi
# Restaurar fitxers
echo "Restaurant fitxers..."
docker run --rm -v wordpress-popotamo_wp-content:/data -v $(pwd)/backups:/backup alpine tar xzf /backup/$FILES_FILE -C /data
if [ $? -eq 0 ]; then
echo "✓ Fitxers restaurats"
else
echo "✗ Error restaurant els fitxers"
exit 1
fi
echo ""
echo "=== Restauració completada ==="
echo "Reinicia WordPress: docker compose restart wordpress"
EOFRESTORE
chmod +x ~/wordpress-popotamo/restore.sh
cd ~/wordpress-popotamo
docker compose up -d
# Veure logs
docker compose logs -f wordpress
Prem Ctrl+C per sortir dels logs.
# Aturar els contenidors
cd ~/wordpress-popotamo
docker compose down
# Tornar a iniciar
docker compose up -d
Les dades es mantenen perquè estan als volums db-data i wp-content.
# Llistar volums
docker volume ls | grep wordpress-popotamo
# Hauries de veure:
# wordpress-popotamo_db-data
# wordpress-popotamo_wp-content
# Inspeccionar un volum
docker volume inspect wordpress-popotamo_wp-content
Backup manual:
cd ~/wordpress-popotamo
./backup-manual.sh
Llistar backups:
ls -lh ~/wordpress-popotamo/backups/
Restaurar:
cd ~/wordpress-popotamo
./restore.sh
A Nominalia, afegeix (si no ho has fet):
A www.popotamo.cat → IP_DEL_VPS
A popotamo.cat → IP_DEL_VPS
Verifica:
dig +short www.popotamo.cat
dig +short popotamo.cat
Edita ~/wordpress-popotamo/docker-compose.yml i modifica només les labels del servei wordpress:
labels:
- "traefik.enable=true"
# Acceptar TOTS els dominis (vell i nous)
- "traefik.http.routers.wordpress-popotamo.rule=Host(\`wordpress.popotamo.cat\`) || Host(\`www.popotamo.cat\`) || Host(\`popotamo.cat\`)"
- "traefik.http.routers.wordpress-popotamo.entrypoints=https"
- "traefik.http.routers.wordpress-popotamo.tls=true"
- "traefik.http.routers.wordpress-popotamo.tls.certresolver=letsencrypt"
- "traefik.http.services.wordpress-popotamo.loadbalancer.server.port=80"
- "traefik.http.middlewares.wordpress-security.headers.frameDeny=true"
- "traefik.http.middlewares.wordpress-security.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.wordpress-security.headers.browserXssFilter=true"
- "traefik.http.routers.wordpress-popotamo.middlewares=wordpress-security"
Nota: Només has de canviar la línia del rule=. La resta queda igual.
Reinicia:
cd ~/wordpress-popotamo
docker compose up -d
Espera 1-2 minuts perquè Traefik generi els nous certificats SSL.
curl -I https://wordpress.popotamo.cat
curl -I https://www.popotamo.cat
curl -I https://popotamo.cat
Tots haurien de retornar HTTP/2 200.
cd ~/wordpress-popotamo
./backup-manual.sh
Tens 3 opcions per canviar les URLs. Tria la que prefereixis:
cd ~/wordpress-popotamo
# Accedir al contenidor WordPress
docker compose exec wordpress bash
# Dins del contenidor, instal·lar WP-CLI
apt update && apt install -y wget less
wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
# Verificar instal·lació
wp --version --allow-root
# Canviar totes les URLs (wordpress.popotamo.cat → www.popotamo.cat)
wp search-replace 'wordpress.popotamo.cat' 'www.popotamo.cat' --allow-root --skip-columns=guid
# O si vols migrar a popotamo.cat sense www:
# wp search-replace 'wordpress.popotamo.cat' 'popotamo.cat' --allow-root --skip-columns=guid
# Verificar les URLs
wp option get siteurl --allow-root
wp option get home --allow-root
# Sortir del contenidor
exit
cd ~/wordpress-popotamo
# Accedir a MariaDB
docker compose exec mariadb mysql -u root -pRootPassword123! wordpress
Dins de MySQL, executa:
-- Canviar URLs principals
UPDATE wp_options
SET option_value = 'https://www.popotamo.cat'
WHERE option_name = 'siteurl';
UPDATE wp_options
SET option_value = 'https://www.popotamo.cat'
WHERE option_name = 'home';
-- Canviar URLs als posts (GUIDs - opcional)
UPDATE wp_posts
SET guid = REPLACE(guid, 'https://wordpress.popotamo.cat', 'https://www.popotamo.cat');
-- Canviar URLs al contingut dels posts
UPDATE wp_posts
SET post_content = REPLACE(post_content, 'https://wordpress.popotamo.cat', 'https://www.popotamo.cat');
-- Canviar URLs als postmeta (camps personalitzats)
UPDATE wp_postmeta
SET meta_value = REPLACE(meta_value, 'https://wordpress.popotamo.cat', 'https://www.popotamo.cat');
-- Canviar URLs a les opcions (widgets, etc)
UPDATE wp_options
SET option_value = REPLACE(option_value, 'https://wordpress.popotamo.cat', 'https://www.popotamo.cat')
WHERE option_name NOT IN ('siteurl', 'home');
-- Verificar
SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('siteurl', 'home');
-- Sortir
exit
Sortir del contenidor:
exit
wordpress.popotamo.catwww.popotamo.catcd ~/wordpress-popotamo
# Reiniciar WordPress per netejar cache
docker compose restart wordpress
# Veure logs
docker compose logs -f wordpress
Accedeix als 3 dominis i verifica que tot funciona:
Important: Tots haurien de mostrar el mateix WordPress, però amb la URL correcta.
Si vols que wordpress.popotamo.cat redirigeixi automàticament a www.popotamo.cat:
cat >> ~/traefik/config/middlewares-cors.yml << 'EOFREDIRECT'
# Redirecció wordpress.popotamo.cat → www.popotamo.cat
redirect-to-www:
redirectRegex:
regex: "^https://wordpress\\.popotamo\\.cat/(.*)"
replacement: "https://www.popotamo.cat/${1}"
permanent: true
EOFREDIRECT
Edita ~/wordpress-popotamo/docker-compose.yml i afegeix aquests labels ABANS dels existents:
labels:
# Redirecció del domini antic
- "traefik.http.routers.wordpress-redirect.rule=Host(\`wordpress.popotamo.cat\`)"
- "traefik.http.routers.wordpress-redirect.entrypoints=https"
- "traefik.http.routers.wordpress-redirect.tls=true"
- "traefik.http.routers.wordpress-redirect.tls.certresolver=letsencrypt"
- "traefik.http.routers.wordpress-redirect.middlewares=redirect-to-www@file"
- "traefik.http.routers.wordpress-redirect.priority=100"
# Router principal (només www i sense www)
- "traefik.enable=true"
- "traefik.http.routers.wordpress-popotamo.rule=Host(\`www.popotamo.cat\`) || Host(\`popotamo.cat\`)"
- "traefik.http.routers.wordpress-popotamo.entrypoints=https"
# ... resta de labels igual
Reinicia:
cd ~/traefik
docker compose restart
cd ~/wordpress-popotamo
docker compose up -d
Ara wordpress.popotamo.cat redirigirà automàticament a www.popotamo.cat.
Si tens enllaços externs (Google Search Console, Analytics, xarxes socials), actualitza'ls:
www.popotamo.cat# Comprovar certificats SSL
curl -I https://www.popotamo.cat | grep -i "HTTP\|server"
# Comprovar redirecció (si l'has configurat)
curl -I https://wordpress.popotamo.cat | grep -i "location"
# Accedir al WordPress admin
firefox https://www.popotamo.cat/wp-admin
WordPress mostra contingut mixt (HTTP i HTTPS):
# Instal·lar plugin "Really Simple SSL"
# O afegir a wp-config.php:
docker compose exec wordpress bash
cat >> /var/www/html/wp-config.php << 'EOFSSL'
/* Forçar SSL */
define('FORCE_SSL_ADMIN', true);
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
$_SERVER['HTTPS']='on';
EOFSSL
exit
Les imatges no carreguen:
# Executar de nou search-replace
cd ~/wordpress-popotamo
docker compose exec wordpress bash
wp search-replace 'wordpress.popotamo.cat' 'www.popotamo.cat' --allow-root
exit
Error 404 a les pàgines:
# Regenerar permalinks
# WordPress Admin → Configuració → Enllaços permanents → Desar canvis
| Pas | Acció | Comandament |
|---|---|---|
| 1 | Afegir DNS nou domini | Nominalia |
| 2 | Actualitzar Traefik labels | Editar docker-compose.yml |
| 3 | Fer backup | ./backup-manual.sh |
| 4 | Canviar URLs | WP-CLI o SQL |
| 5 | Reiniciar WordPress | docker compose restart |
| 6 | Verificar | Accedir als 3 dominis |
| 7 | (Opcional) Redirecció | Afegir middleware |
Temps estimat: 15-30 minuts
Documentació: Novembre 2025 · Versió 1.0