How to Deploy MySQL Server on Ubuntu or Docker

An Ubuntu server and glowing Docker database cube on a dark platform.

A MySQL server becomes a production risk when it starts as a quick local install and stays that way. The database then ends up exposed on port 3306, running with a shared root password, and missing a tested restore path.

You can deploy MySQL Server with a native Ubuntu package or an isolated Docker container. Both work well when you restrict network access, create least-privilege users, and treat backups as a routine operation.

DEPLOY MYSQL SERVER WITH THE RIGHT BASELINE

This guide assumes Ubuntu 22.04 or 24.04, MySQL 8.4 LTS, and a host you control. It also assumes Docker Engine is installed if you choose the container route.

Use native MySQL when the Linux host is dedicated to database operations or your team already manages services with systemd. Use Docker when the application stack runs in containers and you want a versioned, portable database deployment.

Deployment typeUse it whenService controlData location
Native Ubuntu packageThe host is a long-lived database VMsystemctlHost filesystem
Official Docker imageYour applications already use Dockerdocker or ComposeNamed volume or host mount

MySQL’s official installation documentation covers supported distribution paths. Avoid mixing the Ubuntu MySQL package with Oracle’s APT repository on the same host unless you have a documented migration plan.

A database deployment is not complete when mysql starts. It is complete when the application connects with a restricted account and a restore test passes.

Decide where applications connect

Keep MySQL private by default. A web application on the same host should use 127.0.0.1 or the local Unix socket. A containerized application should connect over an internal Docker network.

Do not publish port 3306 to the public internet. If another server needs access, use a private subnet, security group, VPN, or SSH tunnel. Then allow only the known application address.

Use a fixed version

MySQL 8.4 is the long-term support release line. Record the exact patch version you deploy, the host name, the install date, and the configuration file path.

For Docker, pin a patch tag such as mysql:8.4.11 rather than mysql:8.4. A moving tag can pull a newer image during a rebuild. Pinning gives you a repeatable starting point.

INSTALL MYSQL 8.4 NATIVELY ON UBUNTU

The MySQL APT repository is the direct route when you need Oracle’s MySQL packages on Ubuntu. Download the current repository package from the MySQL APT repository page, then select the MySQL 8.4 track during setup.

Add the repository and install the server

Run these commands from the directory that contains the downloaded .deb file:

  1. Install the repository configuration package: sudo dpkg -i ./mysql-apt-config_*.deb
  2. Refresh package metadata: sudo apt-get update
  3. Install MySQL Server: sudo apt-get install -y mysql-server
  4. Confirm the service is active: sudo systemctl status mysql
  5. Check the installed client version: mysql --version

The mysql-apt-config_*.deb placeholder matches the repository package filename you downloaded. If the installer shows a product selection screen, choose MySQL Server 8.4 before continuing.

Restrict the native listener

Ubuntu commonly stores the MySQL server configuration in /etc/mysql/mysql.conf.d/mysqld.cnf. Confirm the server listens only on localhost when applications run on the same machine:

sudo grep -n "bind-address" /etc/mysql/mysql.conf.d/mysqld.cnf

Set the value below if it is missing or broader than required:

bind-address = 127.0.0.1

Restart and verify the service:

  1. Restart MySQL: sudo systemctl restart mysql
  2. Check the listener: sudo ss -lntp | grep 3306
  3. Review recent service logs: sudo journalctl -u mysql -n 50 --no-pager

For a private-network deployment, replace 127.0.0.1 with the server’s private IP. Do not use 0.0.0.0 unless a firewall and network policy already limit every source address.

DEPLOY MYSQL SERVER IN DOCKER

Docker keeps the database runtime separate from the host package manager. It does not remove database operations work. You still need persistent storage, restricted networking, credentials, backups, and an upgrade plan.

The official MySQL image supports 8.4 tags and accepts MYSQL_ROOT_PASSWORD during first startup. Never place that password directly in a shell command, Git repository, or shared Compose file.

Create an internal network and secrets file

Create a Docker network for the application and database:

docker network create app-net

Create a local file named .mysql.env with permissions limited to the account that runs Docker:

chmod 600 .mysql.env

Add these values to that file. Replace every placeholder before startup:

  • MYSQL_ROOT_PASSWORD=<LONG_RANDOM_ROOT_PASSWORD>
  • MYSQL_DATABASE=<APP_DATABASE>
  • MYSQL_USER=<APP_USER>
  • MYSQL_PASSWORD=<LONG_RANDOM_APP_PASSWORD>

Use a secrets manager in production. A local environment file is acceptable for a controlled single-host deployment, but it is not a shared secret-management system.

Start the container without publishing MySQL

Run this command:

docker run -d --name mysql84 --restart unless-stopped --network app-net --env-file ./.mysql.env -v mysql84-data:/var/lib/mysql mysql:8.4.11

