diff --git a/.env.example b/.env.example
index 727168028..1deccd6f0 100644
--- a/.env.example
+++ b/.env.example
@@ -16,9 +16,10 @@ LOG_LEVEL='info'
# This is how you can use the sqlite driver:
DB_DRIVER='better-sqlite3'
-# Safe local source-development path. SQLite files are ignored by Git.
+# Set an absolute writable SQLite path so migrations and the app use the same file.
+# Their working directories differ; do not use a relative path here.
# Container images use /appdata/db/db.sqlite by default instead.
-DB_URL='./db.sqlite'
+DB_URL=
# Those are the two ways to use the mysql2 driver:
# 1. Using the URL format:
diff --git a/.github/workflows/deployment-beta-release.yml b/.github/workflows/deployment-beta-release.yml
index 293bac146..e150d8501 100644
--- a/.github/workflows/deployment-beta-release.yml
+++ b/.github/workflows/deployment-beta-release.yml
@@ -16,10 +16,10 @@ permissions:
jobs:
create-and-merge-pr:
runs-on: ubuntu-latest
- timeout-minutes: 2
+ timeout-minutes: 10
steps:
- name: Discord notification
- if: ${{ github.event.inputs.send-notifications }}
+ if: ${{ inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
@@ -37,11 +37,22 @@ jobs:
branch: next
- name: Create pull request
id: create-pull-request
- run: 'gh pr create --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" --body "Detected change to be ``${{ steps.semver.outputs.bump }}``
Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}-beta``" --base beta --head next --label automerge'
+ run: |
+ pr_url="$(gh pr list --base beta --head next --state open --json url --jq '.[0].url // empty')"
+ if [[ -z "$pr_url" ]]; then
+ pr_url="$(gh pr create \
+ --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" \
+ --body "Detected change to be ${{ steps.semver.outputs.bump }}
Bump version from ${{ steps.semver.outputs.current }} to ${{ steps.semver.outputs.next }}-beta" \
+ --base beta \
+ --head next \
+ --label automerge)"
+ fi
+ echo "url=$pr_url" >> "$GITHUB_OUTPUT"
+ echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Discord notification
- if: ${{ github.event.inputs.send-notifications }}
+ if: ${{ inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
@@ -58,7 +69,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
run: |
- gh pr review --approve --body "Automatically approved by GitHub Action"
+ gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
- name: Obtain token
id: obtainMergeToken
uses: actions/create-github-app-token@v3
@@ -82,21 +93,21 @@ jobs:
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
- name: Merged Discord notification
- if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
+ if: ${{ steps.automerge.outputs.mergeResult == 'merged' && inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
- name: Major Bump Discord notification
- if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
+ if: ${{ steps.semver.outputs.bump == 'major' && inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
- name: Discord Fail Notification
- if: failure() && github.event.inputs.send-notifications
+ if: ${{ failure() && inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
diff --git a/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx b/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx
index ea67538dc..be34bdb71 100644
--- a/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx
+++ b/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx
@@ -15,19 +15,13 @@ Many of you used the v2 demo when it was still rough, found bugs and came back t
We are also planning a small batch of **free, limited-edition Homarr swag** for Workshop contributors and a few randomly picked community members. It is not for sale and it is not for profit. No Homarr merch empire, sorry.
-Homarr 2.0 is an upgrade, not a brand-new app wearing the same logo. Here is what actually changed.
+Homarr 2.0 builds on the dashboard you already use. Here is what changed.

-:::warning Back up before upgrading
-
-Database migrations run automatically when Homarr starts. Create a
-[Homarr backup](/docs/management/backup) first. Do not test a prerelease image on your only production volume.
-Backups are boring. Restores are worse.
-
-:::
+Update your Homarr image as usual. Homarr runs database migrations automatically when the updated container starts; no manual database migration or separate v2 upgrade procedure is needed. Workshop uses the production service by default, with no preview URL configuration required.
## Custom Widgets v2 and Workshop
@@ -150,9 +144,8 @@ Groups use a new Viewer, Editor and Admin matrix. The API, Assistant and MCP fol
Board startup work runs in parallel, heavy screens load when opened, and identical integration requests share work instead of doing it twice.
-## Before upgrading
+## Compatibility notes
-- Keep a backup and use the final release image, not a prerelease tag.
- Legacy Custom JSX definitions enter a migration state. Homarr keeps encrypted credentials and an archived copy until the v2 definition validates.
- Mobile layouts place rail content in the main flow instead of displaying rails.
- Custom Widgets remain Beta.
@@ -160,8 +153,7 @@ Board startup work runs in parallel, heavy screens load when opened, and identic
## Links
-- Try the [Homarr 2.0 preview](https://app-v2.preview.homarr.dev).
-- Follow the [v2 beta setup guide](/docs/getting-started/upgrade-v2).
-- Follow the [v2 release pull request](https://github.com/homarr-labs/homarr/pull/6545) and [public beta thread](https://github.com/homarr-labs/homarr/issues/6600).
-- Compare [v1.76.2 with the release/v2 branch](https://github.com/homarr-labs/homarr/compare/v1.76.2...release/v2).
+- Try the [live demo](https://demo.homarr.dev/).
+- See [installation and update instructions](/docs/getting-started/installation/docker).
+- Browse [Workshop](https://homarr.dev/workshop).
- Read the guides for [Boards](/docs/management/boards), [Custom Widgets](/docs/management/custom-widgets), [Assistant](/docs/management/assistant), [MCP](/docs/management/mcp) and [Docker labels](/docs/integrations/docker-labels).
diff --git a/apps/docs/docs/advanced/command-line/development/index.mdx b/apps/docs/docs/advanced/command-line/development/index.mdx
index c2e7edb1b..47f5335a3 100644
--- a/apps/docs/docs/advanced/command-line/development/index.mdx
+++ b/apps/docs/docs/advanced/command-line/development/index.mdx
@@ -90,7 +90,7 @@ homarr run dev
homarr run --detach dev
homarr run --pr 6441
homarr run --pr 6441 --demo
-homarr --pr 6441 --env WORKSHOP_WEB_URL=https://app-v2.preview.homarr.dev/
+homarr --pr 6441 --env WORKSHOP_WEB_URL=https://workshop.example.com/workshop
```
- `--pr `: Pull and run a PR image from GHCR (shorthand: `homarr --pr `).
diff --git a/apps/docs/docs/advanced/development/getting-started.mdx b/apps/docs/docs/advanced/development/getting-started.mdx
index 0e69e83e0..a9c43dddb 100644
--- a/apps/docs/docs/advanced/development/getting-started.mdx
+++ b/apps/docs/docs/advanced/development/getting-started.mdx
@@ -31,9 +31,13 @@ pnpm install --frozen-lockfile
### Run the Homarr application
1. Copy `.env.example` to `.env`.
-2. Set `DB_URL` to an absolute writable SQLite file path.
-3. Keep Redis and the optional local databases running in one terminal.
-4. Apply migrations and start Next.js in another terminal.
+2. Run `openssl rand -hex 32` twice. Set `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` to the two different generated
+ values in `.env`; the example leaves both empty. Keep this file private and preserve `SECRET_ENCRYPTION_KEY`
+ when reusing a database.
+3. Set `DB_URL` to an absolute writable SQLite file path. Migration and application commands run from different
+ workspace directories, so a relative path can point them at different databases.
+4. Keep Redis and the optional local databases running in one terminal.
+5. Apply migrations and start Next.js in another terminal.
```sh
cp .env.example .env
@@ -57,6 +61,20 @@ Homarr becomes the instance administrator.
Use `pnpm docker:dev:up` instead when you only need Redis in the background. Running the migration also seeds the
default data; use `pnpm db:seed` to seed it explicitly later.
+`pnpm dev` starts only Next.js. When your work needs scheduled tasks or live subscriptions, run the relevant service
+in a separate terminal alongside it:
+
+```sh
+pnpm --filter @homarr/tasks dev
+```
+
+```sh
+pnpm --filter @homarr/websocket dev
+```
+
+The WebSocket service listens on port 3001. Production startup embeds both services, so these extra commands are
+only needed for development.
+
Workshop contributors can set up PocketBase and Docusaurus with the dedicated
[Workshop development guide](./workshop).
@@ -80,8 +98,8 @@ Workshop contributors can set up PocketBase and Docusaurus with the dedicated
- `pnpm typecheck` — typecheck all workspaces.
- `pnpm test` — unit and contract suites.
- `pnpm build` — production monorepo build.
-- `docker build -t homarr .` — build a production Docker image.
-- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:` — run it.
+- `docker build -t homarr:local .` — build a production Docker image.
+- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:local` — run it.
## CI and focused validation
diff --git a/apps/docs/docs/getting-started/installation/docker.mdx b/apps/docs/docs/getting-started/installation/docker.mdx
index e721c5933..d9371261b 100644
--- a/apps/docs/docs/getting-started/installation/docker.mdx
+++ b/apps/docs/docs/getting-started/installation/docker.mdx
@@ -24,6 +24,10 @@ see [Managing integrations](/docs/management/integrations#configuration).
### Update
+Update the image and recreate the container with the same configuration. Homarr runs database migrations automatically
+at startup, including when moving from v1 to v2; no separate upgrade procedure is needed. Keep the `/appdata` mount and
+`SECRET_ENCRYPTION_KEY` unchanged. Workshop connects to the production service by default.
+
```sh
docker compose pull
docker compose up -d
diff --git a/apps/docs/docs/getting-started/installation/source.mdx b/apps/docs/docs/getting-started/installation/source.mdx
index 3544fb1be..58d760608 100644
--- a/apps/docs/docs/getting-started/installation/source.mdx
+++ b/apps/docs/docs/getting-started/installation/source.mdx
@@ -21,8 +21,8 @@ This method is only useful, when you want to develop on Homarr or extend it with
## Prerequisites
-- [Node.js](https://nodejs.org/en/download/)
-- [pnpm](https://pnpm.io/installation)
+- [Node.js](https://nodejs.org/en/download/) 24.18 or newer
+- [pnpm](https://pnpm.io/installation) at the version pinned in the repository's `package.json`
- [Redis](https://redis.io/download)
## Steps
@@ -38,9 +38,9 @@ This method is only useful, when you want to develop on Homarr or extend it with
Install all dependencies using pnpm install
- Copy the `.env.example` file to `.env`. Its `DB_URL='./db.sqlite'` value is ready for local use and stores the
- database in the cloned repository. Replace it with an absolute writable path such as
- `DB_URL='/home/username/homarr/db.sqlite'` if you want to store the database elsewhere.
+ Copy `.env.example` to `.env` and set `DB_URL` to an absolute writable path, such as
+ `DB_URL='/home/username/homarr/db.sqlite'`. Migration and application commands run from different workspace
+ directories, so a relative path can point them at different databases.
Run `openssl rand -hex 32` twice and set `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` to the two different generated
diff --git a/apps/docs/docs/getting-started/upgrade-v2.mdx b/apps/docs/docs/getting-started/upgrade-v2.mdx
deleted file mode 100644
index 397ec4266..000000000
--- a/apps/docs/docs/getting-started/upgrade-v2.mdx
+++ /dev/null
@@ -1,88 +0,0 @@
----
-title: Try the v2 beta
-sidebar_position: 2
-tags:
- - Getting started
- - Upgrade
- - Migration
- - Backup
----
-
-This page covers the temporary Homarr v2 public beta. Try the [live demo](https://app-v2.preview.homarr.dev/)
-without installing anything, or run the beta image published in the
-[public beta announcement](https://github.com/homarr-labs/homarr/issues/6600):
-
-```sh
-docker pull ghcr.io/homarr-labs/homarr-test:v2
-```
-
-For a fresh beta installation, follow the [Docker installation guide](/docs/getting-started/installation/docker),
-use `ghcr.io/homarr-labs/homarr-test:v2` as the image, and use a separate `/appdata` volume.
-Set `WORKSHOP_API_URL=https://v2.preview.homarr.dev` to use the preview Workshop.
-
-Homarr runs database migrations automatically at startup; no manual database migration command is needed.
-Once v2 is released, existing installations can use their normal update process. The beta image below is temporary.
-
-:::danger Back up before starting v2
-
-In Homarr v1, go to **Management → Tools → Backup**, export a backup, and keep the downloaded file somewhere safe
-before starting v2. See [Backup and restore](/docs/management/backup) for details.
-
-:::
-
-## Try the beta with existing v1 data
-
-1. Change the Homarr image to `ghcr.io/homarr-labs/homarr-test:v2`.
-2. Keep the existing `/appdata` mount, database variables, Redis configuration, and `SECRET_ENCRYPTION_KEY` unchanged.
-3. Start one Homarr replica and follow its logs. Startup must reach `Migration complete`; Homarr aborts startup when a
- database migration fails.
-4. Open Homarr and sign in. Confirm **Management → About** reports v2 before starting additional replicas.
-5. Keep using the beta image for beta updates. To return to v1, restore the pre-beta backup as described below.
-
-Keep your other existing Compose settings, including environment variables. For Docker Compose, update the image and preview Workshop URL before the usual update:
-
-```yaml
-services:
- homarr:
- image: ghcr.io/homarr-labs/homarr-test:v2
- environment:
- WORKSHOP_API_URL: https://v2.preview.homarr.dev
-```
-
-```sh
-docker compose pull
-docker compose up -d
-docker compose logs --follow homarr
-```
-
-Do not add `-v` when stopping the Compose deployment; that can remove named volumes.
-
-## What v2 migrates
-
-The startup migration:
-
-- creates the Custom Widget v2, Assistant, header preference, and branding storage;
-- adds Base/Mobile layout roles and left/right board gutters;
-- converts legacy Categories and dynamic sections into Containers and rails;
-- preserves legacy Custom Widget definitions, encrypted secrets, and board references for explicit migration.
-
-Legacy Custom Widgets are not silently rewritten. In **Management → Custom Widgets**, entries marked
-**Migration required** provide a credential-free migration prompt. Review and test the resulting v2 definition before
-removing the archived v1 version. See [Custom Widgets: Import and Workshop](/docs/management/custom-widgets#import-and-workshop).
-
-## Verify the upgraded instance
-
-- Sign in with each enabled authentication method and verify administrator access.
-- Open every important board on desktop and mobile. Check Containers, former Categories, rails, item placement, and
- home-board assignments.
-- Test at least one read and one permitted action for each critical integration. Recheck integration and board access
- for representative Viewer, Editor, and Admin groups.
-- Check Docker or Podman endpoints, search engines, uploaded media, trusted certificates, scheduled tasks, WebSockets,
- and external Redis when used.
-- Review every legacy Custom Widget and migrate it only after its v2 preview and requests work.
-- Restart Homarr once more and confirm the logs contain no migration, database, Redis, authentication, or task errors.
-
-## Roll back
-
-Use the backup exported before the upgrade if you need to restore your v1 installation. Do not reuse a database that
-v2 has already migrated with v1.
diff --git a/apps/docs/docs/management/custom-widgets/index.mdx b/apps/docs/docs/management/custom-widgets/index.mdx
index 1e07f0611..06c604c8c 100644
--- a/apps/docs/docs/management/custom-widgets/index.mdx
+++ b/apps/docs/docs/management/custom-widgets/index.mdx
@@ -57,5 +57,5 @@ Legacy definitions show **Migration required**. The migration prompt excludes cr
describe the visible behavior separately, import the returned v2 definition, and verify it before removing the archived
version.
-Back up the database and `SECRET_ENCRYPTION_KEY` before upgrading or rolling back. Multi-replica installations must use
-the same Redis service so cached responses invalidate consistently.
+Keep `SECRET_ENCRYPTION_KEY` unchanged when updating Homarr so existing credentials remain readable. Multi-replica
+installations must use the same Redis service so cached responses invalidate consistently.
diff --git a/apps/docs/docs/management/users/index.mdx b/apps/docs/docs/management/users/index.mdx
index daba14154..86693456d 100644
--- a/apps/docs/docs/management/users/index.mdx
+++ b/apps/docs/docs/management/users/index.mdx
@@ -18,8 +18,9 @@ password. Decimal units are the default. Change them from the user form or **Com
Preferences → Data size units**. Byte rates follow the same preference; bit rates stay decimal and use labels such as
Mb/s. Deleting a user does not delete boards they created.
-In demo mode (`DEMO_MODE=true`), user deletion and password changes are disabled for everyone, including administrators,
-even when `DEMO_READ_ONLY=false` allows other changes. This applies to the UI and API.
+In demo mode (`DEMO_MODE=true`), user deletion, password changes, and local username changes are disabled for everyone,
+including administrators, even when `DEMO_READ_ONLY=false` allows other changes. With demo editing enabled, other
+profile preferences remain editable. This applies to the UI and API.

diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts
index 75b90c621..116b3f587 100644
--- a/packages/api/src/router/user.ts
+++ b/packages/api/src/router/user.ts
@@ -454,7 +454,7 @@ export const userRouter = createTRPCRouter({
}
const user = await ctx.db.query.users.findFirst({
- columns: { email: true, provider: true },
+ columns: { name: true, email: true, provider: true },
where: eq(users.id, input.id),
});
@@ -472,6 +472,13 @@ export const userRouter = createTRPCRouter({
});
}
+ if (isDemoMode && input.name !== user.name) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Username changes are disabled in demo mode",
+ });
+ }
+
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.name, input.id);
const emailDirty = input.email && user.email !== input.email;