How to properly unmount image (dmg / sparsebundle)

The man page for hdiutil(1) states

 detach dev_name [-force]
            detach a disk image and terminate any associated process.  dev_name is a partial /dev node path (e.g. "disk1").  As of Mac OS X 10.4, dev_name can also be a mount-
            point.  If Disk Arbitration is running, detach will use it to unmount any filesystems and detach the image.  If not, detach will attempt to unmount any filesystems
            and detach the image directly (using the `eject' ioctl).  If Disk Arbitration is not running, it may be necessary to unmount the filesystems with umount(8) before
            detaching the image.  eject is a synonym for detach.  In common operation, detach is very similar to diskutil(8)'s eject.

So the recommended way to detach would be to simply pass the mountpoint. I’ve tested this on Catalina and it works for me. Here is the transcript:

1. Creating the image and reviewing the automounted volume

% cd ~/Desktop 

% hdiutil create -size 1m -layout GPTSPUD -fs APFS -volname temp -type SPARSEBUNDLE -nospotlight -encryption AES-256 temp.sparsebundle
Enter a new password to secure "temp.sparsebundle": 
Re-enter new password: 
created: /Users/pion/Desktop/temp.sparsebundle

% mount
[...]
/dev/disk5s1 on /Volumes/temp (apfs, local, nodev, nosuid, journaled, noowners, mounted by pion)

% diskutil list
[...]
/dev/disk4 (disk image):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        +1.0 MB     disk4
   1:                 Apple_APFS Container disk5         1.0 MB     disk4s1
/dev/disk5 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +1.0 MB     disk5
                                 Physical Store disk4s1
   1:                APFS Volume temp                    41.0 KB    disk5s1

2. Unmounting the automounted volume

% hdiutil detach /Volumes/temp 
"disk4" ejected.

% mount
[...]

% diskutil list               
[...]

3. Mounting onto Desktop mountroot

% hdiutil attach -mountroot . temp.sparsebundle
Enter password to access "temp.sparsebundle": 
/dev/disk4              GUID_partition_scheme           
/dev/disk4s1            Apple_APFS                      
/dev/disk5              EF57347C-0000-11AA-AA11-0030654 
/dev/disk5s1            41504653-0000-11AA-AA11-0030654 /Users/pion/Desktop/temp

% mount
[...]
/dev/disk5s1 on /Users/pion/Desktop/temp (apfs, local, nodev, nosuid, journaled, noowners, mounted by pion)

% diskutil list
[...]
/dev/disk4 (disk image):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        +1.0 MB     disk4
   1:                 Apple_APFS Container disk5         1.0 MB     disk4s1

/dev/disk5 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +1.0 MB     disk5
                                 Physical Store disk4s1
   1:                APFS Volume temp                    24.6 KB    disk5s1

4. Unmounting from Desktop mountroot

% hdiutil detach temp
"disk4" ejected.

% mount
[...]

% diskutil list
[...]

Incidentally, if you were curious why you get both a disk8 and disk9 instead of just disk8, it's because you're using APFS. disk8 is the new disk you've just created (as a disk image) and mounted. disk8s1 is its first (and only) partition, formatted as APFS. The way that APFS works is by creating its own container that covers an entire partition and then mounts it as a separate virtual disk (disk9) which can then have its own virtual partition (disk9s1).