This command creates a named volume called mysql84-data. Docker keeps MySQL data in that volume after the container restarts or is replaced.

Notice what the command does not include: -p 3306:3306. Containers on app-net can reach MySQL by using mysql84 as the host name. The public network cannot reach it.

Check startup status with:

  1. docker ps --filter name=mysql84
  2. docker logs mysql84 --tail 100
  3. docker exec -it mysql84 mysqladmin -u root -p ping

The final command asks for the root password. A successful response is mysqld is alive.

HARDEN ACCOUNTS AND NETWORK ACCESS

The root account is for database administration. Your application should never use it. Create one database user with only the permissions the application needs.

Run initial hardening

On a native deployment, start the interactive hardening tool:

sudo mysql_secure_installation

The MySQL security utility helps remove default risks such as anonymous accounts and test databases. Read each prompt. Disable remote root access unless a documented private administration path requires it.

On Docker, the image initializes its root password from MYSQL_ROOT_PASSWORD. You still need to control who can connect and which users have privileges.

Create a restricted application account

Open the MySQL prompt on Ubuntu:

sudo mysql

Open it in Docker:

docker exec -it mysql84 mysql -u root -p

Then run the following SQL. Replace the placeholders with your database name, application user, allowed source, and a random password.

CREATE DATABASE <APP_DATABASE> CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

CREATE USER '<APP_USER>'@'<ALLOWED_SOURCE>' IDENTIFIED BY '<LONG_RANDOM_APP_PASSWORD>';

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP ON <APP_DATABASE>.* TO '<APP_USER>'@'<ALLOWED_SOURCE>';

FLUSH PRIVILEGES;

For a same-host native application, <ALLOWED_SOURCE> can be localhost. For Docker, use the private Docker subnet only when needed, such as 172.20.%. Do not use '%' unless you have a narrow firewall rule and a clear reason.

MySQL supports TLS for client connections. Review the secure connection guidance before allowing traffic across hosts.

VERIFY THE APPLICATION PATH

A running database process does not prove the application can connect. Test the same network path and account your application will use.

Test the restricted user

From the application host or container, run:

mysql -h <MYSQL_HOST> -u <APP_USER> -p <APP_DATABASE> -e "SELECT CURRENT_USER(), NOW();"

For Docker, <MYSQL_HOST> is usually mysql84. For a local native service, use 127.0.0.1. Enter the password when prompted rather than placing it after -p.

The result should show the restricted account and a current timestamp. If it fails, check the user host value, Docker network membership, firewall rules, and the bind-address setting.

Watch the signals that matter

Track more than container uptime. A database can be alive while connections pile up or disk space disappears.

Review these checks at minimum:

  • Check storage with df -h on the host.
  • Review MySQL errors with sudo journalctl -u mysql -f on native installs.
  • Review container logs with docker logs -f mysql84.
  • Query active sessions with SHOW PROCESSLIST;.
  • Set alerts for failed backups, low disk capacity, repeated restart events, and high connection counts.

Keep an operations record with the server name, image tag or package version, private address, database owner, backup location, and last successful restore test. Do not overwrite old entries after a change. Add an adjustment record with the date, reason, and approval owner.

BACK UP MYSQL AND PROVE YOU CAN RESTORE IT

A database backup that has never been restored is an assumption. Make restore testing part of the deployment.

Create a logical backup

Create a backup directory with restricted access:

sudo install -d -m 700 /var/backups/mysql

Then run a logical backup with a dedicated backup account:

mysqldump -u <BACKUP_USER> -p --single-transaction --routines --events --databases <APP_DATABASE> > /var/backups/mysql/<APP_DATABASE>-$(date +%F).sql

For Docker, run mysqldump from a secured backup job or a separate client container on app-net. Do not copy a production password into a cron command.

The MySQL mysqldump documentation explains SQL dump creation and reload procedures. For larger databases, plan physical backups and binary-log retention as well.

Test a restore on a separate target

Restore only into a non-production database or isolated MySQL instance:

mysql -u <RESTORE_USER> -p <RESTORE_DATABASE> < /var/backups/mysql/<BACKUP_FILE>.sql

Check table counts, a few known records, stored routines, and application login behavior. Record the backup file name, source server, completion time, result, and tester.

Keep backups off the database host. Use encrypted object storage or another controlled location with a retention policy. A backup stored beside the failed disk does not protect the business.

FINAL DEPLOYMENT CHECK

To deploy MySQL Server safely, keep the service private, use restricted accounts, pin your version, and test recovery before real traffic depends on it.

Native Ubuntu and Docker deployments differ in service control and storage paths. The security controls stay the same. Private access, least privilege, and tested backups turn a running MySQL process into an operable database service.