diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
deleted file mode 100644
index 670d11a06b..0000000000
--- a/.devcontainer/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:2ef23730ec68d8511ec8e6e0b82550ca728b256805d81f60ed890f3bfb21cfb9
-FROM ${BASEIMAGE}
-
-# Flutter SDK
-# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
-ENV FLUTTER_CHANNEL="stable"
-ENV FLUTTER_VERSION="3.29.1"
-ENV FLUTTER_HOME=/flutter
-ENV PATH=${PATH}:${FLUTTER_HOME}/bin
-
-# Flutter SDK
-RUN mkdir -p ${FLUTTER_HOME} \
-  && curl -C - --output flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/${FLUTTER_CHANNEL}/linux/flutter_linux_${FLUTTER_VERSION}-${FLUTTER_CHANNEL}.tar.xz \
-  && tar -xf flutter.tar.xz --strip-components=1 -C ${FLUTTER_HOME} \
-  && rm flutter.tar.xz \
-  && chown -R 1000:1000 ${FLUTTER_HOME}
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
deleted file mode 100644
index 2d567f033a..0000000000
--- a/.devcontainer/devcontainer.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "name": "Immich",
-  "service": "immich-devcontainer",
-  "dockerComposeFile": [
-    "docker-compose.yml",
-    "../docker/docker-compose.dev.yml"
-  ],
-  "customizations": {
-    "vscode": {
-      "extensions": [
-        "Dart-Code.dart-code",
-        "Dart-Code.flutter",
-        "dbaeumer.vscode-eslint",
-        "dcmdev.dcm-vscode-extension",
-        "esbenp.prettier-vscode",
-        "svelte.svelte-vscode"
-      ]
-    }
-  },
-  "forwardPorts": [],
-  "initializeCommand": "bash .devcontainer/scripts/initializeCommand.sh",
-  "onCreateCommand": "bash .devcontainer/scripts/onCreateCommand.sh",
-  "overrideCommand": true,
-  "workspaceFolder": "/immich",
-  "remoteUser": "node"
-}
diff --git a/.devcontainer/mobile/Dockerfile b/.devcontainer/mobile/Dockerfile
new file mode 100644
index 0000000000..7208ea8fdc
--- /dev/null
+++ b/.devcontainer/mobile/Dockerfile
@@ -0,0 +1,26 @@
+ARG BASEIMAGE=ghcr.io/immich-app/base-server-dev:202503182202@sha256:45ae044b64a7b518f8d94fa4de718090c1c7689d516ac2ac0976a5331eaeb396
+FROM ${BASEIMAGE} AS dev
+
+# Flutter SDK
+# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
+ENV FLUTTER_CHANNEL="stable"
+ENV FLUTTER_VERSION="3.29.1"
+ENV FLUTTER_HOME=/flutter
+ENV PATH=${PATH}:${FLUTTER_HOME}/bin
+
+# Flutter SDK
+RUN mkdir -p ${FLUTTER_HOME} \
+  && curl -C - --output flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/${FLUTTER_CHANNEL}/linux/flutter_linux_${FLUTTER_VERSION}-${FLUTTER_CHANNEL}.tar.xz \
+  && tar -xf flutter.tar.xz --strip-components=1 -C ${FLUTTER_HOME} \
+  && rm flutter.tar.xz \
+  && chown -R 1000:1000 ${FLUTTER_HOME}
+
+RUN apt-get update \
+  && wget -qO- https://dcm.dev/pgp-key.public | gpg --dearmor -o /usr/share/keyrings/dcm.gpg \
+  && echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | tee /etc/apt/sources.list.d/dart_stable.list \
+  && apt-get update \
+  && apt-get install dcm
+
+RUN apt-get install inetutils-ping sudo
+RUN usermod -aG sudo sudo node
+RUN dart --disable-analytics
\ No newline at end of file
diff --git a/.devcontainer/mobile/devcontainer.json b/.devcontainer/mobile/devcontainer.json
new file mode 100644
index 0000000000..2853fd4e78
--- /dev/null
+++ b/.devcontainer/mobile/devcontainer.json
@@ -0,0 +1,19 @@
+{
+  "name": "Immich - Mobile",
+  "service": "immich-mobile",
+  "dockerComposeFile": ["docker-compose.yml"],
+  "customizations": {
+    "vscode": {
+      "extensions": [
+        "Dart-Code.dart-code",
+        "Dart-Code.flutter",
+        "dcmdev.dcm-vscode-extension",
+        "esbenp.prettier-vscode"
+      ]
+    }
+  },
+  "forwardPorts": [],
+  "overrideCommand": true,
+  "workspaceFolder": "/immich",
+  "remoteUser": "node"
+}
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/mobile/docker-compose.yml
similarity index 52%
rename from .devcontainer/docker-compose.yml
rename to .devcontainer/mobile/docker-compose.yml
index 25719641d2..612406cade 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/mobile/docker-compose.yml
@@ -1,8 +1,9 @@
 services:
