4. Use nullfs mounts
title: "Using nullfs mounts" get started, setup description: Using nullfs mounts in our application
In the previous chapter, we talked about and used a volume mount to persist the data in our database. A volume mount is a great choice when you need somewhere persistent to store your application data within Kleene.
A nullfs mount is another type of mount, which lets you share a directory from the host's filesystem into the container. When working on an application, you can use a nullfs mount to mount source code into the container. Another usecase can be the need to make contents available from external storage such as nfs.
In this chapter, we'll see how we can use nullfs mounts and a tool called nodemon to watch for file changes, and then restart the application automatically. There are equivalent tools in most other languages and frameworks.
Quick volume type comparisons¶
The following table outlines the main differences between volume mounts and nullfs mounts.
| Volumes | Nullfs mounts | |
|---|---|---|
| Host location | Kleened stores it under the <kleene-root>/volumes dataset | You decide |
| Example | --mount my-volume:/usr/local/data | --mount /path/to/data:/usr/local/data |
Under the hood, volume mounts are nullfs mounts where Kleene manages the underlying ZFS filesystem.
Nullfs mounts are, as the name suggests, mountpoints created using the nullfs(5) file system layer of FreeBSD.
Trying out Kleene's nullfs mounts¶
Before looking at how we can use nullfs mounts for developing our application, let's run a quick experiment to get a practical understanding of how they work.
-
Open a terminal and and go to the
appdirectory of the getting started repository. -
Run the following command to start
shin an fresh container with a nullfs mount.
The --mount option tells Kleene to create a nullfs mount, where /path/to/getting-started is the app-repos on the host, and /mnt is where that directory should be mounted inside the container.
- After running the command, Klee starts an interactive
shsession in the root directory of the container's filesystem. Now, list the contents of/mntwithin the container:
# ls /mnt
ls /mnt
.dockerignore LICENSE docs
.git README.md mkdocs.yml
.github app requirements.txt
.gitignore build.sh
Dockerfile docker-compose.yml
This is the directory that you mounted when starting the container. Listing the contents of this directory displays the same files as in the getting-started directory on our laptop.
- Create a new file named
myfile.txt.
# touch /mnt/myfile.txt
# ls /mnt
.dockerignore LICENSE docs
.git README.md mkdocs.yml
.github app myfile.txt
.gitignore build.sh requirements.txt
Dockerfile docker-compose.yml
-
Now if you open this directory on the host, you'll see the
myfile.txtfile has been created in the directory. -
From the host, delete the
myfile.txtfile and look into the container again. The file has disappeared. -
Type
exitin the container-console to exit the container and close the session.
This demonstrated how files are shared between the host and the container, and how changes are immediately reflected on both sides. Now let's see how we can use nullfs mounts during application development.
Run your app in a development container¶
The following steps describe how to run a development container that does the following:
- Nullfs-mount our source code stored on the host in
/home/jane/getting-started/app, into the container. - Install all dependencies
- Start
nodemonto watch for filesystem changes
So, let's do it!
-
Make sure to delete any
webappcontainers previously created. -
Make a Dockerfile for a new image that does not contain the application source code. In this example, the easy way is to build on the existing image where
nodeandyarnare already installed. Ideally we would split up the first image in two, such that we had a 'base image' containing the necessary software packages and another image for setting up our application.
Save the following content in Dockerfile.dev
FROM webapp:latest
RUN rm -rf /app
# Listens on port 3000
CMD cd /app && yarn install && yarn run dev
in same directory as the other Dockerfile. The CMD instruction starts a Bourne shell (sh) and runs yarn install to install dependency packages and then running yarn run dev to start the development server. If we look in the package.json, we'll see that the dev script starts nodemon.
- Build the new image in a similar way as the previous image:
-f Dockerfile.dev, thus avoiding the default Dockerfile we created previously. The above command should be executed in the getting-started/app directory. - Run the following command from the
getting-started/appdirectory.
- The
-dflag is omitted so the container output will be printed to the terminal. - The
-n testnetconnects the container to our testing network. --mount /home/jane/getting-started/app:/app- nullfs mount our application source code from the host into the/appdirectory in the container.webapp-dev- the image to use. This is our newly built image from above.
You should see output similar to this:
<initial output here>
$ nodemon src/index.js
2.0.20
to restart at any time, enter `rs`
watching path(s): *.*
watching extensions: js,mjs,json
starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
Now you can hit Ctrl+C to return to the terminal prompt. Don't worry, your container is stilling running. Use klee lsc to be sure.
- Now, make a change to the app. In the
src/static/js/app.jsfile, on line 109, change the "Add Item" button to simply say "Add":
Save the file.
- Refresh the page in your web browser, and you should see the change reflected almost immediately. It might take a few seconds for the Node server to restart. If you get an error, try refreshing after a few seconds.

Using nullfs mounts is useful for local development setups. The advantage is that the development machine doesn't need to have all of the build tools and environments installed. With a single klee run command, dependencies and tools are ready to go.
Next steps¶
In order to prepare for production, you need to migrate your database from working in SQLite to something that can scale a little better. For simplicity, you'll keep with a relational database and switch your application to use MariaDB. But, how should you run MariaDB? How do you allow the containers to talk to each other? You'll learn about that next!