#import.meta.glob not working on production

159 messages · Page 1 of 1 (latest)

proven stream
#

I have a directory outside my project root, which I've symlinked to ./src/assets/external From here, I have /midelco/uploads which holds my product images. I added preserveSymlinks in my Vite config too.

I even symlinked the same directory structure in my local environment to verify it's working.

In my code I have:

const allImages = import.meta.glob<{ default: ImageMetadata }>("/src/assets/external/midelco/uploads/*.{jpeg,jpg,png,JPG}");

Which returns the images in that directory:

all images:  {
  '/src/assets/external/midelco/uploads/29138_0.jpg': [Function: /src/assets/external/midelco/uploads/29138_0.jpg],
  '/src/assets/external/midelco/uploads/29138_1.jpg': [Function: /src/assets/external/midelco/uploads/29138_1.jpg],
  '/src/assets/external/midelco/uploads/29138_2.jpg': [Function: /src/assets/external/midelco/uploads/29138_2.jpg]
}

But on production, this is always an empty array. It has the same directory structure, and I use a pre-build script that creates the symlink before the build starts.

I SSH'ed in and see the symlink present with the matching directory structure, and images.

Yet, the glob returns empty....

proven stream
#

When I import an image directly, it also doesn't work:

[vite]: Rollup failed to resolve import "@/assets/external/midelco/uploads/29138_0.jpg" from "/app/src/pages/test.astro".
proven stream
#

Gonna try a bind mount instead of a symlink. See if that helps

proven stream
#

Failed because of permission issues. Even though the container's user is already root.

#

Has to be some kind of restriction on the mounted volume.

full prairie
proven stream
#

I tried that when I was still using symlinks, but it didn't work

#

Redeploying right now with --cap-add=SYS_ADMIN to see if that allows the bind mount

#

no luck

#

permission issue is gone, but it's still failing to mount

#

gpt says it's simply not possible to do in a container

#

gonna try symlinks again

proven stream
#

I'm using this in my config:

vite: {
    server: {
        fs: {
            allow: [
                ".", // always allow the project root
                "/external", // explicitly allow following the symlink target
            ],
        },
    },
    resolve: {
        preserveSymlinks: true,
    },
},
#

Which I don't need locally to get it working btw. The glob returns images locally. But on production it's still empty

#

the directory tree is identical

void tusk
#

Is it all happening in a rocker container ?

proven stream
#

yes

void tusk
#

Can we see the docker file?

proven stream
#

In Hetzner I mounted my volume in /mnt/disk
In Coolify I mounted /mnt/disk to the container's /external path

I see this path when I attach to the container.

Then I use a pre-build script to create the symlink, which I also see in ./src/assets/external and all the content is inside of it.

I don't use a docker file. I use Coolify's deploy from a private repo

#

so the path is there before the build starts. But glob just doesn't see any images for some reason

void tusk
#

Ah yeah was just going to see if the volume is there

#

and if you ls inside the docker -it you see the files ?

proven stream
#

yes

#
lrwxrwxrwx 1 root root    9 Apr 26 14:27 external -> /external

That's the mount in my ./src/assets directory

#

and when I run tree:

