Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

byustephen's avatar

Public Symlink With Mac and Windows developers, with an Ubuntu Production Machine

The App:

Student Login system where student's upload their photos, we tag them, and then the administrators can attach those photos to weekly update emails.

The Problem

Don't know the best way to store photos that can be displayed in a public manner like a gallery to show the photos. Plus we have a developer on a Windows machine, another on a Mac, and the production server is Ubuntu. All with different paths to the app. This means the symlink option doesn't seem to work.

The Options

  1. Create a small function to serve the files from behind the webroot. Then we create a route that serves files from the uploads folder. Kind of like in this forum thread: https://laravel.io/forum/04-23-2015-securing-filesimages

  2. Put all the photos in the public directory, and don't worry about security.

  3. Find some way to have symlinks work on three different machines. Turn off the symlink in git through the gitignore, and make sure it works with automated deployment tools like Rocketeer. Seems 'trixsy' - Gollum.

Thoughts

So, what's the best way to do this here? I think we are leaning towards #1, and that seems like the most secure and the best cross-platform solution. We'll just make sure that when we pass in a parameter that we scrub it, make sure that only certain extensions are allowed (like jpg, png, gif). Still, is there a better way?

0 likes
3 replies
byustephen's avatar
byustephen
OP
Best Answer
Level 3

I wanted to update our answer since this changed a bit. We keep all our files outside the webroot as it was getting a little tricky to make sure we had all the right paths when mixing windows and mac dev platforms.

We went with keeping the files in a static folder outside the webroot for security. And we pull the files via php on page load with lazyloading to keep the bandwidth down.

So here's the code:

Route Declaration
//Image Controller
Route::get('images/{image}', 'PhotoController@get_image_from_directory')->name('images');

The Actual Function

    /**
     * Routes helper function to show the images 
     * Has some minimal string parsing for security.
     * Consider locking this down to mime type 
     */
    public function get_image_from_directory($image = null){

        //simple directory traversall protection
        $image = str_replace('../', '', $image);

        $file_info = new SplFileInfo($image);
        $extension = $file_info->getExtension();
        $extension = strtolower($extension);

        //unsupported extension check
        if($extension != 'jpg' and $extension != 'jpeg' and $extension != 'png' and $extension != 'gif'){
          return;
        }

        $app_file_path = $_ENV['APP_FILE_PATH']; 
        $path = $app_file_path . '/' . $image;

        if (file_exists($path)) { 
          return Response::download($path);
        }

    }

And actually showing the image

<img class="lazyload" src="{{ asset('default.jpg') }}" data-src="{{ route('images', $file->filename) }}" width="100%"></img>
1 like
Cronix's avatar

@byustephen Some additional options are to have the devs use Laravel Homestead to develop on, which is an Ubuntu image with everything laravel needs out of the box. Then your dev environments would match your production environment and everything "will just work". This is what we do with 14 devs, using different os's and versions of those os's. It's works great with 0 issues.

Another option is to use AWS S3 for the image storage. Then the "local" paths don't matter as it's pulling from the S3 bucket, so all paths would be the same for everybody.

I'm not a fan of the code you ended up with. It seems counter productive to run every image request through a function just so it will aide in different development environments, and putting an additional strain on your production server on top of it (where it isn't needed or wanted).

byustephen's avatar

Thanks for the input. I never said this was a perfect solution, but it does work. S3 would work as well, but we didn't want to pay for the storage.

The actual ideal solution is to keep the symlink out of the git repo, and then just run the artisan command to link the public storage every time when you deploy. That's what I did on my last app that I built and it works great (but in my later app I put everything in the public directory since it needed less security).

So there you go, options are: -behind web root and load via php function -3rd party storage like S3 -a few gitignore tweaks and a deploy script that links up the shared directory every time. -And I am sure there are more.

Please or to participate in this conversation.