MatrixOne on Ubuntu 24.04 on Azure User Guide
Overview
MatrixOne is an open source HTAP database: one engine for transactional and analytical SQL, so the same system that records rows can answer aggregate queries over them without a separate warehouse. It speaks the MySQL wire protocol, so the ordinary mysql client and standard MySQL drivers connect to it, and it adds vector search (a VECF32 column type with l2_distance) and full-text search (FULLTEXT indexes with MATCH ... AGAINST). This image runs MatrixOne 4.2.6 standalone: one mo-service process holds the log service, the transaction node and one compute node, started by systemd.
What the cloudimg image adds:
- No default login, passwords unique to your VM. Upstream MatrixOne creates two built-in accounts,
rootanddump, both with the well-known password111, unless a different password is supplied at the moment the database is first created. So the image ships no database at all: first boot creates this VM's catalogue on the data disk with a 32 characterrootpassword generated on the VM and handed to the server on standard input (never on a command line, in a file, a log or the journal), then givesdumpits own password. The password111never exists on your VM, and first boot proves it is refused for both accounts. Both passwords are written to the root only file/root/matrixone-credentials.txt. - It never starts on an empty data disk. MatrixOne would create a fresh catalogue with
111on an empty store, so the service refuses to start until first boot has finished, and refuses to start on an empty or replaced data disk, with a clear message in the journal (see The data disk). - Every port bound to localhost. The MySQL-protocol port
6001and every internal port MatrixOne opens (log service, transaction node, compute node, query service) listen on127.0.0.1only, and MatrixOne's Prometheus endpoint (which upstream binds to every interface) is switched off. From the network only SSH is reachable; you reach the database through an SSH tunnel (Step 8), or open it deliberately to one network (Step 9). - A MatrixOne built for this image. The MatrixOne 4.2.6 release binary was built with Go 1.26.4 and modules that now carry fixed security advisories, so cloudimg compiles MatrixOne 4.2.6 from its unmodified release source with Go 1.26.10 and the fixed module releases. The build's provenance is on the VM (
/usr/share/doc/matrixone/BUILD-PROVENANCE.txt; Step 10). - Iceberg connector switched off. MatrixOne's Apache Iceberg connector reads Avro files with a library that has three security advisories with no fixed release (see Security notes). Upstream's default configuration turns the connector on; this image turns it off, so every Iceberg statement is refused before any Avro data is read.
- Hardened service. MatrixOne runs as an unprivileged
matrixoneuser with no capabilities, a read-only view of the system and no access to/root. MatrixOne and its first boot service are blocked from the Azure Instance Metadata Service and the Azure WireServer (MatrixOne can fetch a URL you name from SQL, for example when loading data), while the Azure Linux Agent and cloud-init keep their access. The MatrixOne binaries and configuration are owned by root and read-only to the service. - Your data on its own disk. The store (
/var/lib/matrixone) is on a dedicated 40 GiB Azure data disk you can resize or snapshot on its own. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: MatrixOne 4.2.6 (/opt/matrixone, running as matrixone.service, configuration in /etc/matrixone/launch), the MariaDB mysql client from Ubuntu, a helper matrixone-mysql that signs the client in as root, a demo database demo with a vector column and a full-text index, Ubuntu 24.04 LTS, cloud-init and the Azure Linux Agent. There is no MySQL or MariaDB server on the image.
What this is not
This is a single-VM MatrixOne. The distributed deployment (several compute nodes on shared object storage) is out of scope for this image. MatrixOne is compatible with a large part of MySQL 8.0 syntax but is not MySQL; check Matrix Origin's documentation for the statements your application uses. The image does not claim Iceberg support: the connector is off.
MatrixOne is built for the x86-64-v3 instruction set (AVX2), like upstream's own release. Current Azure x64 VM sizes have it; first boot stops with a clear message, and MatrixOne stays stopped, on a processor that does not.
Prerequisites
- An Azure subscription, an SSH key pair, and a VNet and subnet in your region.
- Standard_B4ms (4 vCPU, 16 GiB RAM) is the recommended starting size; use a larger size for heavier analytical work.
- A network security group that allows TCP 22 (SSH) from your admin network. MatrixOne listens on the loopback address only, so no database port needs to be open.
- For remote SQL access, a MySQL client on your computer. MatrixOne uses the
mysql_native_passwordauthentication plugin, so use a MariaDB client or a MySQL 8.0.30+ client; MySQL 9.x clients removed that plugin and cannot connect.
Step 1: Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for MatrixOne by cloudimg and select Create. On Basics pick your subscription, resource group, region and size (Standard_B4ms or larger); under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) only. The image brings two disks: the OS disk and a 40 GiB data disk (LUN 0) for /var/lib/matrixone; you can make the data disk larger on the Disks tab. Then Review + create, Create.
First boot takes about a minute once the VM is running: it creates the catalogue with your VM's passwords, then starts MatrixOne.
Step 2: Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn cloudimg1647283583153:matrixone-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name matrixone \
--image cloudimg1647283583153:matrixone-ubuntu-24-04:default:latest \
--size Standard_B4ms \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
Step 3: Connect to your VM
ssh azureuser@<vm-public-ip>
A welcome banner prints the most useful commands.
Step 4: Check the services
Both the first boot service and MatrixOne itself should be active:
systemctl is-active matrixone-firstboot.service matrixone.service
active
active
Every MatrixOne port listens on the loopback address; from the network, only SSH (port 22) is reachable:
sudo ss -Htln | awk '{print $4}' | sort -V
0.0.0.0:22
127.0.0.1:6001
127.0.0.1:6002
127.0.0.1:6003
127.0.0.1:19998
127.0.0.1:22000
127.0.0.1:22001
127.0.0.1:22002
127.0.0.1:32000
127.0.0.1:32001
127.0.0.1:32002
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
6001 is the MySQL-protocol port; the others are MatrixOne's internal services (log service 32000 to 32002, transaction node 22000 to 22002, compute node 6002 and 6003, query service 19998). The two port 53 lines are Ubuntu's local DNS resolver, also loopback only.
The store is on the dedicated data disk:
findmnt -no SOURCE,TARGET,FSTYPE,SIZE /var/lib/matrixone
/dev/sda /var/lib/matrixone ext4 39.1G
The device name (/dev/sda, /dev/sdc, ...) depends on the VM; the disk is always LUN 0 (/dev/disk/azure/scsi1/lun0) and is mounted by its file system UUID.