external
|-- lost+found
|-- midelco
|   `-- uploads
|       |-- 10072_0.jpg
|       |-- 10281_0.jpg
|       |-- 10281_2.jpg
|       |-- 10281_3.jpg
|       |-- 10281_4.jpg
#

I use the path to the uploads directory in my glob import

#

And locally, where I recreated the same tree using a symlink as well, it works. Images appear on the page.

#

But on production it doesn't work because the glob is empty

void tusk
#

So my thought is there’s a lead somewhere in the differences between your machine and the prod machine

#

on local have you done it in docker etc just the same ?

proven stream
#

No local isn't docker. But it is symlinked.

#

Could it be a read permission issue?

#
drwxr-xr-x 3 root root     4096 Apr 17 15:47 ..
-rw-rw-r-- 1 1000 1000   207838 Apr 25 12:24 10072_0.jpg
-rw-rw-r-- 1 1000 1000   182851 Apr 25 12:24 10281_0.jpg
-rw-rw-r-- 1 1000 1000   251145 Apr 25 12:24 10281_2.jpg
-rw-rw-r-- 1 1000 1000   210452 Apr 25 12:24 10281_3.jpg

#

maybe docker's root user can't read from user 1000 ?

void tusk
#

Ya maybe

#

If not I’d try to do with locally with docker

#

Also is your local OS same as prod?

proven stream
#

Only in that it's Linux

#

I use Manjaro, production uses Ubuntu

#

and Docker uses alpine I think?

#

w/e nixpacks uses

void tusk
#

Mm

#

ya I guess try the perms, and maybe reduce variables by not using the ts config path alias

proven stream
#

I'm not using aliases in my glob line

void tusk
proven stream
#

That was when I tested importing a specific image directly

#

No luck. I tried changing the ownership of the directory and files using chown between my pre-build and build script. Didn't work

void tusk
#

Ya personally I’d try to get it going locally in docker I guess

void tusk
proven stream
#

the symlinked dir

void tusk
#

Mm

#

Was tree included in the image btw unrelated lol

proven stream
#

no I installed it

#

I want to avoid having to install docker on my local machine to debug this tbh.

#

But Coolify's discord is tough to get support. Most of my question have been ignored so far.

void tusk
#

It’s a pretty niche issue

#

But basically we’ve identified it works outside docker

#

And so we can conclude that it’s probably related to docker

#

You’ve added the volume and can see the files inside the container in that volume

#

Then you symlinked inside the container to that volume and your src

proven stream
#

I'm gonna try to rebuild within the container. See if that does anything

void tusk
#

and you’re trying to build inside the container and it’s failing to resolve

#

oh interesting where are you building now

proven stream
#

just running npm run build --remote inside the container

void tusk
#

oh ok you just mean build inside the container again

proven stream
#

yeah

#

as opposed to redeploying from the Coolify dashboard

#

this could rule out any pre-build issues

#
...
...
  vite:env } +6ms
  vite:env loading env files: [ '/app/.env', '/app/.env.local', '/app/.env.', '/app/.env..local' ] +0ms
  vite:env env files loaded in 0.71ms +0ms
  vite:env using resolved env: {} +6ms
Killed
#

great

#

back to square one

#

Why can't things just work

full prairie
#

Couldnt you just copy the images from the volume into the docker container at build time and then link them?

proven stream
#

Was just about to say that that might be my only option.

#

But it's 7gb (+12.000) images

#

gonna add up to the build time, and storage capacity.

#

It will grow as more products are getting added

full prairie
#

Why not just throw them into S3 and serve them over a CDN?

proven stream
#

Because these images are uploaded via sftp from a Point of Sale app on the clients PC, when they add new products.

full prairie
#

And they are going into the Volume ?

proven stream
#

Yes

#

Set up a sftp server on hetzner and the PoS app connects to it

#

They don't support S3

full prairie
#

I see. What about just setting up like a basic Hono API that serves the images from the volume?

proven stream
#

I'm not familiar with Hono. But would I get the same issue? If I created a new app in Coolify that will act as an endpoint to get images from the volume, it's going to have the same issues as the Astro container

#

I wonder if it works if I symlink to /public

full prairie
#

Okay. So if you just ssh into the server. Can you access the volume and images over the CLI?

proven stream
#

yes

#

also when I attach to the container

void tusk
full prairie
#

I acknoledge my use case is probably very specific, and I found a way to circumvent the issue by adding an alias so that the symlinked packages can resolve their own path.

proven stream
#

I saw that, but it's marked as 'Completed'

void tusk
#

closed by author with message hunter copied

full prairie
#

This may be really hacky. But how about running something like rclone on a cron? Direct on the server and having it copy the files into S3 ?

#
#

Can set it up so it doesnt copy duplicates and stuff.

proven stream
full prairie
#

Maybe? im not sure

proven stream
#

ditched that idea after I found out

full prairie
#

That shouldnt matter though. Once they put the images on the volume. You can do whatever you want with them

proven stream
#

Yes, but it's an extra service, extra action, extra fees, extra work+maintenance...

#

I'll keep it in mind though

void tusk
#

The issues doesn’t explain it working on local though

#

Did you try?

#

I also think it’s a vite alias not a tsconfig one

#

And blu said it could be a bug

#

So if you can reproduce this it might be a good citizen moment to add the reproduction steps to the issue

proven stream
#

Testing it out now. I added an alias in the vite config, and in tsconfig

#

Can't get the alias to work:

alias: {
     "@external": path.resolve(__dirname, "./src/assets/external"),
 },

path is not defined
also tried it with just the path, and no resolve around it

#

Also tried symlinking to /public, but can't read the images

#

ah right, I had to copy it to /dist in the container

void tusk
#

Bummer

proven stream
#

also tried setting preserveSymlinks: false, instead of true because it treats the symlink differently

#

but no...

#

gonna try to use a normal directory instead of a symlink, with some dummy images to rule out some stupid typo if there is one

proven stream
#

Looking into Minio to self host an S3 bucket

#

But buckets don't work by pointing it to a directory with existing assets

void tusk
#

Love minio

proven stream
#

Not sure if it's possible to use without rcloning my images to it

void tusk
#

it has its own cli to injest stuff but ya

#

It would still need to be duplicated

proven stream
#

right, but I want to avoid that

void tusk
#

ya

proven stream
#

gonna try bind mounts again and see if there's anything I haven't tried yet

#

there's the docker --privileged flag I could try but that introduces some security concerns

#

I want to find out why it failed without a specific reason when I used --cap-add=SYS_ADMIN

proven stream
#

I reverted back to bind mounts again. I get the permission issues with I use my node script which mounts the directories. But if I omit the script, let it deploy (without the mount, but also without errors), and attach to the container to mount manually, I get no errors.

#

This is with --cap-add=SYS_ADMIN --privileged

#

So I can mount manually, but that's after the build so it's no use at that point. But I cannot mount pre-build with my node script.

celest agate
#

Sooo lets see, how did you mount the directory in coolify? Using ther UI?

proven stream
#

Yes

#

And I can see it when I attach to the container

celest agate
#

Could you share a screenshot of how that config looks?

proven stream
#

So inside the container /external holds directories with assets like images

celest agate
#

Could you try changing that to a volume mount instead?

#

It's really just a shot in the dark because while bind mounts and volume mounts look the same they function very differently

proven stream
#

Alright, let me try that

#

Alright I got the volume to appear in the container succesfully. Do you think I should try a bind mount or a symlink in order to get it in the project?

celest agate
#

So if the volume's there a symlink should do the trick at this point

#

Assuming vite or the package resolving the paths does follow the symlink at least

proven stream
#

No luck. Same as before

#

I used a symlink with the same directory structure locally as well, and it works. But not on prod

#
const allImages = import.meta.glob<{ default: ImageMetadata }>("/src/assets/external/midelco/uploads/*.{jpeg,jpg,png,JPG}");

// get just the first 10 entries, to avoid flooding the logs
console.log("all images: ", Object.keys(allImages).slice(0, 10));

Output on local:

all images:  [
  '/src/assets/external/midelco/uploads/29319_2.jpeg',
  '/src/assets/external/midelco/uploads/29319_3.jpeg',
  '/src/assets/external/midelco/uploads/29319_4.jpeg',
  '/src/assets/external/midelco/uploads/29319_5.jpeg',
  '/src/assets/external/midelco/uploads/29319_6.jpeg',
  '/src/assets/external/midelco/uploads/31411_0.jpeg'
]

On prod:

all images:  []
#

Tree in container:

external
|-- lost+found
|-- midelco
|   `-- uploads
|       |-- 10072_0.jpg
|       |-- 10281_0.jpg
|       |-- 10281_2.jpg
|       |-- 10281_3.jpg
|       |-- 10281_4.jpg
|       |-- 10882_0.jpg
#

