Keeping the root session open until a second login as deploy works is the step that saves people from a lockout. One thing worth checking on cloud images: a drop-in under /etc/ssh/sshd_config.d/ such as 50-cloud-init.conf can already set PasswordAuthentication yes, and sshd keeps the first value it reads, so running sshd -T | grep passwordauthentication after the reload shows what actually applies. Also, ports published by Docker containers can skip the ufw rules entirely.
Thanks, StackShortlist, both points are worth adding. On Ubuntu 22.04+, the Include for /etc/ssh/sshd_config.d/*.conf sits at the top of sshd_config, so drop-ins are read first, and for most options sshd keeps the first value it sees. A cloud-init 50-cloud-init.conf with PasswordAuthentication yes therefore beats the main file. Fix that file or add an earlier-sorting drop-in like 00-hardening.conf, then confirm with sudo sshd -T | grep -i passwordauthentication. On Docker: published ports go through iptables DNAT and the FORWARD chain, so ufw's INPUT rules never see them. Bind to 127.0.0.1:port:port, filter in the DOCKER-USER chain, or use ufw-docker.
The 00-hardening.conf drop-in is a clean fix, since it survives cloud-init rewriting its own file. And DOCKER-USER is the part most guides miss, so readers will be glad to see it in the post.
StackShortlist
Proof Over Promises.
Keeping the root session open until a second login as deploy works is the step that saves people from a lockout. One thing worth checking on cloud images: a drop-in under /etc/ssh/sshd_config.d/ such as 50-cloud-init.conf can already set PasswordAuthentication yes, and sshd keeps the first value it reads, so running sshd -T | grep passwordauthentication after the reload shows what actually applies. Also, ports published by Docker containers can skip the ufw rules entirely.