Step 5: Get your passwords
sudo cat /root/matrixone-credentials.txt
# MatrixOne 4.2.6 on Ubuntu 24.04 - generated on first boot by matrixone-firstboot.service
# These passwords are unique to this VM; none was baked into the image. Keep this file secret.
MATRIXONE_HOST=127.0.0.1
MATRIXONE_PORT=6001
MATRIXONE_ROOT_USER=root
MATRIXONE_ROOT_PASSWORD=<MATRIXONE_ROOT_PASSWORD>
MATRIXONE_DUMP_USER=dump
MATRIXONE_DUMP_PASSWORD=<your-dump-password>
MATRIXONE_SERVER_VERSION=8.0.30-MatrixOne-v4.2.6
# On the VM (the mysql client signed in as root; the password never goes on a command line):
# sudo matrixone-mysql demo
# From your computer, through an SSH tunnel (MatrixOne listens on 127.0.0.1:6001 only):
# ssh -N -L 6001:127.0.0.1:6001 azureuser@<vm-public-ip>
# mysql -h 127.0.0.1 -P 6001 -u root -p
# User guide: https://www.cloudimg.co.uk/guides/matrixone-on-ubuntu-24-04-azure/
The file is mode 0600 and owned by root. root is MatrixOne's administrator in the sys tenant. dump is the second built-in account upstream creates; it has its own password so the two accounts never share one. Keep both in your secrets manager.
Step 6: Connect with the mysql client
matrixone-mysql runs the mysql client signed in as root with the password from the credentials file, passed to the client in a temporary root-only option file rather than on the command line (where any user could read it in the process list):
sudo matrixone-mysql demo -e 'SELECT version(); SELECT current_user();'
version()
8.0.30-MatrixOne-v4.2.6
current_user()
root@localhost
Run sudo matrixone-mysql demo with no -e for an interactive session. If you use the mysql client directly, let it prompt for the password (-p with nothing after it); never type -p<password>, which puts the password on the command line.
The image's own check proves both per-VM passwords work and that 111, a blank password and common or random passwords are refused for root and dump:
sudo /usr/local/sbin/matrixone-roundtrip.sh | tail -1
OK-matrixone-roundtrip

