Skip to content
Guides · Recipes

How to Run MySQL or MariaDB in a Cloud Sandbox

Updated October 27, 2026 .md

Short answer: Run the official mysql:8.4 or mariadb:11.8 Docker image in a Sandboxer microVM, bound to 127.0.0.1 with --restart unless-stopped. Load the Sakila sample database, then reach it from your laptop with ssh -L through the stanza sbx ssh-config prints. Data survived a stop and resume.

What we ran#

Everything below ran on production on October 27, 2026, in one default sandbox (4 vCPU, 8 GB, Ubuntu 24.04): MySQL Community Server 8.4.13 from the mysql:8.4 image, then MariaDB 11.8.9 from the mariadb:11.8 image, both with MySQL's public Sakila sample database. Timings are single runs or a few back-to-back runs; yours will differ.

Step Result
sbx new --type default 3.2 s to ready
Pull mysql:8.4 and start it 20 s (most of it the image pull)
Load Sakila (schema + data) 1.4 s, 16,044 rentals
Start MariaDB 11.8 (image already pulled) 4.9 s to accepting TCP
mysqlslap, Sakila join, 160 queries, 1 client 5.1 to 5.6 s (31 to 35 ms a query)
Same, 16 clients 1.6 to 2.9 s
Query from a laptop through ssh -L 155 ms median round trip
sbx stop --wait then sbx resume 2.2 s stop, 1.4 s resume, memoryRestored: true, row intact
Docker daemon restart --restart unless-stopped brought MySQL back, row intact

1. Start MySQL in a sandbox#

Docker is preinstalled in every sandbox. Use the official image as its Docker Hub page documents:

sbx new --name mysql --type default --no-env
id=sb_7hicnttcab    # the id that sbx new printed

sbx exec $id --timeout 300 "docker run -d --name mysql --restart unless-stopped \
  -e MYSQL_ROOT_PASSWORD=pw -p 127.0.0.1:3306:3306 -v mysqldata:/var/lib/mysql mysql:8.4"
sbx exec $id "until docker exec mysql mysql -uroot -ppw -h127.0.0.1 -e 'select 1' >/dev/null 2>&1; do sleep 0.5; done
docker exec mysql mysql -uroot -ppw -e 'select version()' 2>/dev/null"
version()
8.4.13

Three choices matter:

  • -p 127.0.0.1:3306:3306. MySQL listens inside the VM only. Nothing outside can reach it. You connect through SSH (step 4).
  • -v mysqldata:/var/lib/mysql. The data lives in a named volume on the sandbox disk.
  • --restart unless-stopped. If the sandbox boots from disk instead of restoring memory, Docker starts MySQL again (step 5).

Wait on a TCP query (-h127.0.0.1), not mysqladmin ping. On first start, the image runs a temporary server on the socket only, creates the root user, then restarts. Our first script waited on mysqladmin ping, which answered from the temporary server, and the next query ran before the real server was up. The logs show the two phases:

