Storing Files in Hetzner Volumes with Laravel and Statamic

Storing large amounts of data online has become incredibly cheap and easy in the last few years. Just hook your app up to Amazon S3 or one of the other cloud providers and enjoy virtually endless storage capacity.

But there are also other alternatives: if you're using the Hetzner Cloud for your hosting, you can easily add storage volumes for the low price of 0,04€ per GB per month.

I had the opportunity to use this for a recent project where the client didn't want to use any American cloud provider but needed to store around 50GB of data with another 20GB added every year, without breaking the bank.

So here's a quick tutorial on how you can use Hetzner Volumes for your Laravel app or your Statamic site.

Creating Your Volume

Setting up the storage volume is easily done using the Hetzner Cloud Console. Navigate to the server you want to attach it to, select volumes and create a new one. Next you get to chose the size (which can easily be increased later), as well as the file system type (ext4 vs xfs).

There are some minor differences between ext4 and xfs, but in most cases (including mine), you can just go with ext4. Feel free to do your own research if you think your case might be different.


Creating a new storage volume using the Hetzner Cloud Console (German).

After creating it you will get a screen with four commands you need to run on your server. Note that all of these require admin privileges (sudo) and they will obviously be different depending on the name of your volume.

mkfs.ext4 -F /dev/disk/by-id/scsi-0HC_Volume_20906626
mkdir /mnt/volume-nbg1-1
mount -o discard,defaults /dev/disk/by-id/scsi-0HC_Volume_20906626 /mnt/volume-nbg1-1
echo "/dev/disk/by-id/scsi-0HC_Volume_20906626 /mnt/volume-nbg1-1 ext4 discard,nofail,defaults 0 0" >> /etc/fstab

The last line wouldn't work for me but gave me a 'permission denied' error instead. I was however able to open the file with nano /etc/fstab and add the line /dev/disk/by-id/scsi-0HCVolume20906626 /mnt/volume-nbg1-1 ext4 discard,nofail,defaults 0 0 manually, which should do the same.

After this your volume is mounted on the server and should always be activated again automatically if you have to restart it.

Setting Permissions

In order to use the new volume your app needs to be able to read from and write to it, something that currently requires administrator privileges. So first we need to figure out the correct user - which in my case is called ploi, but in most setups is www-data.

You can check who "owns" your project by running ls -ld . in your app's main folder which in my case returned drwxr-xr-x 17 ploi ploi 4096 Jul 1 10:21 .. Running the same command (ls -ld /mnt/volume-nbg1-1) on the newly created mount returns -rw-r--r-- 1 root root 0 Jul 1 10:46 /mnt/volume-nbg1-1, meaning it is owned by root and therefor not accessible to ploi.

So let's change that by making ploi the new owner of the mount including all subfolders: sudo chown -R ploi:ploi /mnt/volume-nbg1-1

Run the ls command on the mount again to make sure it worked, and you should get the correct output: drwxr-xr-x 3 ploi ploi 4096 Jul 1 10:46 /mnt/volume-nbg1-1

Create a Symlink and Filesystem Disk

We could just use the mount path to store our files to, but let's make it a little prettier by adding a symlink from our projects storage folder.

Running ln -s /mnt/volume-nbg1-1 storage/volume in your project creates a new symlink volume inside your storage folder, which is linked to the mount. As far as Laravel is concerned it's only writing to this new folder inside storage, so we don't need to worry about the actual location of the mount.

Now that we have a storage path, we can simply add a new filesystem to the Laravel config and give it a descriptive name - I decided to stick with volume here.

'disks' => [ 'volume' => [ 'driver' => 'local', 'root' => storage_path('volume'), ], ]
Defining a new filesystem to be used in Laravel

You can now use this filesystem disk in your Laravel app just like you would use local or public. As always you can check out the documentation for details.

The above definition is best for internal files that you don't want anyone to access directly. If you're storing public assets (images, videos, etc.) in your volume that you want to publicly display on your website, you should create your symlink in the public folder (ln -s /mnt/volume-nbg1-1 public/volume) and use a definition like this:

'disks' => [ 'volume' => [ 'driver' => 'local', 'root' => public_path('volume'), 'url' => env('APP_URL').'/volume', 'visibility' => 'public', ], ]
Defining a new filesystem that's publicly accessible

Using Your Filesystem in Statamic

If you're building your site using the Statamic CMS, there is one additional step before you can store assets in your new volume. Simply navigate to Assets in your Control Panel and either create a new container or edit an existing (empty) one.


Creating a new asset container using the new volume disk in Statamic.

The list of storage disks will now have an additional entry with the name you chose earlier. Once you select it and save your container, your assets will be saved to the new storage volume.

Resizing Your Volume

It's important to keep in mind that unlike cloud storage such as Amazon's S3, your volume doesn't automatically grow depending on the amount of data in it. So if you're about to exceed its capacity, you will need to manually resize the volume to keep adding data.

In your Hetzner Cloud Console you can simply navigate to the volume in question, edit it and increase the capacity however you like. Note that it is not possible to shrink a volume - in that case you will have to create a new one instead and manually move your files over.

After increasing the size of the volume, the server's filesystem isn't yet aware of the change. You can see this by running the command df on your server and looking for the mounted volume. In my case I had increased the disk size to 50GB, but my filesystem still only knew about the old 30GB: /dev/sdb 30832548 808868 28434432 3% /mnt/volume-nbg1-1

This can easily be fixed however, by running the command sudo resize2fs /dev/sdb for ext4 formatted volumes or sudo xfs_growpart /dev/sdb for xfs ones. Afterward df should return the right size, in my case roughly 50GB: /dev/sdb 51474912 816904 48208948 2% /mnt/volume-nbg1-1

And that's all of it. Hetzner Cloud allows you to connect up to 16 volumes to a server which can hold up to 10TB of data each, so you should never run out of space for your projects.

More posts: