Com llançar un contenidor de MySQL per a proves locals
Llançar una base de dades MySQL en un contenidor per a proves locals hauria de ser trivial. I ho és, si ja coneixes tots els errors típics i saps exactament què necessites i què no. Però quan no, comences amb alguna cosa com docker run mysql i, al cap de poca estona, et preguntes per què les dades desapareixen, per què triga tant a estar disponible, o per què no pots connectar des del client.
Quina versió de MySQL i per què
Abans de llançar res, val la pena pensar en la versió. En entorns de desenvolupament o CI moltes vegades es llança l’última mysql, per inèrcia. Mala idea. No només perquè “latest” canvia, sinó perquè hi ha diferències subtils però importants entre 5.7, 8.0 i 8.1 que poden fer que les teves proves passin localment i fallin en producció.
Si estàs treballant amb un sistema heretat que fa servir 5.7 i corres proves contra un contenidor amb 8.0, veuràs diferències en la forma de gestionar els índexs, validacions estrictes, autenticació per defecte, i més.
Recomanació: fes servir sempre la mateixa versió exacta que el teu entorn de producció o staging. Ni “latest”, ni “8”, ni “8.0”. Fes servir servir alguna cosa com a mysql:8.0.36 o el que sigui que tinguis en producció. Això evita sorpreses amb canvis menors de MySQL que no es comuniquen bé a les notes de release.
El comandament docker run correcte
El comandament més senzill per desplegar un entorn temporal de proves és aquest:
docker run --rm \
--name mysql-test \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=testdb \
-p 3307:3306 \
mysql:8.0.36
Alguns punts clau:
- –rm: el contenidor s’ elimina quan acaba. Útil si estàs fent proves i no vols netejar a mà després. Per a desenvolupaments més llargs o persistents, millor treure’l i fer servir volums.
- –name mysql-test: li donem un nom explícit. Això permet reiniciar fàcilment (docker restart mysql-test) i evita noms aleatoris que dificulten els scripts.
- -e MYSQL_ROOT_PASSWORD=root: sí, fem servir root/root en entorns locals o temporals. Ho canviem quan cal provar amb rols, però per aixecar una cosa ràpida, això estalvia temps.
- -e MYSQL_DATABASE = testdb: crea una base de dades inicial. Evita haver de córrer scripts per a crear-la.
- -p 3307:3306: fem servir un altre port que 3306 perquè a moltes màquines ja hi ha una instància de MySQL corrent (local, Homebrew, etc.). Més d’una vegada ha passat que ens oblidem, i la connexió no funciona perquè el port ja està en ús.
Consell: sempre prova si el port està lliure abans amb alguna cosa com lsof -i :3306. Docker no dona error si el port està ocupat; simplement no ho exposa, i et preguntes per què no connecta.
Persistència de dades
Per a proves puntuals, –rm i sense volum està bé. Però si has de fer diverses iteracions, convé persistir les dades, encara que només sigui per no perdre logs o veure què va escriure el codi.
Per això, el millor és muntar un volum explícit:
docker run -d \
--name mysql-dev \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=devdb \
-v mysql_data:/var/lib/mysql \
-p 3307:3306 \
mysql:8.0.36
Això crea un volum anomenat mysql_data, gestionat per Docker. Si necessites depurar què hi ha dins, pots inspeccionar-lo amb:
docker run -it --rm \
-v mysql_data:/mnt \
alpine sh
I des d’aquí inspeccionar amb ls /mnt o similars.
Important: Compte amb muntar carpetes locals com a volums (-v $(pwd)/mysql:/var/lib/mysql) si estàs a macOS. El rendiment dels volums muntats en macOS és molt inferior, i MySQL ho nota. De vegades directament no arrenca perquè detecta un fsync() lent i es rendeix. A Linux no és normalment un problema.
Temps d’ arrencada i readiness
MySQL dins d’un contenidor no està llest immediatament. Tot i que el contenidor arrenqui ràpid, la base de dades pot trigar 10 o 15 segons a acceptar connexions. Això és especialment rellevant si estàs fent proves automatitzades i llences els tests immediatament després de llançar el contenidor.
Solució pràctica: no confiïs en docker inspect per a saber si està “running”; espera fins que accepti connexions TCP al port o respongui al client MySQL.
Una forma fiable:
until docker exec mysql-test mysqladmin ping -h"127.0.0.1" --silent; do
sleep 1
done
Això és molt millor que fer sleep 10 a cegues.
Variables d’ entorn
Aquestes són algunes variables que val la pena conèixer:
- MYSQL_ALLOW_EMPTY_PASSWORD =yes: útil en contenidors efímers, encara que és preferible evitar-ho.
- MYSQL_USER i MYSQL_PASSWORD: permeten crear un usuari no-root automàticament, útil per a proves que no han de tenir privilegis globals.
- MYSQL_INITDB_SKIP_TZINFO = is: accelera l’ arrencada inicial si no necessites les taules de zona horària.
Si muntes volums personalitzats i no fas servir entrypoints, també pots quedar-te sense les variables que espera l’script d’inicialització. En aquest cas, prepara’t per inicialitzar a mà.
Inicialització amb SQL personalitzat
MySQL té una funcionalitat molt útil: tot el que posis a /docker-entrypoint-initdb.d/ s’executa a l’arrencada (només si no hi ha ja dades a /var/lib/mysql).
Pots preparar un script SQL o shell com aquest:
-- init.sql
CREATE TABLE test_table (
id INT PRIMARY KEY,
name VARCHAR(100)
);
I llançar-ho així:
docker run -d \
--name mysql-test \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=testdb \
-v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \
-p 3307:3306 \
mysql:8.0.36
Important: això només s’executa si /var/lib/mysql està buit. Si el contenidor ja tenia dades (encara que sigui per un volum muntat), no s’executarà. Si vols repetir proves, esborra el volum primer (docker volume rm mysql_data) o munta una carpeta temporal.
Connectar-se des del client MySQL habitual
Més d’una vegada et trenques el cap perquè el client local no connecta al contenidor, tot i que el port està publicat.
Si fas servir localhost, i estàs a macOS amb Docker Desktop, de vegades dona problemes. Fes servir 127.0.0.1 explícitament. A Linux, normalment funciona bé, però de vegades depèn del firewall o de que no estigui redirigint correctament.
Exemple:
mysql -h 127.0.0.1 -P 3307 -u root -proot
Subtilesa: si fas servir -h localhost, el client mysql fa servir el socket Unix (/tmp/mysql.sock) en comptes de TCP. I és clar, això no existeix al teu contenidor, així que no connecta. Fer servir 127.0.0.1 força la connexió TCP i ho resol.
Contenidor per a proves en CI
En entorns CI/CD com GitHub Actions, GitLab o Bitbucket, també pots llançar un contenidor de MySQL com a servei. Aquí també convé aplicar els mateixos principis:
- No fer servir mysql:latest.
- Espera que el contenidor estigui disponible.
- Fes servir volums temporals o esborra al final si no vols persistència.
Exemple de GitHub Actions:
services:
mysql:
image: mysql:8.0.36
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testdb
I al wait-for-mysql.sh:
#!/bin/sh
until mysql -h 127.0.0.1 -P 3306 -u root -proot -e "SELECT 1"; do
sleep 1
done
Això ha evitat molts errors intermitents quan els tests fallen perquè la base de dades encara no està disponible.
Errors comuns
El contenidor arrenca però no pots connectar des de la teva aplicació
Això pot ser per un d’aquests motius:
- Estàs connectant a localhost en lloc de 127.0.0.1, i el client està intentant fer servir el socket.
- El contenidor està corrent però MySQL encara no ha acabat el seu setup (especialment cert quan fas servir scripts a docker-entrypoint-initdb.d).
- La teva aplicació està corrent en un altre contenidor, i no són a la mateixa xarxa de Docker.
Solució:
- Fes servir 127.0.0.1 sempre que puguis forçar connexió TCP.
- Si l’ aplicació està en un altre contenidor, crea una xarxa Docker explícita:
docker network create test-net
Després llança tots dos contenidors en aquesta xarxa:
docker run -d --name mysql-test --network test-net ...
docker run -d --name app-test --network test-net ...
I connecta a mysql-test:3306 des de l’app.
Has muntat un volum local però el contenidor no arrenca
L’ error típic és:
[ERROR] --initialize specified but the data directory has files in it. Aborting.
Això passa quan muntes una carpeta local buida… però buida des del punt de vista del host, no del contenidor. Docker crea la carpeta amb permisos del teu usuari, i MySQL espera que la carpeta sigui propietat de mysql:mysql.
Com evitar-ho:
- Si muntes volums locals, assegura’t que estiguin completament buits o ja tinguin dades vàlides.
- Si no pots evitar el muntatge, canvia permisos amb chown des d’un contenidor auxiliar:
docker run --rm -v $(pwd)/mysql:/var/lib/mysql alpine chown -R 999:999 /var/lib/mysql
A les imatges oficials de MySQL, l’UID i GID de l’usuari mysql solen ser 999.
El contenidor es reinicia en bucle sense motiu aparent
Si llences un contenidor i s’apaga immediatament o entra en un loop, el primer és veure els logs:
docker logs mysql-test
Moltes vegades, el problema és trivial: el volum està corrupte, la configuració és invàlida, o l’script a /docker-entrypoint-initdb.d té errors de sintaxi.
Una altra possibilitat: has llançat una nova versió de MySQL amb un volum creat amb una versió anterior, i la migració de dades falla de forma silenciosa. Això és més comú del que sembla.
Solució: quan fem proves, fem servir volums amb nom versionat, o simplement eliminem el volum si l’objectiu és reproduir un entorn des de zero:
docker volume rm mysql_data
Configuracions que estalvien temps
Arxius de configuració personalitzats
De vegades necessitem provar alguna cosa amb sql_mode, innodb_log_file_size, o alguna altra variable que no podem canviar des del client.
En aquests casos, muntem un arxiu my.cnf en /etc/mysql/conf.d/:
# my.cnf
[mysqld]
sql_mode=STRICT_TRANS_TABLES
I ho passem al contenidor:
-v $(pwd)/my.cnf:/etc/mysql/conf.d/custom.cnf
Això ens permet simular entorns de producció que tenen configuracions diferents al default de la imatge de MySQL, sense haver de fer un docker build.
Nota: assegura’t que l’arxiu acaba en .cnf. Si muntes un arxiu amb un altre nom, el contenidor l’ignorarà.
Clients preconfigurats per a depuració
Quan fem moltes proves ràpides, va bé tenir un àlies que llança un client mysql ja configurat per a connectar-se al contenidor. Per exemple:
alias mysql-test='mysql -h 127.0.0.1 -P 3307 -u root -proot'
I combinar-ho amb un script de make per a aixecar i tirar el contenidor sense pensar-hi massa:
run:
docker run -d --name mysql-test -e MYSQL_ROOT_PASSWORD=root -p 3307:3306 mysql:8.0.36
stop:
docker rm -f mysql-test
Això sembla una tonteria, però quan treballes en proves que involucren esborrar i rellançar MySQL moltes vegades, t’estalvia temps i errors tontos.
Quan fer servir Docker Compose
Per a una prova ràpida, fem servir docker run. Per a una cosa que es mantindrà diversos dies o que involucra diversos serveis, passem a docker-compose.
Un docker-compose.yml típic per a aquest cas:
version: '3.8'
services:
mysql:
image: mysql:8.0.36
ports:
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: devdb
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
mysql_data:
Y el ciclo queda así:
docker-compose up -d
# proves...
docker-compose down -v
On -v esborra els volums, útil quan vull un entorn net.
MySQL o MariaDB?
Quan només necessites una base de dades relacional per a proves i no fas servir features específiques de MySQL (com rols, JSON functions avançades, etc.), MariaDB pot ser més lleugera i ràpida per aixecar. Però té diferències semàntiques importants, sobretot amb collations i tipus de retorn de funcions de data. Així que és preferible ser explícit: si has de provar MySQL, fes servir MySQL. No el substitueixis per MariaDB sense verificar que el comportament sigui idèntic.
Consideracions per a contenidors en equip
Quan l’entorn el farà servir més d’una persona (desenvolupadors, testers, CI), cal cuidar altres coses:
- El password no ha de canviar entre màquines.
- La base de dades s’ ha de poder reconstruir sense estat.
- El port no ha d’entrar en conflicte (3306 gairebé sempre ho està).
Per això, fem servir una convenció de ports (3307, 3308, etc.) i scripts de make o bash per a evitar que cadascú es munti un setup diferent. Fins i tot podem incloure un .env per centralitzar els valors i evitar duplicació:
MYSQL_PORT=3307
MYSQL_VERSION=8.0.36
MYSQL_PASSWORD=root
Conclusió
Llançar MySQL a Docker per a proves no és complicat, però hi ha prou detalls i subtilitats com perquè valgui la pena fer les coses bé des del principi. El que més ajuda és tenir un setup reproduïble, ràpid de llançar, sense efectes secundaris i el més proper possible a l’entorn real.
Evitar usar versions flotants, cuidar la xarxa, forçar la connexió TCP, no confiar que el contenidor està “ready” només perquè està “running”, i tenir una forma clara d’inicialitzar dades o eliminar residus, són petites decisions que t’estalvien molts problemes.