Step 7: Explore the demo database
First boot created a small table, demo.articles, with a vector column (embedding VECF32(3)) and a full-text index on body:
sudo matrixone-mysql demo -e 'SELECT id, title FROM articles;'
id title
1 Getting started
2 Vector search
3 Full-text search
Vector search
Find the rows whose embedding is nearest to a query vector:
sudo matrixone-mysql demo -e "SELECT id, title, l2_distance(embedding, '[0.85,0.8,0.75]') AS distance FROM articles ORDER BY distance LIMIT 2;"
id title distance
2 Vector search 0.07071065157651901
3 Full-text search 0.6964194178581238
Full-text search
sudo matrixone-mysql demo -e "SELECT id, title FROM articles WHERE MATCH(body) AGAINST('columns');"
id title
2 Vector search
Your own database
Ordinary MySQL-style SQL works for your own data, and the same engine that wrote the rows aggregates them:
sudo matrixone-mysql -e "CREATE DATABASE IF NOT EXISTS shop; CREATE TABLE IF NOT EXISTS shop.orders (id INT PRIMARY KEY, customer VARCHAR(50), amount DECIMAL(10,2)); INSERT INTO shop.orders VALUES (1,'alice',19.99),(2,'bob',5.00),(3,'alice',42.50) ON DUPLICATE KEY UPDATE amount=VALUES(amount); SELECT customer, count(*) AS orders, sum(amount) AS total FROM shop.orders GROUP BY customer ORDER BY customer;"
customer orders total
alice 2 62.49
bob 1 5.00
For applications, create a dedicated account rather than using root, for example CREATE USER app IDENTIFIED BY '<password>'; followed by the grants your application needs (MatrixOne grants privileges through roles: CREATE ROLE, GRANT ... TO ROLE, GRANT <role> TO <user>). Type such statements in an interactive sudo matrixone-mysql session so the password is never on a command line.

