You can use dd or fallocate (or head or a few other tools) to create a file to act as your container. Then as Vincent Yu stated cryptsetup can handle the loop setup/teardown for you.
fallocate -l 100M mypath/filecontainer
sudo losetup # Debug for demonstration, not necessary in a real script
sudo cryptsetup -y luksFormat mypath/filecontainer
sudo losetup # Debug for demonstration, not necessary in a real script
sudo cryptsetup luksOpen mypath/filecontainer filecontainer
sudo losetup # Debug for demonstration, not necessary in a real script
sudo mkfs -t ext4 /dev/mapper/filecontainer
sync
sudo cryptsetup luksClose filecontainer
sudo losetup # Debug for demonstration, not necessary in a real script
In the debug sudo losetup output, note the AUTOCLEAR = 1 for the filecontainer entry. You could instead go crazy with loopdev=$(sudo losetup -f); mount -o loop <etc>, but letting cryptsetup handle it is WAY nicer.
The key is you want to make sure you FIRST use cryptsetup luksOpen containerfile mappedname to have it mounted to /dev/mapper/mappedname by default (you can give it a full path starting with / and it will mount there instead I believe, and THEN format the /dev/mapper/mappedname target and makes sure you are NOT touching the filecontainer directly, otherwise you are bypassing the encryption engine and just creating an unencrypted loopback file.