And this is my astro config:

vite: {
    plugins: [tailwindcss(), pagefind()],
    server: {
        fs: {
            allow: [
                ".",
                "/external2",
            ],
        },
    },
    resolve: {
        preserveSymlinks: true,
    },
},
celest agate
#

Starting to think that this is a vite issue, are there any similar issues on their repo?

celest agate
#

The issue seems like a lead

proven stream
#

I contacted one of the authors. Received some instructions, but I already tried those. (creating aliases). Will try again though. Perhaps they were incorrect.

void tusk
#

you tried the alias thing? This thread would be hard for someone to jump into by the way might be worth giving a recap for anyone else to see if they have ideas

proven stream
#

Yes, I tried many alias variations in both the Astro config as the ts config.

#

You think I should create a new thread?

void tusk
#

I’d just a recap of everything we’ve tried so far and the issue

I.e that you can see the folder inside your container, inside your src and when you cd into it and ls you see the images etc

proven stream
#

Gotcha. Will do

#

For anyone just joining - I'm trying to glob import images from a symlinked directory.

I have a VPS running Coolify where my Astro site is deployed. On the host I have a directory where a 3rd party sFTP's images to: /mnt/disk These are the product images I need to display on the static product pages.

I mounted this as a volume to the docker container (/external) where Astro runs in and I confirmed the directory is mounted and the contents are present.