Step 8: Connect from your own computer through an SSH tunnel
MatrixOne listens on 127.0.0.1:6001 only. Forward the port over SSH from your computer, then point any MySQL client or driver at your local end of the tunnel:
ssh -N -L 6001:127.0.0.1:6001 azureuser@<vm-public-ip>
mysql -h 127.0.0.1 -P 6001 -u root -p
Enter the root password from Step 5 when prompted. MatrixOne does not offer TLS in this configuration, so the SSH tunnel is what encrypts the connection; a client set to --ssl-mode=REQUIRED will fail.
Step 9: Open port 6001 to one network (optional)
Only do this for a network you trust, for example your application subnet inside the same VNet: the MySQL protocol is not encrypted here. Change the address the MySQL-protocol port listens on (the internal ports stay on loopback), restart MatrixOne, and allow 6001 in your network security group from that one address range:
sudoedit /etc/matrixone/launch/cn.toml
# in the [cn.frontend] table, change host = "127.0.0.1" to host = "0.0.0.0"
sudo systemctl restart matrixone
az network nsg rule create --resource-group <your-rg> --nsg-name <your-nsg> \
--name allow-matrixone-6001 --priority 1010 --direction Inbound --access Allow \
--protocol Tcp --destination-port-ranges 6001 --source-address-prefixes <your-cidr>
Never use * or Internet as the source. To close it again, set host = "127.0.0.1" back, restart MatrixOne and delete the rule. While the port is open, matrixone-roundtrip.sh reports that 6001 is reachable off the VM; that is expected.
Step 10: Security checks
MatrixOne and its first boot service cannot reach the Azure Instance Metadata Service (169.254.169.254) or the WireServer (168.63.129.16), in either address form:
grep -h IPAddressDeny /etc/systemd/system/matrixone.service.d/50-cloudimg-imds-deny.conf /etc/systemd/system/matrixone-firstboot.service.d/50-cloudimg-imds-deny.conf
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 ::ffff:169.254.169.254/128 ::ffff:168.63.129.16/128
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 ::ffff:169.254.169.254/128 ::ffff:168.63.129.16/128
First boot tested the block from inside its own service before it created the catalogue (a loopback connection is the positive control; the Azure Linux Agent is not affected):
sudo journalctl -u matrixone-firstboot.service -o cat | grep -o 'EGRESS-PROBE.*'
EGRESS-PROBE matrixone-firstboot.service loopback=REACHABLE imds4=TimeoutError imds4mapped=TimeoutError imds6=ENETUNREACH wireserver=TimeoutError wireservermapped=TimeoutError verdict=BLOCKED
The MySQL-protocol port is pinned to loopback, and the Iceberg connector and the Prometheus endpoint are off:
grep -E '^(host|port|enable|enable-metric-to-prom) =' /etc/matrixone/launch/cn.toml
host = "127.0.0.1"
port = 6001
enable = false
host = "127.0.0.1"
enable-metric-to-prom = false
Iceberg statements are refused by MatrixOne's feature gate:
{ sudo matrixone-mysql -e 'SHOW ICEBERG CATALOGS' 2>&1 || true; } | grep -o 'ICEBERG_FEATURE_DISABLED.*'
ICEBERG_FEATURE_DISABLED: SHOW ICEBERG CATALOGS is disabled by configuration [account_id=0]
MatrixOne runs unprivileged:
ps -o user:12=,comm= -C mo-service
sudo grep -E '^(CapEff|NoNewPrivs)' /proc/$(systemctl show -p MainPID --value matrixone.service)/status
matrixone mo-service
CapEff: 0000000000000000
NoNewPrivs: 1
How MatrixOne was built, and what govulncheck found:
grep -E '^(toolchain|govulncheck|source change|unfixed) ' /usr/share/doc/matrixone/BUILD-PROVENANCE.txt
toolchain go1.26.10 linux/amd64, official tarball https://go.dev/dl/go1.26.10.linux-amd64.tar.gz
toolchain sha256 ccfe7ae822d2c2f41f4fe7c956199967a4dac22d0ee0d2b2bfdb440aab3b20c5
source change none (upstream source, unmodified)
govulncheck v1.9.0, source: Your code is affected by 3 vulnerabilities from 1 module. (GO-2026-5046 GO-2026-5047 GO-2026-5048); binary: Your code is affected by 3 vulnerabilities from 1 module. (GO-2026-5046 GO-2026-5047 GO-2026-5048)
unfixed GO-2026-5046, GO-2026-5047, GO-2026-5048 (github.com/hamba/avro/v2: no fixed release exists). Imported only

