Unable to set permissions within shared folder using Vagrant and VirtualBox

Yellow door with an old lock.
Unlock your potential.

I recently started experimenting with Vagrant (v1.5.1), Puppet (v3.5.1) and VirtualBox (v4.3.10) to replace my MAMP environment. I used the config.vm.synced_folder method in Vagrant to share a folder of websites with the virtual machine.

Since a few directories, like caching and user uploads, need to be writable by Apache, I figured I’d use Puppet to set those permissions, but regardless of where I specified that requirement, it never took effect — and Puppet did try:

Notice: /Stage[main]/Ryse_apache/File[/var/www/domains/domain.com/www/htdocs/wp-content/cache]/owner: owner changed 'vagrant' to 'apache'
Notice: /Stage[main]/Ryse_apache/File[/var/www/domains/domain.com/www/htdocs/wp-content/cache]/group: group changed 'vagrant' to 'apache'

I found out that this was a limitation with VirtualBox and how it shares folders.

Now, Vagrant does have additional options that can be set when sharing a folder that allow you to specify the owner and group, but the problem is that Puppet installs Apache, and since the folders are shared before the Puppet provisioner runs, the Apache user does not yet exist.

This is the error you get:

Failed to mount folders in Linux guest. This is usually because
the "vboxsf" file system is not available. Please verify that
the guest additions are properly installed in the guest and
can work properly. The command attempted was:

mount -t vboxsf -o uid=`id -u apache`,gid=`getent group apache | cut -d: -f3` /var/www/domains /var/www/domains
mount -t vboxsf -o uid=`id -u apache`,gid=`id -g apache` /var/www/domains /var/www/domains

Let’s look at a few solutions on how to allow Apache to write to the folders it needs to, and which solution I ended up implementing. If you have a different permissions problem, you might still be able to tweak one of the solutions below, since they broadly cover an array of issues.

Setup

Here is the core of my Vagrantfile:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "centos65"
  config.vm.box_download_checksum = "66b7a6b11e040f20e78ce269c8459918fcb1941c938b    acc71bbc48eac33b9cdf"
  config.vm.box_download_checksum_type = "SHA256"
  config.vm.box_url = "https://github.com/2creatives/vagrant-centos/releases/down    load/v6.5.1/centos65-x86_64-20131205.box"
  config.vm.hostname = "server01.local.vps"
  config.vm.network "private_network", ip: "192.168.10.100"
  config.vm.synced_folder ".", "/vagrant", disabled: false
  config.vm.synced_folder "/Users/ryansechrest/Projects/Sites", "/var/www/domains"

  config.vm.provider "virtualbox" do |virtualbox|
    virtualbox.gui = false
    virtualbox.name = "CentOS 6.5 - Server 01"
    virtualbox.memory = 2048
    virtualbox.cpus = 2
  end
end

Using the configuration above, the shared folder’s owner and group will be vagrant.

Solution 1: Set permissions of directories to 777 and files to 666

You can use the dmode and fmode mount options to set the permissions of all directories to 777 (rwxrwxrwx) and all files to 666 (rw-rw-rw-):

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  ...
  config.vm.synced_folder "/Users/ryansechrest/Projects/Sites", "/var/www/domains", mount_options: ["dmode=777", "fmode=666"]
  ...

end

Of all the solutions, this is probably the easiest and fastest, but making all files world-writable is generally regarded as bad practice. That said, if the environment is just used for local development, it may not be an issue, but personally, since it’s not something I’d do in a production environment, I opted out of this solution.

Solution 2: Remount shared folder with desired permissions after Puppet provisioner

You can run a shell provisioner that remounts your shared folder with the desired owner and group after the Puppet provisioner runs:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  ...
  config.vm.synced_folder "/Users/ryansechrest/Projects/Sites", "/var/www/domains"
  ...

  config.vm.provision "puppet" do |puppet|
    puppet.manifest_file  = "site.pp"
    puppet.manifests_path = "manifests"
    puppet.module_path = "modules"
  end

  config.vm.provision "shell",
    path: "bootstrap-post.sh"
end

As you can see, the Shell provisioner follows the Puppet provisioner by executing bootstrap-post.sh:

#!/bin/bash

# Set permissions
mount -t vboxsf -o uid=`id -u apache`,gid=`id -g apache`,dmode=755,fmode=644 /var/www/domains /var/www/domains

If you prefer, you could also do this inline:

config.vm.provision "shell",
  inline: "mount -t vboxsf -o uid=`id -u apache`,gid=`id -g apache`,dmode=755,fmode=644 /var/www/domains /var/www/domains"

This solution works great when you boot your virtual machine for the first time or resume it from a suspended state, but when you boot your machine the following times, the shared folder won’t be remounted with the desired permissions, because the provisioners already ran.

Now, you can get around this by simply using the provision flag when booting (vagrant up --provision), but it’s something you have to remember. In addition, you may not always want to reprovision your machine, because you might be working on something that could get reset.

Because of this caveat, I didn’t opt for this solution.