-  immich-devcontainer:
+  immich-mobile:
     build:
       dockerfile: Dockerfile
     extra_hosts:
       - 'host.docker.internal:host-gateway'
     volumes:
-      - ..:/immich:cached
+      - ../..:/immich
+      - open_api_node_modules:/immich/open-api/typescript-sdk/node_modules
diff --git a/.devcontainer/scripts/installAndRun.sh b/.devcontainer/scripts/installAndRun.sh
new file mode 100755
index 0000000000..c028cab9d4
--- /dev/null
+++ b/.devcontainer/scripts/installAndRun.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+export IMMICH_PORT="${DEV_SERVER_PORT:-2283}"
+export DEV_PORT="${DEV_PORT:-3000}"
+
+sudo chown node -R /immich/cli/node_modules /immich/e2e/node_modules /immich/open-api/typescript-sdk/node_modules /immich/server/node_modules /immich/web/node_modules /mnt/upload
+
+echo "Installing dependencies (server)"
+npm --prefix /immich/server install
+
+echo "Installing dependencies (web)"
+npm --prefix /immich/open-api/typescript-sdk install
+npm --prefix /immich/open-api/typescript-sdk run build
+npm --prefix /immich/web install
diff --git a/.devcontainer/server/.env b/.devcontainer/server/.env
new file mode 100644
index 0000000000..ac973c1275
--- /dev/null
+++ b/.devcontainer/server/.env
@@ -0,0 +1,19 @@
+# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
+
+# The location where your uploaded files are stored
+UPLOAD_LOCATION=upload-volume
+
+# The location where your database files are stored
+DB_DATA_LOCATION=db-volume
+
+# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
+# TZ=Etc/UTC
+
+# Connection secret for postgres. You should change it to a random password
+# Please use only the characters `A-Za-z0-9`, without special characters or spaces
+DB_PASSWORD=postgres
+
+# The values below this line do not need to be changed
+###################################################################################
+DB_USERNAME=postgres
+DB_DATABASE_NAME=immich
diff --git a/.devcontainer/server/Dockerfile b/.devcontainer/server/Dockerfile
new file mode 100644
index 0000000000..f3cc71a97a
--- /dev/null
+++ b/.devcontainer/server/Dockerfile
@@ -0,0 +1,8 @@
+ARG BASEIMAGE=ghcr.io/immich-app/base-server-dev:202503182202@sha256:45ae044b64a7b518f8d94fa4de718090c1c7689d516ac2ac0976a5331eaeb396
+FROM ${BASEIMAGE} AS dev
+
+RUN apt-get install inetutils-ping sudo
+RUN usermod -aG sudo node
+# RUN echo "node:pass" | chpasswd
+# RUN echo "root:root" | chpasswd
+RUN echo "node ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
diff --git a/.devcontainer/server/devcontainer.json b/.devcontainer/server/devcontainer.json
new file mode 100644
index 0000000000..1460170b46
--- /dev/null
+++ b/.devcontainer/server/devcontainer.json
@@ -0,0 +1,29 @@
+{
+  "name": "Immich - Backend and Frontend",
+  "service": "immich-server",
+  "dockerComposeFile": ["docker-compose.yml"],
+  "customizations": {
+    "vscode": {
+      "extensions": [
+        "dbaeumer.vscode-eslint",
+        "esbenp.prettier-vscode",
+        "svelte.svelte-vscode",
+        "ms-vscode-remote.remote-containers",
+        "foxundermoon.shell-format",
+        "timonwong.shellcheck",
+        "rvest.vs-code-prettier-eslint",
+        "bluebrown.yamlfmt",
+        "vkrishna04.cspell-sync"
+      ]
+    }
+  },
+  "features": {
+    "ghcr.io/devcontainers/features/nvidia-cuda:1": {},
+    "ghcr.io/meaningful-ooo/devcontainer-features/homebrew:2": {}
+  },
+  "forwardPorts": [3000],
+  "overrideCommand": true,
+  "workspaceFolder": "/immich",
+  "remoteUser": "node",
+  "postCreateCommand": "/immich/.devcontainer/scripts/installAndRun.sh"
+}
diff --git a/.devcontainer/server/docker-compose.yml b/.devcontainer/server/docker-compose.yml
new file mode 100644
index 0000000000..212a22fe7a
--- /dev/null
+++ b/.devcontainer/server/docker-compose.yml
@@ -0,0 +1,53 @@
+services:
+  immich-server:
+    build:
+      dockerfile: Dockerfile
+    hostname: immich
+    extra_hosts:
+      - 'host.docker.internal:host-gateway'
+    environment:
+      - IMMICH_SERVER_URL=http://127.0.0.1:2283/
+    volumes:
+      - ../..:/immich
+      - cli_node_modules:/immich/cli/node_modules
+      - e2e_node_modules:/immich/e2e/node_modules
+      - open_api_node_modules:/immich/open-api/typescript-sdk/node_modules
+      - server_node_modules:/immich/server/node_modules
+      - web_node_modules:/immich/web/node_modules
+      # - immich:/immich
+      - ${UPLOAD_LOCATION}:/immich/server/upload
+
+  redis:
+    image: redis:6.2-alpine@sha256:148bb5411c184abd288d9aaed139c98123eeb8824c5d3fce03cf721db58066d8
+    healthcheck:
+      test: redis-cli ping || exit 1
+
+  database:
+    image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
+    env_file:
+      - .env
+    environment:
+      POSTGRES_PASSWORD: ${DB_PASSWORD}
+      POSTGRES_USER: ${DB_USERNAME}
+      POSTGRES_DB: ${DB_DATABASE_NAME}
+      POSTGRES_INITDB_ARGS: '--data-checksums'
+    volumes:
+      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
+    ports:
+      - 5432:5432
+    healthcheck:
+      test: >-
+        pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
+      interval: 5m
+      start_interval: 30s
+      start_period: 5m
+    command: >-
+      postgres -c shared_preload_libraries=vectors.so -c 'search_path="$$user", public, vectors' -c logging_collector=on -c max_wal_size=2GB -c shared_buffers=512MB -c wal_compression=on
+
+volumes:
+  cli_node_modules:
+  e2e_node_modules:
+  open_api_node_modules:
+  server_node_modules:
+  web_node_modules:
+  immich:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 49dbf3944c..bac54e9a98 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -40,5 +40,7 @@
   "explorer.fileNesting.enabled": true,
   "explorer.fileNesting.patterns": {
     "*.ts": "${capture}.spec.ts,${capture}.mock.ts"
-  }
+  },
+  "debug.javascript.autoAttachFilter": "smart",
+  "eslint.workingDirectories": ["./server", "./web"]
 }