Security notes
- GO-2026-5046, GO-2026-5047, GO-2026-5048 are denial-of-service advisories (CPU exhaustion, integer overflow, unbounded memory) in the Avro decoder
github.com/hamba/avro/v2, for which no fixed release exists. MatrixOne uses that library only in its Iceberg connector, to read Iceberg manifest files. The connector is off on this image and every Iceberg statement is refused before any Avro data is read, as shown above, so this code is not reachable. If you turn the connector on ([cn.frontend.iceberg] enable = truein/etc/matrixone/launch/cn.toml), that code becomes reachable and there is no upstream fix yet: only point it at storage you trust. - MatrixOne cannot reach the Azure metadata service, so loading data from Azure Blob Storage or S3 cannot use the VM's managed identity; pass credentials in the statement or stage you create, as Matrix Origin's documentation describes.
The data disk
The whole store (/var/lib/matrixone/mo-data) lives on the data disk, so it survives reboots and you can resize or snapshot it on its own. First boot writes a marker on the disk when it creates the catalogue, and matrixone.service checks for it before every start:
systemctl cat matrixone.service | grep -E '^(ExecStartPre|RequiresMountsFor)='
RequiresMountsFor=/var/lib/matrixone
ExecStartPre=/usr/local/sbin/matrixone-precheck
If the data disk is missing, empty or replaced with one this image's first boot did not create, MatrixOne does not start (it would otherwise create a new catalogue with the upstream password 111). The journal says why:
matrixone-precheck: REFUSING TO START MatrixOne: /var/lib/matrixone holds no catalogue created by this image's first boot (an empty or unexpected data disk)
First boot itself refuses to touch a data disk that already holds data it did not create, so it never overwrites a disk you attached. To move your data to another VM made from this image, let the new VM finish its first boot, stop MatrixOne (sudo systemctl stop matrixone), then replace the contents of /var/lib/matrixone with a copy of the old VM's (keeping the .cloudimg-bootstrapped marker and matrixone:matrixone ownership), or swap in a disk made from a snapshot of the old VM's data disk, and start MatrixOne again. The restored catalogue keeps the old VM's passwords: put them in the new VM's credentials file (Step 11).
To grow the data disk, resize it in the portal (or az disk update --size-gb), then grow the file system once inside the VM: sudo resize2fs "$(findmnt -no SOURCE /var/lib/matrixone)".
Step 11: Change the passwords
Type the new password into an interactive client session (so it is never on a command line), then update the credentials file with an editor:
sudo MYSQL_HISTFILE=/dev/null matrixone-mysql
mysql> ALTER USER root IDENTIFIED BY '<new-password>';
mysql> exit
sudoedit /root/matrixone-credentials.txt
# replace the value of MATRIXONE_ROOT_PASSWORD with the new password
MYSQL_HISTFILE=/dev/null keeps the statement out of the client's history file. Use ALTER USER: MatrixOne accepts SET PASSWORD but does not change the password with it. Change dump the same way (ALTER USER dump IDENTIFIED BY '<new-password>';) and update MATRIXONE_DUMP_PASSWORD. Run sudo /usr/local/sbin/matrixone-roundtrip.sh | tail -1 afterwards; it should print OK-matrixone-roundtrip.
Step 12: Backup, updates and maintenance
- Back up by snapshotting the data disk, or by copying
/var/lib/matrixonewith the service stopped (sudo systemctl stop matrixone). MatrixOne also has its own backup tooling andmo-dumpfor logical dumps; see Matrix Origin's documentation. - Memory: MatrixOne sets its Go memory limit to about 70 percent of the VM's RAM each time it starts, so it adapts to the VM size you choose. The file service caches are capped at 128 MB (local) and 256 MB (shared) in
/etc/matrixone/launch/*.toml; raise them on large VMs for analytical work. - Logs:
sudo journalctl -u matrixone.service -n 50. - Configuration lives in
/etc/matrixone/launch/; restart the service after editing it (sudo systemctl restart matrixone). Keep everylisten-addressandhoston127.0.0.1unless you deliberately expose a port (Step 9), and never enable the Prometheus endpoint on a network you do not trust (MatrixOne binds it to every interface). - Updates: Ubuntu security updates install automatically. MatrixOne is pinned to the version this image was built with; upgrade it deliberately, after taking a snapshot of the data disk and reading Matrix Origin's upgrade notes for the release.
Support and licensing
MatrixOne is open source software under the Apache License 2.0 (/usr/share/doc/matrixone/LICENSE); the jieba dictionary MatrixOne uses for Chinese full-text tokenising is under the MIT licence (/usr/share/doc/matrixone/LICENSE.jieba-dict). The MatrixOne on this image is built from the unmodified upstream source. cloudimg provides 24/7 technical support for this image: support@cloudimg.co.uk.
MatrixOne is a trademark of Matrix Origin. MySQL is a trademark of Oracle Corporation. This image is provided by cloudimg and is not affiliated with or endorsed by Matrix Origin or Oracle Corporation.