diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore
deleted file mode 100644
index 6bf3b5d9e5..0000000000
--- a/.devcontainer/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.env
-library
\ No newline at end of file
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
deleted file mode 100644
index 4ce7076011..0000000000
--- a/.devcontainer/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:7c2e711a4f7b02f32d2da16192d5e05aa7c95279be4ce889cff5df316f251c1d
-FROM ${BASEIMAGE}
-
-# Flutter SDK
-# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
-ENV FLUTTER_CHANNEL="stable"
-ENV FLUTTER_VERSION="3.29.3"
-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
index 2d567f033a..4e4285f131 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,26 +1,67 @@
{
- "name": "Immich",
- "service": "immich-devcontainer",
+ "name": "Immich - Backend, Frontend and ML",
+ "service": "immich-server",
+ "runServices": [
+ "immich-server",
+ "redis",
+ "database",
+ "immich-machine-learning"
+ ],
"dockerComposeFile": [
- "docker-compose.yml",
- "../docker/docker-compose.dev.yml"
+ "../docker/docker-compose.dev.yml",
+ "./server/container-compose-overrides.yml"
],
"customizations": {
"vscode": {
"extensions": [
- "Dart-Code.dart-code",
- "Dart-Code.flutter",
"dbaeumer.vscode-eslint",
- "dcmdev.dcm-vscode-extension",
"esbenp.prettier-vscode",
- "svelte.svelte-vscode"
+ "svelte.svelte-vscode",
+ "ms-vscode-remote.remote-containers",
+ "foxundermoon.shell-format",
+ "timonwong.shellcheck",
+ "rvest.vs-code-prettier-eslint",
+ "bluebrown.yamlfmt",
+ "vkrishna04.cspell-sync",
+ "vitest.explorer",
+ "ms-playwright.playwright",
+ "ms-azuretools.vscode-docker"
]
}
},
- "forwardPorts": [],
- "initializeCommand": "bash .devcontainer/scripts/initializeCommand.sh",
- "onCreateCommand": "bash .devcontainer/scripts/onCreateCommand.sh",
+ "forwardPorts": [3000, 9231, 9230, 2283],
+ "portsAttributes": {
+ "3000": {
+ "label": "Immich - Frontend HTTP",
+ "description": "The frontend of the Immich project",
+ "onAutoForward": "openBrowserOnce"
+ },
+ "2283": {
+ "label": "Immich - API Server - HTTP",
+ "description": "The API server of the Immich project"
+ },
+ "9231": {
+ "label": "Immich - API Server - DEBUG",
+ "description": "The API server of the Immich project"
+ },
+ "9230": {
+ "label": "Immich - Workers - DEBUG",
+ "description": "The workers of the Immich project"
+ }
+ },
"overrideCommand": true,
- "workspaceFolder": "/immich",
- "remoteUser": "node"
+ "workspaceFolder": "/workspaces/immich",
+ "remoteUser": "node",
+ "userEnvProbe": "loginInteractiveShell",
+ "remoteEnv": {
+ // The location where your uploaded files are stored
+ "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}",
+ // 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": "${localEnv:DB_PASSWORD:postgres}",
+ // The database username
+ "DB_USERNAME": "${localEnv:DB_USERNAME:postgres}",
+ // The database name
+ "DB_DATABASE_NAME": "${localEnv:DB_DATABASE_NAME:immich}"
+ }
}
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
deleted file mode 100644
index 25719641d2..0000000000
--- a/.devcontainer/docker-compose.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-services:
- immich-devcontainer:
- build:
- dockerfile: Dockerfile
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- volumes:
- - ..:/immich:cached
diff --git a/.devcontainer/mobile/container-compose-overrides.yml b/.devcontainer/mobile/container-compose-overrides.yml
new file mode 100644
index 0000000000..62a97a01eb
--- /dev/null
+++ b/.devcontainer/mobile/container-compose-overrides.yml
@@ -0,0 +1,34 @@
+services:
+ immich-server:
+ build:
+ target: dev-container-mobile
+ environment:
+ - IMMICH_SERVER_URL=http://127.0.0.1:2283/
+ volumes: !override # bind mount host to /workspaces/immich
+ - ..:/workspaces/immich
+ - cli_node_modules:/workspaces/immich/cli/node_modules
+ - e2e_node_modules:/workspaces/immich/e2e/node_modules
+ - open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
+ - server_node_modules:/workspaces/immich/server/node_modules
+ - web_node_modules:/workspaces/immich/web/node_modules
+ - ${UPLOAD_LOCATION}/photos:/workspaces/immich/server/upload
+ - ${UPLOAD_LOCATION}/photos/upload:/workspaces/immich/server/upload/upload
+ - /etc/localtime:/etc/localtime:ro
+
+ database:
+ volumes:
+ - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
+
+volumes:
+ # Node modules for each service to avoid conflicts and ensure consistent dependencies
+ cli_node_modules:
+ e2e_node_modules:
+ open_api_node_modules:
+ server_node_modules:
+ web_node_modules:
+
+ # UPLOAD_LOCATION must be set to a absolute path or vol-upload
+ vol-upload:
+
+ # DB_DATA_LOCATION must be set to a absolute path or vol-database
+ vol-database:
diff --git a/.devcontainer/mobile/devcontainer.json b/.devcontainer/mobile/devcontainer.json
new file mode 100644
index 0000000000..0dbcc8e9c8
--- /dev/null
+++ b/.devcontainer/mobile/devcontainer.json
@@ -0,0 +1,52 @@
+{
+ "name": "Immich - Mobile",
+ "service": "immich-server",
+ "runServices": [
+ "immich-server",
+ "redis",
+ "database",
+ "immich-machine-learning"
+ ],
+ "dockerComposeFile": [
+ "../../docker/docker-compose.dev.yml",
+ "./container-compose-overrides.yml"
+ ],
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "Dart-Code.dart-code",
+ "Dart-Code.flutter",
+ "dcmdev.dcm-vscode-extension",
+ "esbenp.prettier-vscode",
+ "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",
+ "vitest.explorer",
+ "ms-playwright.playwright",
+ "ms-azuretools.vscode-docker"
+ ]
+ }
+ },
+ "forwardPorts": [],
+ "overrideCommand": true,
+ "workspaceFolder": "/workspaces/immich",
+ "remoteUser": "node",
+ "userEnvProbe": "loginInteractiveShell",
+ "remoteEnv": {
+ // The location where your uploaded files are stored
+ "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}",
+ // 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": "${localEnv:DB_PASSWORD:postgres}",
+ // The database username
+ "DB_USERNAME": "${localEnv:DB_USERNAME:postgres}",
+ // The database name
+ "DB_DATABASE_NAME": "${localEnv:DB_DATABASE_NAME:immich}"
+ }
+}
diff --git a/.devcontainer/scripts/initializeCommand.sh b/.devcontainer/scripts/initializeCommand.sh
deleted file mode 100644
index 9d9d196696..0000000000
--- a/.devcontainer/scripts/initializeCommand.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-# If .env file does not exist, create it by copying example.env from the docker folder
-if [ ! -f ".devcontainer/.env" ]; then
- cp docker/example.env .devcontainer/.env
-fi
diff --git a/.devcontainer/scripts/onCreateCommand.sh b/.devcontainer/scripts/onCreateCommand.sh
deleted file mode 100644
index 2f898ec32e..0000000000
--- a/.devcontainer/scripts/onCreateCommand.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-# Enable multiarch for arm64 if necessary
-if [ "$(dpkg --print-architecture)" = "arm64" ]; then
- sudo dpkg --add-architecture amd64 && \
- sudo apt-get update && \
- sudo apt-get install -y --no-install-recommends \
- qemu-user-static \
- libc6:amd64 \
- libstdc++6:amd64 \
- libgcc1:amd64
-fi
-
-# Install DCM
-wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg
-sudo echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list
-
-sudo apt-get update
-sudo apt-get install dcm
-
-dart --disable-analytics
-
-# Install immich
-cd /immich || exit
-make install-all
diff --git a/.devcontainer/server/container-common.sh b/.devcontainer/server/container-common.sh
new file mode 100755
index 0000000000..95f4e222a1
--- /dev/null
+++ b/.devcontainer/server/container-common.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+export IMMICH_PORT="${DEV_SERVER_PORT:-2283}"
+export DEV_PORT="${DEV_PORT:-3000}"
+
+# search for immich directory inside workspace.
+# /workspaces/immich is the bind mount, but other directories can be mounted if runing
+# Devcontainer: Clone [repository|pull request] in container volumne
+WORKSPACES_DIR="/workspaces"
+IMMICH_DIR="$WORKSPACES_DIR/immich"
+
+# Find directories excluding /workspaces/immich
+mapfile -t other_dirs < <(find "$WORKSPACES_DIR" -mindepth 1 -maxdepth 1 -type d ! -path "$IMMICH_DIR" ! -name ".*")
+
+if [ ${#other_dirs[@]} -gt 1 ]; then
+ echo "Error: More than one directory found in $WORKSPACES_DIR other than $IMMICH_DIR."
+ exit 1
+elif [ ${#other_dirs[@]} -eq 1 ]; then
+ export IMMICH_WORKSPACE="${other_dirs[0]}"
+else
+ export IMMICH_WORKSPACE="$IMMICH_DIR"
+fi
+
+echo "Found immich workspace in $IMMICH_WORKSPACE"
+
+run_cmd() {
+ echo "$@"
+ "$@"
+}
+
+fix_permissions() {
+
+ echo "Fixing permissions for ${IMMICH_WORKSPACE}"
+
+ run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} +
+
+ run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \
+ "${IMMICH_WORKSPACE}/cli/node_modules" \
+ "${IMMICH_WORKSPACE}/e2e/node_modules" \
+ "${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
+ "${IMMICH_WORKSPACE}/server/node_modules" \
+ "${IMMICH_WORKSPACE}/server/dist" \
+ "${IMMICH_WORKSPACE}/web/node_modules" \
+ "${IMMICH_WORKSPACE}/web/dist"
+}
+
+install_dependencies() {
+
+ echo "Installing dependencies"
+
+ (
+ cd "${IMMICH_WORKSPACE}" || exit 1
+ run_cmd make install-server
+ run_cmd make install-open-api
+ run_cmd make build-open-api
+ run_cmd make install-web
+ )
+}
\ No newline at end of file
diff --git a/.devcontainer/server/container-compose-overrides.yml b/.devcontainer/server/container-compose-overrides.yml
new file mode 100644
index 0000000000..94fbbab8cd
--- /dev/null
+++ b/.devcontainer/server/container-compose-overrides.yml
@@ -0,0 +1,44 @@
+services:
+ immich-server:
+ build:
+ target: dev-container-server
+ env_file: !reset []
+ environment:
+ - IMMICH_SERVER_URL=http://127.0.0.1:2283/
+ volumes: !override
+ - ..:/workspaces/immich
+ - cli_node_modules:/workspaces/immich/cli/node_modules
+ - e2e_node_modules:/workspaces/immich/e2e/node_modules
+ - open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
+ - server_node_modules:/workspaces/immich/server/node_modules
+ - web_node_modules:/workspaces/immich/web/node_modules
+ - ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload
+ - ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload
+ - /etc/localtime:/etc/localtime:ro
+
+ immich-web:
+ env_file: !reset []
+
+ immich-machine-learning:
+ env_file: !reset []
+
+ database:
+ env_file: !reset []
+ environment: !override
+ POSTGRES_PASSWORD: ${DB_PASSWORD-postgres}
+ POSTGRES_USER: ${DB_USERNAME-postgres}
+ POSTGRES_DB: ${DB_DATABASE_NAME-immich}
+ POSTGRES_INITDB_ARGS: '--data-checksums'
+ volumes:
+ - ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data
+
+ redis:
+ env_file: !reset []
+
+volumes:
+ # Node modules for each service to avoid conflicts and ensure consistent dependencies
+ cli_node_modules:
+ e2e_node_modules:
+ open_api_node_modules:
+ server_node_modules:
+ web_node_modules:
diff --git a/.devcontainer/server/container-start-backend.sh b/.devcontainer/server/container-start-backend.sh
new file mode 100755
index 0000000000..230a1378a2
--- /dev/null
+++ b/.devcontainer/server/container-start-backend.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+# shellcheck source=common.sh
+# shellcheck disable=SC1091
+source /immich-devcontainer/container-common.sh
+
+echo "Starting Nest API Server"
+
+cd "${IMMICH_WORKSPACE}/server" || (
+ echo workspace not found
+ exit 1
+)
+
+while true; do
+ node ./node_modules/.bin/nest start --debug "0.0.0.0:9230" --watch
+ echo " Nest API Server crashed with exit code $?. Respawning in 3s ..."
+ sleep 3
+done
diff --git a/.devcontainer/server/container-start-frontend.sh b/.devcontainer/server/container-start-frontend.sh
new file mode 100755
index 0000000000..43bde2a344
--- /dev/null
+++ b/.devcontainer/server/container-start-frontend.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# shellcheck source=common.sh
+# shellcheck disable=SC1091
+source /immich-devcontainer/container-common.sh
+
+echo "Starting Immich Web Frontend"
+
+cd "${IMMICH_WORKSPACE}/web" || (
+ echo Workspace not found
+ exit 1
+)
+
+until curl --output /dev/null --silent --head --fail "http://127.0.0.1:${IMMICH_PORT}/api/server/config"; do
+ echo 'waiting for api server...'
+ sleep 1
+done
+
+while true; do
+ node ./node_modules/.bin/vite dev --host 0.0.0.0 --port "${DEV_PORT}"
+ echo "Web crashed with exit code $?. Respawning in 3s ..."
+ sleep 3
+done
diff --git a/.devcontainer/server/container-start.sh b/.devcontainer/server/container-start.sh
new file mode 100755
index 0000000000..ef22db5d72
--- /dev/null
+++ b/.devcontainer/server/container-start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+# shellcheck source=common.sh
+# shellcheck disable=SC1091
+source /immich-devcontainer/container-common.sh
+
+fix_permissions
+install_dependencies
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000000..119d6a961b
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,72 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Fix Permissions, Install Dependencies",
+ "type": "shell",
+ "command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "dedicated",
+ "showReuseMessage": true,
+ "clear": false,
+ "group": "Devcontainer tasks",
+ "close": true
+ },
+ "runOptions": {
+ "runOn": "default"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Immich API Server (Nest)",
+ "dependsOn": ["Fix Permissions, Install Dependencies"],
+ "type": "shell",
+ "command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "dedicated",
+ "showReuseMessage": true,
+ "clear": false,
+ "group": "Devcontainer tasks",
+ "close": true
+ },
+ "runOptions": {
+ "runOn": "default"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Immich Web Server (Vite)",
+ "dependsOn": ["Fix Permissions, Install Dependencies"],
+ "type": "shell",
+ "command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "dedicated",
+ "showReuseMessage": true,
+ "clear": false,
+ "group": "Devcontainer tasks",
+ "close": true
+ },
+ "runOptions": {
+ "runOn": "default"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Immich Server and Web",
+ "dependsOn": ["Immich Web Server (Vite)", "Immich API Server (Nest)"],
+ "runOptions": {
+ "runOn": "folderOpen"
+ },
+ "problemMatcher": []
+ }
+ ]
+}
diff --git a/docs/docs/developer/devcontainers.md b/docs/docs/developer/devcontainers.md
new file mode 100644
index 0000000000..10cb733383
--- /dev/null
+++ b/docs/docs/developer/devcontainers.md
@@ -0,0 +1,481 @@
+---
+title: Devcontainers
+sidebar_position: 3
+---
+
+# Development with Dev Containers
+
+Dev Containers provide a consistent, reproducible development environment using Docker containers. With a single click, you can get started with an Immich development environment on Mac, Linux, Windows, or in the cloud using GitHub Codespaces.
+
+[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/immich-app/immich/)
+
+[](https://codespaces.new/immich-app/immich/)
+
+[Learn more about Dev Containers](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers)
+
+## Prerequisites
+
+Before getting started, ensure you have:
+
+- **Docker Desktop** (latest version)
+ - [Mac](https://docs.docker.com/desktop/install/mac-install/)
+ - [Windows](https://docs.docker.com/desktop/install/windows-install/) (with WSL2 backend recommended)
+ - [Linux](https://docs.docker.com/desktop/install/linux-install/)
+- **Visual Studio Code** with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
+- **Git** for cloning the repository
+- At least **8GB of RAM** (16GB recommended)
+- **20GB of free disk space**
+
+:::tip Alternative Development Environments
+While this guide focuses on VS Code, you have many options for Dev Container development:
+
+**Local Editors:**
+
+- [IntelliJ IDEA](https://www.jetbrains.com/help/idea/connect-to-devcontainer.html) - Full JetBrains IDE support
+- [neovim](https://github.com/jamestthompson3/nvim-remote-containers) - Lightweight terminal-based editor
+- [Emacs](https://github.com/emacs-lsp/lsp-docker) - Extensible text editor
+- [DevContainer CLI](https://github.com/devcontainers/cli) - Command-line interface
+
+**Cloud-Based Solutions:**
+
+- [GitHub Codespaces](https://github.com/features/codespaces) - Fully integrated with GitHub, excellent devcontainer.json support
+- [GitPod](https://www.gitpod.io) - SaaS platform with recent Dev Container support (historically used gitpod.yml)
+
+**Self-Hostable Options:**
+
+- [Coder](https://coder.com) - Enterprise-focused, requires Terraform knowledge, self-managed
+- [DevPod](https://devpod.sh) - Client-only tool with excellent devcontainer.json support, works with any provider (local, cloud, or on-premise)
+ :::
+
+## Dev Container Services
+
+The Dev Container environment consists of the following services:
+
+| Service | Container Name | Description | Ports |
+| ---------------- | ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------- |
+| Server & Web | `immich-server` | Runs both API server and web frontend in development mode | 2283 (API)
3000 (Web)
9230 (Workers Debug)
9231 (API Debug) |
+| Database | `database` | PostgreSQL database | 5432 |
+| Cache | `redis` | Valkey cache server | 6379 |
+| Machine Learning | `immich-machine-learning` | Immich ML model inference server | 3003 |
+
+## Getting Started
+
+### Step 1: Clone the Repository
+
+```bash
+git clone https://github.com/immich-app/immich.git
+cd immich
+```
+
+### Step 2: Configure Environment Variables
+
+The immich dev containers read environment variables from your shell environment, not from `.env` files. This allows them to work in cloud environments without pre-configuration.
+
+:::important Required Configuration
+When running locally, and if you want to create (or use an existing) DB and/or photo storage folder, you must set the `UPLOAD_LOCATION` variable in your shell environment before launching the Dev Container. This determines where uploaded files are stored and also where the DB stores it data.
+
+```bash
+# Set temporarily for current session
+export UPLOAD_LOCATION=/opt/dev_upload_folder
+
+# Or add to your shell profile for persistence
+# (~/.bashrc, ~/.zshrc, ~/.bash_profile, etc.)
+echo 'export UPLOAD_LOCATION=/opt/dev_upload_folder' >> ~/.bashrc
+source ~/.bashrc
+```
+
+:::
+
+### Step 3: Launch the Dev Container
+
+#### Using VS Code UI:
+
+1. Open the cloned repository in VS Code
+2. Press `F1` or `Ctrl/Cmd+Shift+P` to open the command palette
+3. Type and select "Dev Containers: Rebuild and Reopen in Container"
+4. Select "Immich - Backend, Frontend and ML" from the list
+5. Wait for the container to build and start (this may take several minutes on first run)
+
+#### Using VS Code Quick Actions:
+
+1. Open the repository in VS Code
+2. You should see a popup asking if you want to reopen in a container
+3. Click "Reopen in Container"
+
+#### Using Command Line:
+
+```bash
+# Using the DevContainer CLI
+devcontainer up --workspace-folder .
+```
+
+## Environment Variable Details
+
+### How Dev Containers Handle Environment Variables
+
+Unlike the Immich developer setup based on Docker Compose which uses `.env` files, Immich Dev Containers read environment variables from your shell environment. This is configured in `.devcontainer/devcontainer.json`:
+
+```json
+"remoteEnv": {
+ "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}",
+ "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
+ "DB_USERNAME": "${localEnv:DB_USERNAME:postgres}",
+ "DB_DATABASE_NAME": "${localEnv:DB_DATABASE_NAME:immich}"
+}
+```
+
+The `${localEnv:VARIABLE:default}` syntax reads from your shell environment with optional defaults.
+
+### Upload Location Path Resolution
+
+The `UPLOAD_LOCATION` environment variable controls where files are stored:
+
+**Default:** `./Library` (relative to the `docker` directory)
+**Resolved to:** `/docker/Library`
+
+**Bind Mounts Created:**
+
+```yaml
+# From .devcontainer/server/container-compose-overrides.yml
+- ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload
+- ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data
+```
+
+### Database Configuration
+
+These variables have sensible defaults (for development) but can be customized:
+
+| Variable | Default | Description |
+| ------------------ | ---------- | ------------------- |
+| `DB_PASSWORD` | `postgres` | PostgreSQL password |
+| `DB_USERNAME` | `postgres` | PostgreSQL username |
+| `DB_DATABASE_NAME` | `immich` | Database name |
+
+### Setting Environment Variables
+
+Add these to your shell profile (`~/.bashrc`, `~/.zshrc`, `~/.bash_profile`, etc.):
+
+```bash
+# Required
+export UPLOAD_LOCATION=./Library # or absolute path
+
+# Optional (only if using non-default values)
+export DB_PASSWORD=your_password
+export DB_USERNAME=your_username
+export DB_DATABASE_NAME=your_database
+```
+
+Remember to reload your shell configuration:
+
+```bash
+source ~/.bashrc # or ~/.zshrc, etc.
+```
+
+## Git Configuration
+
+### SSH Keys and Authentication
+
+To use your SSH keys for GitHub access inside the Dev Container:
+
+1. **Start SSH Agent** on your host machine:
+
+ ```bash
+ eval "$(ssh-agent -s)"
+ ssh-add ~/.ssh/id_rsa # or your key path
+ ```
+
+2. **VS Code automatically forwards your SSH agent** to the container
+
+For detailed instructions, see the [VS Code guide on sharing Git credentials](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials).
+
+### Commit Signing
+
+To use your SSH key for commit signing, see the [GitHub guide on SSH commit signing](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key#telling-git-about-your-ssh-key).
+
+## Development Workflow
+
+### Automatic Setup
+
+When the Dev Container starts, it automatically:
+
+1. **Runs post-create script** (`container-server-post-create.sh`):
+
+ - Adjusts file permissions for the `node` user
+ - Installs dependencies: `npm install` in all packages
+ - Builds TypeScript SDK: `npm run build` in `open-api/typescript-sdk`
+
+2. **Starts development servers** via VS Code tasks:
+
+ - `Immich API Server (Nest)` - API server with hot-reloading on port 2283
+ - `Immich Web Server (Vite)` - Web frontend with hot-reloading on port 3000
+ - Both servers watch for file changes and recompile automatically
+
+3. **Configures port forwarding**:
+ - Web UI: http://localhost:3000 (opens automatically)
+ - API: http://localhost:2283
+ - Debug ports: 9230 (workers), 9231 (API)
+
+:::info
+The Dev Container setup replaces the `make dev` command from the traditional setup. All services start automatically when you open the container.
+:::
+
+### Accessing Services
+
+Once running, you can access:
+
+| Service | URL | Description |
+| -------- | --------------------- | ---------------------------------------------------------------------------------------------- |
+| Web UI | http://localhost:3000 | Main web interface |
+| API | http://localhost:2283 | REST API endpoints (Not used directly, web UI will expose this over http://localhost:3000/api) |
+| Database | localhost:5432 | PostgreSQL (username: `postgres`) (Not used directly) |
+
+### Connecting Mobile Apps
+
+To connect the mobile app to your Dev Container:
+
+1. Find your machine's IP address
+2. In the mobile app, use: `http://YOUR_IP:3000/api`
+3. Ensure your firewall allows connections on port 2283
+
+### Making Code Changes
+
+- **Server code** (`/server`): Changes trigger automatic restart
+- **Web code** (`/web`): Changes trigger hot module replacement
+- **Database migrations**: Run `npm run sync:sql` in the server directory
+- **API changes**: Regenerate TypeScript SDK with `make open-api`
+
+## Testing
+
+### Running Tests
+
+The Dev Container supports multiple ways to run tests:
+
+#### Using Make Commands (Recommended)
+
+```bash
+# Run tests for specific components
+make test-server # Server unit tests
+make test-web # Web unit tests
+make test-e2e # End-to-end tests
+make test-cli # CLI tests
+
+# Run all tests
+make test-all # Runs tests for all components
+
+# Medium tests (integration tests)
+make test-medium-dev # End-to-end tests
+```
+
+#### Using NPM Directly
+
+```bash
+# Server tests
+cd /workspaces/immich/server
+npm test # Run all tests
+npm run test:watch # Watch mode
+npm run test:cov # Coverage report
+
+# Web tests
+cd /workspaces/immich/web
+npm test # Run all tests
+npm run test:watch # Watch mode
+
+# E2E tests
+cd /workspaces/immich/e2e
+npm run test # Run API tests
+npm run test:web # Run web UI tests
+```
+
+### Code Quality Commands
+
+```bash
+# Linting
+make lint-server # Lint server code
+make lint-web # Lint web code
+make lint-all # Lint all components
+
+# Formatting
+make format-server # Format server code
+make format-web # Format web code
+make format-all # Format all code
+
+# Type checking
+make check-server # Type check server
+make check-web # Type check web
+make check-all # Check all components
+
+# Complete hygiene check
+make hygiene-all # Runs lint, format, check, SQL sync, and audit
+```
+
+### Additional Make Commands
+
+```bash
+# Build commands
+make build-server # Build server
+make build-web # Build web app
+make build-all # Build everything
+
+# API generation
+make open-api # Generate OpenAPI specs
+make open-api-typescript # Generate TypeScript SDK
+make open-api-dart # Generate Dart SDK
+
+# Database
+make sql # Sync database schema
+
+# Dependencies
+make install-server # Install server dependencies
+make install-web # Install web dependencies
+make install-all # Install all dependencies
+```
+
+### Debugging
+
+The Dev Container is pre-configured for debugging:
+
+1. **API Server Debugging**:
+
+ - Set breakpoints in VS Code
+ - Press `F5` or use "Run and Debug" panel
+ - Select "Attach to Server" configuration
+ - Debug port: 9231
+
+2. **Worker Debugging**:
+
+ - Use "Attach to Workers" configuration
+ - Debug port: 9230
+
+3. **Web Debugging**:
+ - Use browser DevTools
+ - VS Code debugger for Chrome/Edge extensions supported
+
+## Troubleshooting
+
+### Common Issues
+
+#### Permission Errors
+
+**Problem**: `EACCES` or permission denied errors
+**Solution**:
+
+- The Dev Container runs as the `node` user (UID 1000)
+- If your host UID differs, you may see permission issues
+- Try rebuilding the container: "Dev Containers: Rebuild Container"
+
+#### Container Won't Start
+
+**Problem**: Dev Container fails to start or build
+**Solution**:
+
+1. Check Docker is running: `docker ps`
+2. Clean Docker resources: `docker system prune -a`
+3. Check available disk space
+4. Review Docker Desktop resource limits
+
+#### Port Already in Use
+
+**Problem**: "Port 3000/2283 is already in use"
+**Solution**:
+
+1. Check for conflicting services: `lsof -i :3000` (macOS/Linux)
+2. Stop conflicting services or change port mappings
+3. Restart Docker Desktop
+
+#### Upload Location Not Set
+
+**Problem**: Errors about missing UPLOAD_LOCATION
+**Solution**:
+
+1. Set the environment variable: `export UPLOAD_LOCATION=./Library`
+2. Add to your shell profile for persistence
+3. Restart your terminal and VS Code
+
+#### Database Connection Failed
+
+**Problem**: Cannot connect to PostgreSQL
+**Solution**:
+
+1. Ensure all containers are running: `docker ps`
+2. Check logs: "Dev Containers: Show Container Log"
+3. Verify database credentials match environment variables
+
+### Getting Help
+
+If you encounter issues:
+
+1. Check container logs: View → Output → Select "Dev Containers"
+2. Rebuild without cache: "Dev Containers: Rebuild Container Without Cache"
+3. Review [common Docker issues](https://docs.docker.com/desktop/troubleshoot/)
+4. Ask in [Discord](https://discord.immich.app) `#help-desk-support` channel
+
+## Mobile Development
+
+While the Dev Container focuses on server and web development, you can connect mobile apps for testing:
+
+### Connecting iOS/Android Apps
+
+1. **Ensure API is accessible**:
+
+ ```bash
+ # Find your machine's IP
+ # macOS
+ ipconfig getifaddr en0
+ # Linux
+ hostname -I
+ # Windows (in WSL2)
+ ip addr show eth0
+ ```
+
+2. **Configure mobile app**:
+
+ - Server URL: `http://YOUR_IP:2283/api`
+ - Ensure firewall allows port 2283
+
+3. **For full mobile development**, see the [mobile development guide](/docs/developer/setup) which covers:
+ - Flutter setup
+ - Running on simulators/devices
+ - Mobile-specific debugging
+
+## Advanced Configuration
+
+### Custom VS Code Extensions
+
+Add extensions to `.devcontainer/devcontainer.json`:
+
+```json
+"customizations": {
+ "vscode": {
+ "extensions": [
+ "your.extension-id"
+ ]
+ }
+}
+```
+
+### Additional Services
+
+To add services (e.g., Redis Commander), modify:
+
+1. `/docker/docker-compose.dev.yml` - Add service definition
+2. `/.devcontainer/server/container-compose-overrides.yml` - Add overrides if needed
+
+### Resource Limits
+
+Adjust Docker Desktop resources:
+
+- **macOS/Windows**: Docker Desktop → Settings → Resources
+- **Linux**: Modify Docker daemon configuration
+
+Recommended minimums:
+
+- CPU: 4 cores
+- Memory: 8GB
+- Disk: 20GB
+
+## Next Steps
+
+- Read the [architecture overview](/docs/developer/architecture)
+- Learn about [database migrations](/docs/developer/database-migrations)
+- Explore [API documentation](/docs/api)
+- Join `#immich` on [Discord](https://discord.immich.app)
diff --git a/server/Dockerfile b/server/Dockerfile
index fcf16ea139..21c2031227 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -16,6 +16,58 @@ ENV PATH="${PATH}:/usr/src/app/bin" \
NVIDIA_VISIBLE_DEVICES=all
ENTRYPOINT ["tini", "--", "/bin/sh"]
+FROM dev AS dev-container-server
+
+RUN apt-get update && \
+ apt-get install sudo inetutils-ping openjdk-11-jre-headless \
+ vim nano \
+ -y --no-install-recommends --fix-missing
+
+RUN usermod -aG sudo node
+RUN echo "node ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+RUN mkdir -p /workspaces/immich
+RUN chown node -R /workspaces
+
+RUN mkdir /immich-devcontainer && chown node -R /immich-devcontainer
+COPY --chmod=777 ../.devcontainer/server/*.sh /immich-devcontainer/
+
+FROM dev-container-server AS dev-container-mobile
+
+# Enable multiarch for arm64 if necessary
+RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
+ dpkg --add-architecture amd64 && \
+ apt-get update && \
+ apt-get install -y --no-install-recommends \
+ qemu-user-static \
+ libc6:amd64 \
+ libstdc++6:amd64 \
+ libgcc1:amd64; \
+ fi
+
+# Flutter SDK
+# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
+ENV FLUTTER_CHANNEL="stable"
+ENV FLUTTER_VERSION="3.29.3"
+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 node ${FLUTTER_HOME}
+
+USER node
+RUN sudo apt-get update \
+ && wget -qO- https://dcm.dev/pgp-key.public | sudo 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' | sudo tee /etc/apt/sources.list.d/dart_stable.list \
+ && sudo apt-get update \
+ && sudo apt-get install dcm -y
+
+COPY --chmod=777 ../.devcontainer/mobile/container-mobile-post-create.sh /immich-devcontainer/container-mobile-post-create.sh
+
+RUN dart --disable-analytics
FROM dev AS prod