A published container port is reachable from the internet regardless of what UFW says, because Docker inserts its rules ahead of the chain UFW manages.
$ ufw status
Status: active
3306 DENY IN Anywhere
$ docker run -d -p 3306:3306 mysql:8
$ nmap -p 3306 your-server # OPEN
# the fix: bind to localhost, and reverse-proxy if it must be public
# ports: ['127.0.0.1:3306:3306']
Binding to the loopback address in the port mapping is the reliable answer and it is one prefix. The alternative — a DOCKER-USER chain with rules ahead of Docker’s — works and is fiddly enough that people get it wrong under pressure. This surprises almost everyone once, usually by exposing a database that the firewall was believed to be protecting, and it is worth checking with an external port scan rather than trusting the firewall status.