Browser-based database administration is easier to contain when the web interface and database share an isolated network, start in a known order, and expose only the listener an operator needs. A two-service Docker Compose project makes that boundary repeatable without publishing the database port to the host.
The MariaDB service stores its data in a named volume and reads both passwords from file-backed Compose secrets. The phpMyAdmin service reaches it by the Compose service name database, while a healthcheck delays the web container until MariaDB finishes initialization.
The web port binds to 127.0.0.1, so phpMyAdmin is reachable only from the Docker host. Remote administration should use an authenticated TLS reverse proxy or an SSH tunnel instead of changing the listener to all host interfaces.
$ umask 077
$ mkdir -p phpmyadmin-stack/secrets
$ cd phpmyadmin-stack
secrets/
$ openssl rand -base64 32 > secrets/db_root_password.txt
$ openssl rand -base64 32 > secrets/db_password.txt
services: database: image: mariadb:11.8 restart: unless-stopped environment: MARIADB_DATABASE: inventory_app MARIADB_USER: guide_operator MARIADB_ROOT_HOST: localhost MARIADB_PASSWORD_FILE: /run/secrets/db_password MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root_password secrets: - db_password - db_root_password volumes: - database_data:/var/lib/mysql healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 10 start_period: 20s volumes: database_data: secrets: db_password: file: ./secrets/db_password.txt db_root_password: file: ./secrets/db_root_password.txt
phpmyadmin: image: phpmyadmin:5.2.3-apache restart: unless-stopped depends_on: database: condition: service_healthy environment: PMA_HOST: database ports: - "127.0.0.1:8080:80"
$ docker compose config --quiet
A successful validation returns no output and exits with status 0.
Tool: Docker Compose Healthchecks Checker
$ docker compose up --detach
$ docker compose ps database
$ docker compose port phpmyadmin 80 127.0.0.1:8080
$ curl --fail --head http://127.0.0.1:8080/ HTTP/1.1 200 OK Server: Apache/2.4.68 (Debian) X-Powered-By: PHP/8.3.32 ##### snipped ##### X-Content-Type-Options: nosniff Content-Type: text/html; charset=utf-8
$ cat secrets/db_password.txt
The password remains visible in the terminal, while shell command arguments and saved terminal output can expose it elsewhere.