Then I created a pre-build script that created a symlink from /external to /app/src/assets/external

I also confirmed this gets created succesfully and the contents are there. In fact, if I symlink it to /public, I can browse to the images inside the directory and view them. However, I want to import them so I can use the <Image /> component.

The reason I chose a symlink is because the directory is outside the project root, a 3rd party will frequently push images to it, the directory can't get deleted during a rebuild/deploy, and I don't want to copy the files because it's 7.5GB and growing (+12.000 files).

I tried many alias variations in my Astro and TS config
For example:

tsconfig.json

"paths": {
    // Match the Astro alias setup
    "@midelco/*": ["src/assets/external/midelco/*"],
    "@midelco-rel/*": ["src/assets/external/midelco/*"],

    // Aliases for the external folder
    "@external/*": ["src/assets/external/*"],
    "@external-rel/*": ["src/assets/external/*"]
}
#

Astro config:

vite: {
        server: {
            fs: {
                allow: [
                    '/app/src/assets/external',
                    '/app',
                ],
            },
        },
        resolve: {
            // preserveSymlinks: true,
            alias: {
                // Absolute path alias for the symlink
                '@midelco': fileURLToPath(new URL('./src/assets/external/midelco', import.meta.url)),
    
                // Aliases relative to the root directory
                '@midelco-rel': '/app/src/assets/external/midelco',
    
                // Shortened alias for the external folder
                '@external': fileURLToPath(new URL('./src/assets/external', import.meta.url)),
                '@external-rel': '/app/src/assets/external',
            },
        },
    },
#

Test page:

console.log("Testing all glob imports...");

// Import all images from @midelco/uploads
const allImages = import.meta.glob<{ default: any }>('@midelco/uploads/*.{jpeg,jpg,png,JPG}');
console.log('allImages (alias @midelco):', allImages);

// Import all images using @midelco-rel alias
const allImagesRel = import.meta.glob<{ default: any }>('@midelco-rel/uploads/*.{jpeg,jpg,png,JPG}');
console.log('allImagesRel (alias @midelco-rel):', allImagesRel);

// Import all images using @external alias
const allImagesExternal = import.meta.glob<{ default: any }>('@external/midelco/uploads/*.{jpeg,jpg,png,JPG}');
console.log('allImagesExternal (alias @external):', allImagesExternal);

// Import all images using @external-rel alias
const allImagesExternalRel = import.meta.glob<{ default: any }>('@external-rel/midelco/uploads/*.{jpeg,jpg,png,JPG}');
console.log('allImagesExternalRel (alias @external-rel):', allImagesExternalRel);

// Import all images using explicit path
const allImagesExplicit = import.meta.glob<{ default: any }>('/app/src/assets/external/midelco/uploads/*.{jpeg,jpg,png,JPG}');
console.log('allImagesExplicit (explicit path):', allImagesExplicit);

// Import all images with case-insensitive extensions
const allImagesCaseInsensitive = import.meta.glob<{ default: any }>('@midelco/uploads/*.{[jJ][pP][eE][gG],[jJ][pP][gG],[pP][nN][gG]}');
console.log('allImagesCaseInsensitive (case-insensitive):', allImagesCaseInsensitive);