\ No newline at end of file
diff --git a/server/src/utils/file.ts b/server/src/utils/file.ts
index 716e0b1957..e8e6badae6 100644
--- a/server/src/utils/file.ts
+++ b/server/src/utils/file.ts
@@ -1,7 +1,7 @@
 import { HttpException, StreamableFile } from '@nestjs/common';
 import { NextFunction, Response } from 'express';
 import { access, constants } from 'node:fs/promises';
-import { basename, extname, isAbsolute } from 'node:path';
+import { basename, extname, isAbsolute, resolve } from 'node:path';
 import { promisify } from 'node:util';
 import { CacheControl } from 'src/enum';
 import { LoggingRepository } from 'src/repositories/logging.repository';
@@ -64,13 +64,13 @@ export const sendFile = async (
 
     // configure options for serving
     const options: SendFileOptions = { dotfiles: 'allow' };
-    if (!isAbsolute(file.path)) {
-      options.root = process.cwd();
+    let filePath = file.path;
+    if (!isAbsolute(filePath)) {
+      filePath = resolve(filePath);
     }
+    await access(filePath, constants.R_OK);
 
-    await access(file.path, constants.R_OK);
-
-    return await _sendFile(file.path, options);
+    return await _sendFile(filePath, options);
   } catch (error: Error | any) {
     // ignore client-closed connection
     if (isConnectionAborted(error) || res.headersSent) {