Solution 3: Use Rsync to “share” folders

You can use Rsync to share your folder with the virtual machine:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  ...
  config.vm.synced_folder "/Users/ryansechrest/Projects/Sites", "/var/www/domains", type: "rsync"
  ...

end

This is easy to setup, but, depending on the size of your shared folder, can be very slow when booting for the first time and also take up a lot of space, because Rsync copies every file from the host to the virtual machine.

You can also run vagrant rsync-auto, which will start watching your shared folder for changes and sync them automatically. I found that it takes at least 40-45 seconds for Rsync to start and sync one file; I reckon that subsequent files would follow quickly.

Another thing to keep in mind is with regard to machine state:

The rsync-auto command does not currently handle machine state changes gracefully. For example, if you start the rsync-auto command, then halt the guest machine, then make changes to some files, then boot it back up, rsync-auto will not attempt to resync.

To ensure that the command works properly, you should start rsync-auto only when the machine is running, and shut it down before any machine state changes.

You can always force a resync with the rsync command.

(source)

Rsync provides you with the most flexibility in terms of permissions, because you can specify exactly what you need. In addition, if you’re mostly working with text files and can get over the initial sync wait, and the small resync delay, this may be a viable solution for you.

I didn’t opt for this solution, because I have gigs of data in my shared folder and frequently destroy the virtual machine, so the initial wait is too long. Also, if you’re rapidly going back and forth between coding and testing, 45 seconds is a lifetime.

Solution 4: Run Apache under Vagrant

You can configure Apache to run under the Vagrant user:

# /etc/httpd/conf/httpd.conf

...
User vagrant
Group vagrant
...

If you’re using the PuppetLabs Apache module, it looks like this:

class { "apache":
  user => "vagrant",
  group => "vagrant",
}

This is the solution I implemented. It gives Apache the necessary permissions to modify files, albeit very generously, since it can modify much more than it needs to, but it’s better than making everything world-writable, having to reprovision at inconvenient times, or waiting for any files to synchronize.

Potential other solutions

  1. Share project via Git instead of shared folder — Instead of sharing the folder containing the website, you could simply check it out from a repository (whether shared or remote). You can even do an initial clone using Puppet, so there’s something there when you create the virtual machine. This allows you to set any permissions you like, with the caveat that you have to commit and push all your changes to the virtual machine for testing. I didn’t opt for this solution, because it’s far less efficient and almost defeats the purpose of having a local development environment in the first place.
  2. Create Apache user in box the file — Instead of creating the Apache user during the installation, you could use a box file that already has an Apache user, which would allow you to use that user via the mount options. That said, it does require you to either find or maintain your own box file with that user already created. Since I use minimal CentOS box files that are maintained by others, it’s an overhead I don’t need.
  3. Use a different provider — I believe this issue is isolated to VirtualBox, so using a different provider, like VMware or Hyper-V, may circumvent the issue altogether.

Conclusion

I hope you found a solution here that works for you, but if you think of anything else or have any suggestions, I’d love to hear them… leave a comment below.

Featured image by iMattSmart.


Comments (11)

Previously posted in WordPress and transferred to Ghost.

Jesús Gómez
January 6, 2015 at 7:21 pm

Thank you! very helpful.

David Winiecki
January 11, 2015 at 1:00 pm

I think you don’t need to use --provision every time you vagrant up if you use the run: "always" option. But like you said you still may not want to provision every time.

I haven’t actually tried using run, but the documentation says it should work.
Vagrant.configure("2") do |config|
  config.vm.provision "shell", inline: "echo hello",
    run: "always"
end
https://docs.vagrantup.com/v2/provisioning/basic_usage.html

Ryan Sechrest
January 12, 2015 at 10:56 am

That’s great tip– thanks for sharing it!

Juan
January 23, 2015 at 3:42 pm

Awesome! Thanks so much!
The solution 4 is the only one that worked. I tried everything, chown, chmod, mount options, nothing worked no matter how many times y provisioned the vm.

The sync would always set everything back to vagrant:vagrant. And not sync ever again, just one time.

Changing User and group to vagrant did the trick.

Sharkey
June 8, 2015 at 2:34 am

Thanks for this; it’s nice to find answers from local developers!

I found another way to do it – if you can fix the ids of the user/group you need, you can do the following:
config.vm.synced_folder "../local-folder", "/path/to/vm-folder/", group: 5000, owner: 5000, mount_options: ['blah=blah']
When using the UID/GID, Vagrant doesn’t care if they exist yet. This way I can keep the same users/groups as our production env.

Markus Schulte
February 10, 2016 at 4:34 am

Well done. This problem is discussed on stackoverflow, too, but misses your “Solution 1”. This solution actually fits my use-case best, so I have added it on stackoverflow (http://stackoverflow.com/a/35312716/1645517) linking to your article.

Ryan Sechrest
February 10, 2016 at 9:48 am

Thanks, Markus! Happy to see that this is still helping people!

Claudie
March 14, 2018 at 10:32 am

Thank you so much!! I just saved so much precious time.