[Note] [Entrypoint]: Temporary server started.
[Note] [Entrypoint]: Temporary server stopped
[Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.13'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306

2. Load the Sakila sample database#

Sakila is MySQL's sample DVD-rental schema: films, actors, stores, rentals and payments. Download it from MySQL inside the sandbox and pipe both files into the server:

sbx exec $id --timeout 300 "curl -fsSLO https://downloads.mysql.com/docs/sakila-db.tar.gz && tar xzf sakila-db.tar.gz
docker exec -i mysql mysql -uroot -ppw < sakila-db/sakila-schema.sql
docker exec -i mysql mysql -uroot -ppw < sakila-db/sakila-data.sql"

The load took 1.4 seconds. Check the counts with COUNT(*), not information_schema.tables.table_rows: right after the load, table_rows is an estimate and left rental and payment out of our top five.

+---------+----------+-------+
| rentals | payments | films |
+---------+----------+-------+
|   16044 |    16044 |  1000 |
+---------+----------+-------+

Now a real query. Save this as top.sql. It joins five tables to rank film categories by revenue:

SELECT c.name AS category, COUNT(*) AS rentals, SUM(p.amount) AS revenue
FROM sakila.payment p
JOIN sakila.rental r ON r.rental_id = p.rental_id
JOIN sakila.inventory i ON i.inventory_id = r.inventory_id
JOIN sakila.film_category fc ON fc.film_id = i.film_id
JOIN sakila.category c ON c.category_id = fc.category_id
GROUP BY c.name ORDER BY revenue DESC LIMIT 5;
sbx scp ./top.sql $id:/home/user/
sbx exec $id "docker exec -i mysql mysql -uroot -ppw -t < top.sql"
+-----------+---------+---------+
| category  | rentals | revenue |
+-----------+---------+---------+
| Sports    |    1179 | 5314.21 |
| Sci-Fi    |    1101 | 4756.98 |
| Animation |    1166 | 4656.30 |
| Drama     |    1060 | 4587.39 |
| Comedy    |     941 | 4383.58 |
+-----------+---------+---------+

3. Measure it with mysqlslap#

The mysql:8.4 image doesn't include mysqlslap. Its /usr/bin has mysql, mysqladmin, mysqldump and mysqlsh, but no mysqlslap. Install Ubuntu's client in the sandbox (4.6 s) and point it at the container over TCP:

sbx exec $id --timeout 300 "sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq mysql-client && mysqlslap --version"
mysqlslap  Ver 8.0.46-0ubuntu0.24.04.4 for Linux on x86_64 ((Ubuntu))

Save as slap.sh. The first run repeats the revenue join, the second uses mysqlslap's own generated mixed read/write load (mysqlslap options):

export MYSQL_PWD=pw
mysqlslap -h 127.0.0.1 -uroot --create-schema=sakila \
  --query="SELECT c.name, SUM(p.amount) FROM payment p JOIN rental r USING (rental_id) JOIN inventory i USING (inventory_id) JOIN film_category fc USING (film_id) JOIN category c USING (category_id) GROUP BY c.name" \
  --concurrency=1,4,16 --iterations=3 --number-of-queries=160
mysqlslap -h 127.0.0.1 -uroot --auto-generate-sql --auto-generate-sql-load-type=mixed \
  --concurrency=1,4,16 --iterations=3 --number-of-queries=2000
sbx scp ./slap.sh $id:/home/user/
sbx exec $id --timeout 300 "bash slap.sh"
Benchmark
	Average number of seconds to run all queries: 5.057 seconds
	Minimum number of seconds to run all queries: 4.191 seconds
	Maximum number of seconds to run all queries: 5.496 seconds
	Number of clients running queries: 1
	Average number of queries per client: 160
…
Benchmark
	Average number of seconds to run all queries: 1.587 seconds
	…
	Number of clients running queries: 16
	Average number of queries per client: 10
…
Benchmark
	Average number of seconds to run all queries: 0.633 seconds
	…
	Number of clients running queries: 16
	Average number of queries per client: 125

We ran the script twice. Averages for all queries in each benchmark:

Load 1 client 4 clients 16 clients
Sakila revenue join, 160 queries 5.06 / 5.57 s 4.51 / 2.36 s 1.59 / 2.94 s
Generated mixed load, 2,000 queries 1.17 / 1.25 s 0.72 / 0.63 s 0.63 / 0.48 s

The join takes about 31 to 35 ms on one client. More clients help, but the runs vary: 4 clients took 4.5 s in one run and 2.4 s in the next. Run mysqlslap several times and compare ranges, not single numbers.

4. Connect from your laptop through an SSH tunnel#

MySQL listens on 127.0.0.1 inside the VM, so forward a local port to it over SSH. sbx ssh-config prints an OpenSSH stanza. You can install it into ~/.ssh/config with --install, or save it to its own file and pass that file with -F, which leaves your SSH config untouched:

sbx ssh-config > sbx_ssh_config
cat sbx_ssh_config
# Sandboxer (sbx ssh-config)
Host *.sbx
  User user
  IdentityFile ~/.ssh/sandboxer_ed25519
  IdentitiesOnly yes
  ProxyCommand sbx proxy %h
  UserKnownHostsFile ~/.config/sandboxer/known_hosts
  StrictHostKeyChecking yes
  ServerAliveInterval 30

The paths in yours are absolute. Connect once with sbx ssh so the CLI authorizes its key on the sandbox, then open the tunnel:

sbx ssh $id true
ssh -F ./sbx_ssh_config -f -N -o ExitOnForwardFailure=yes -L 13306:127.0.0.1:3306 $id.sbx

Any MySQL client on your laptop can now use 127.0.0.1:13306. We used Python with PyMySQL 2.2.8:

import statistics, time
import pymysql

conn = pymysql.connect(host="127.0.0.1", port=13306, user="root", password="pw", database="sakila")
with conn.cursor() as cur:
    cur.execute("SELECT VERSION()")
    print(cur.fetchone()[0])
    times = []
    for _ in range(20):
        t0 = time.perf_counter()
        cur.execute("SELECT title, rental_rate FROM film WHERE title LIKE %s ORDER BY title LIMIT 3", ("AC%",))
        rows = cur.fetchall()
        times.append((time.perf_counter() - t0) * 1000)
    print(rows)
    print(f"median {statistics.median(times):.1f} ms, min {min(times):.1f} ms over {len(times)} queries")
8.4.13
(('ACADEMY DINOSAUR', Decimal('0.99')), ('ACE GOLDFINGER', Decimal('4.99')))
median 154.5 ms, min 154.0 ms over 20 queries

Almost all of the 155 ms is the network path from our client to the sandbox and back, which depends on where you are. That is fine for a GUI client or a quick check. For load tests, run the client in the sandbox, as in step 3. Close the tunnel by killing that ssh process.

sbx forward $id --remote 3306 is the CLI's built-in alternative to ssh -L. We tested the ssh -L route only.

5. What survives a stop and resume#

We inserted a row, stopped the sandbox and resumed it:

sbx exec $id "docker exec mysql mysql -uroot -ppw -e \"INSERT INTO sakila.actor (first_name, last_name) VALUES ('SANDBOX', 'TEST')\""
sbx stop $id --wait
sbx resume $id
sbx exec $id "docker ps --format '{{.Names}} {{.Status}}'
docker exec mysql mysql -uroot -ppw -N -e \"SELECT actor_id, first_name, last_name FROM sakila.actor WHERE last_name = 'TEST'\""
mysql Up 3 minutes
201	SANDBOX	TEST

The stop took 2.2 seconds and the resume 1.4 seconds. The resume reported memoryRestored: true, so MySQL never restarted (Up 3 minutes).

A resume boots from disk instead when no machine with the same CPU type is free, or when you resize. Then the disk is kept and processes start fresh: the Docker volume is there, and the restart policy decides whether MySQL comes back. We didn't force a boot from disk here. Restarting the Docker daemon shows the same policy at work:

sbx exec $id "sudo systemctl restart docker; sleep 1
until docker exec mysql mysql -uroot -ppw -h127.0.0.1 -e 'select 1' >/dev/null 2>&1; do sleep 0.5; done
docker ps --format '{{.Names}} {{.Status}}'
docker exec mysql mysql -uroot -ppw -N -e \"SELECT actor_id, first_name, last_name FROM sakila.actor WHERE last_name = 'TEST'\""
mysql Up 1 second
201	SANDBOX	TEST

See snapshots for the resume rules. The pgvector guide forces a boot from disk with a resize and shows a database coming back the same way.

6. MariaDB instead#

The official MariaDB image works the same way. Its variables start with MARIADB_, and the client is mariadb. We ran it next to MySQL on host port 3307 in the same sandbox:

sbx exec $id --timeout 300 "docker run -d --name maria --restart unless-stopped \
  -e MARIADB_ROOT_PASSWORD=pw -p 127.0.0.1:3307:3306 -v mariadata:/var/lib/mysql mariadb:11.8
until docker exec maria mariadb -uroot -ppw -h127.0.0.1 -e 'select 1' >/dev/null 2>&1; do sleep 0.5; done
docker exec maria mariadb -uroot -ppw -N -e 'select version()'
docker exec -i maria mariadb -uroot -ppw < sakila-db/sakila-schema.sql
docker exec -i maria mariadb -uroot -ppw < sakila-db/sakila-data.sql
docker exec -i maria mariadb -uroot -ppw -t < top.sql"
11.8.9-MariaDB-ubu2404
+-----------+---------+---------+
| category  | rentals | revenue |
+-----------+---------+---------+
| Sports    |    1179 | 5314.21 |
…

MariaDB took 4.9 seconds to accept TCP connections (image already pulled), loaded Sakila without errors and returned the same revenue table. Unlike mysql:8.4, the mariadb:11.8 image ships mariadb-slap, so you can benchmark without installing anything. For the tunnel, forward to port 3307 instead of 3306.

7. Clean up#

sbx delete $id --yes

A default sandbox costs $0.036/hour while it runs and nothing while it is stopped.

FAQ#

Why does my first query fail right after docker run?#

On first start, the mysql:8.4 image runs a temporary server on the socket only, then restarts it with networking. mysqladmin ping can answer from the temporary server. Wait until a TCP query with -h127.0.0.1 succeeds instead; we used the same wait for MariaDB.

Where is mysqlslap in the MySQL Docker image?#

The mysql:8.4 image doesn't include it. Install mysql-client from Ubuntu in the sandbox (it ships mysqlslap 8.0.46) and connect over TCP to 127.0.0.1:3306, or use mariadb-slap, which is in the mariadb:11.8 image.

Can I give MySQL a public URL instead of a tunnel?#

A hosted URL is HTTPS, built for web apps and APIs. A MySQL client speaks the MySQL protocol on a TCP port, so we used SSH and didn't test a hosted URL. The tunnel also keeps the database private: MySQL listens only on 127.0.0.1 in the VM.

Will my data survive if the sandbox auto-stops?#

Yes. Auto-stop is a normal stop: the disk and memory are saved, and sbx resume brings the sandbox back. In our run, MySQL kept running through the stop and the row was there. Keep --restart unless-stopped for the times a resume boots from disk.

MySQL or MariaDB?#

Use the one you run in production. Both loaded Sakila and returned the same results here. They differ in features and in later versions, so test against the engine and major version your app uses.

More guides

Top AI sandbox platforms for code execution in 2026, ranked

For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.

Read the guide
Best sandboxes for running coding agents (Claude Code, Codex, Gemini CLI) in 2026, ranked

For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.

Read the guide
Best Daytona sandbox alternatives in 2026, ranked

The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.

Read the guide
Best E2B alternatives in 2026, ranked

If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.

Read the guide