fix(uploads): pre-create journey and places upload dirs

Both were created lazily on the first upload, which needs write permission
on the uploads mount point itself at request time. On hosts whose
bind-mounted uploads dir is not writable by the runtime user, every feature
writing into an already-existing subdir keeps working while journey photo
and place image uploads fail with EACCES (#1762). Creating them in the
image and at boot turns that into one loud startup failure instead of a
stray 500 on upload.

Also drop the recursive chown over all of /app from the final layer: it
copied up every inode it touched and duplicated node_modules, dist and the
client bundle into a 518 MB layer. The chown now rides the layer that
installs node_modules, later copies carry --chown=node:node, and the last
layer chowns only the paths it creates. Everything under /app stays
node-owned; image size 2.36 GB -> 1.68 GB.
This commit is contained in:
jubnl
2026-07-31 15:40:24 +02:00
parent f02f255016
commit 1fb6d19f6f
2 changed files with 38 additions and 15 deletions
+12 -2
View File
@@ -10,16 +10,26 @@ import http from 'node:http';
import type { INestApplication } from '@nestjs/common';
import { buildApp } from './bootstrap';
// Create upload and data directories on startup
// Create upload and data directories on startup.
// Every uploads subdir the app writes to must be listed here (#1762): a dir
// created lazily on first upload needs write permission on the uploads mount
// point itself at request time, which fails with EACCES on Docker hosts whose
// bind-mounted uploads dir isn't writable by the runtime user — while every
// feature writing into an already-existing subdir keeps working. Creating them
// at boot turns that into one loud startup failure instead of a stray 500.
// Keep in sync with the `mkdir -p` list in the Dockerfile (guarded by
// tests/unit/uploads-dirs.test.ts).
const uploadsDir = path.join(__dirname, '../uploads');
const photosDir = path.join(uploadsDir, 'photos');
const filesDir = path.join(uploadsDir, 'files');
const coversDir = path.join(uploadsDir, 'covers');
const avatarsDir = path.join(uploadsDir, 'avatars');
const journeyDir = path.join(uploadsDir, 'journey');
const placesDir = path.join(uploadsDir, 'places');
const backupsDir = path.join(__dirname, '../data/backups');
const tmpDir = path.join(__dirname, '../data/tmp');
[uploadsDir, photosDir, filesDir, coversDir, avatarsDir, backupsDir, tmpDir].forEach(dir => {
[uploadsDir, photosDir, filesDir, coversDir, avatarsDir, journeyDir, placesDir, backupsDir, tmpDir].forEach(dir => {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
});