1. Overview

A great feature of Linux is that we can create a new filesystem in a plain file instead of a partition or disk. Then, we can mount the file to our working system. Storing the files of an application on a distinct filesystem can provide better safety and security to the application data.

This tutorial will demonstrate how we can create a filesystem in a simple file and then mount it.

2. Creating the File

First, we have to create the file that will hold the filesystem. The file should have a non-zero size since the value we set will be the maximum size of our filesystem. Let’s create a file with a size of 100MB:

$ truncate --size=100M myfs.img
$ ls -lh
total 0
-rw-rw-r--    1 ubuntu   ubuntu    100.0M Feb 28 10:41 myfs.img

In this example, we created a new file with the name myfs.img and set its size to 100M using the truncate command. Alternatively, we can use the dd command to create the file:

$ dd if=/dev/zero of=myfs.img count=204800
204800+0 records in
204800+0 records out
$ ls -lh
total 100M
-rw-rw-r-- 1 ubuntu ubuntu 100.0M Feb 28 10:56 myfs.img

As can be seen, we copied 204800 blocks of the pseudo file /dev/zero to myfs.img. The default size of a block is 512 bytes. We can change the block size with the bs option:

$ dd if=/dev/zero of=myfs.img bs=1024 count=102400
102400+0 records in
102400+0 records out
$ ls -lh
total 100M
-rw-rw-r--    1 ubuntu   ubuntu    100.0M Feb 28 11:00 myfs.img

We set the block size to 1024 bytes and the number of blocks to copy to 102400. As expected, the resulting file is again 100MB.

3. Creating the Filesystem

Linux supports many types of filesystems. We can choose one according to our needs. Here, we’ll use the popular ext4 filesystem. To create an ext4 filesystem, we can use the mkfs.ext4 command with our myfs.img file:

$ sudo mkfs.ext4 myfs.img
mke2fs 1.45.5 (07-Jan-2020)
Discarding device blocks: done
Creating a filesystem with 25600 4k blocks and 25600 inodes

Allocating group tables: done
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

As expected, we created an ext4 filesystem in the myfs.img file. Alternatively, we can use the mke2fs command with the -t option to create our filesystem:

$ sudo mke2fs -t ext4 myfs.img
mke2fs 1.45.5 (07-Jan-2020)

The result is the same as in the previous example.

4. Checking Filesystem Options With dumpe2fs

Filesystems have many configuration options. In the previous example, we used default values for all of them. We can view the properties of a filesystem with the dumpe2fs command:

$ sudo dumpe2fs myfs.img
dumpe2fs 1.45.5 (07-Jan-2020)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          04be81b4-01b1-4f32-87a6-38c789cf4455
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)

As shown here, we see that dumpe2fs indeed found a filesystem in the myfs.img file and printed its properties to the standard output.

5. Loop Devices

A loop device is a block device that maps its blocks to the blocks of a regular file in our filesystem. We can map our file to a loop device so that we can mount the device later on.

We map a file to a device with the losetup command:

$ sudo losetup -f myfs.img

Here, we call the losetup command on the myfs.img file. In addition, we use the -f option so the command finds the next available loop device name to use. In other words, if /dev/loop0 and /dev/loop1 already exist in our system, losetup will assign /dev/loop2 to our file.

Furthermore, we can view all loop devices with the -a option of losetup:

$ losetup -a
/dev/loop1: []: (~/myfs.img)
/dev/loop0: []: (/var/lib/snapd/snaps/core20_1611.snap)

Indeed, we can see that the /dev/loop1 loop device is created and assigned to our file.

Alternatively, we can choose the loop device name we want for our filesystem:

$ sudo losetup /dev/loop2 myfs.img
$ losetup -a
/dev/loop2: []: (~/myfs.img)
/dev/loop1: []: (~/myfs.img)
/dev/loop0: []: (/var/lib/snapd/snaps/lxd_22753.snap)

Here, we omitted the -f option and added the /dev/loop2 loop device name in its place. Moreover, we can detach a loop device from a file with the -d option:

$ sudo losetup -d /dev/loop2
$ losetup -a
/dev/loop1: []: (~/myfs.img)
/dev/loop0: []: (/var/lib/snapd/snaps/lxd_22753.snap)

As expected, we removed the /dev/loop2 device.

Interestingly, we can skip the losetup command. Instead of this command, we can use the loop option of the mount command to achieve the same results.

6. Mounting the Loop Device

The final step is to mount our loop device to our filesystem. First, we should create the mount point and then run the mount command to mount our filesystem:

$ mkdir /mnt/myfs
$ sudo mount /dev/loop1 /mnt/myfs
$ ls -l /mnt/myfs
total 16
drwx------    2 root     root         16384 Feb 28 12:02 lost+found

In the above example, initially, we created the myfs folder in the /mnt directory. Next, we mounted the /dev/loop1 device at our mount point. Finally, we ran the ls command on the /mnt/myfs folder to verify that the new filesystem is correctly mounted.

Moreover, we can list our mounted loop device with the df command:

$ df -h | grep loop1
/dev/loop1               92.8M     24.0K     85.8M   0% ~/myfs

Indeed, the df command printed information about our loop device to the standard output.

7. Making the Mount Permanent

Unfortunately, the results of the losetup and mount commands are lost when the system restarts. In other words, the loopback device and the mounted filesystem that we created in the previous sections won’t exist after a reboot.

In any case, we can address this problem in two ways. The first is to create a service that will run the losetup and mount commands on system startup. The second is to add a record to the /etc/fstab file. Here, we focus on the second solution.

The /etc/fstab file is a table of filesystems that the system will load when it starts. Furthermore, we can configure it to create a loop device for an image file. A row in this table contains several fields:

  1. device path or UUID or path to the image file
  2. path to the mount point
  3. the filesystem type
  4. mount options
  5. dump and fsck execution flags

Let’s open the /etc/fstab file with an editor and add a record:

/img/myfs.img /mnt/myfs ext4 loop 0 0

Let’s review how our new record’s fields are mapped:

  • the path to the image file is set to /img/myfs.img
  • /mnt/myfs is the mount point
  • ext4 is the filesystem type
  • loop in the mount options will create a loop device
  • the first 0 value disables the dump tool
  • the second 0 value disables the fsck tool

Notably, we didn’t define a loop device name, so the system will use the next available name.

To verify our settings, we can either restart the system or run the mount command with the -a option. This option will mount all records of the /etc/fstab file:

$ sudo mount -a
$ df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/loop3               92.8M     24.0K     85.8M   0% /mnt/myfs

As we can see, a new loop device was mounted at /mnt/myfs.

8. Conclusion

In this article, we learned how to create a filesystem in a file and how to mount it to our system. Furthermore, we created a loop device and used the dumpe2fs tool to print the filesystem’s properties. Finally, we modified the /etc/fstab file to mount our image file on system startup.