# Upgrading

> Part of the NocoDB documentation (Self-hosting > Maintenance). Index of all pages: https://nocodb.com/llms.txt. Any docs page is available as Markdown by adding `.md` to its URL.

URL: https://nocodb.com/docs/self-hosting/maintenance/upgrading
Last updated: 2026-10-03

Upgrade a self-hosted NocoDB instance: pull the latest image and restart. NocoDB keeps your data and config.

To upgrade NocoDB, pull the latest Docker image and restart your stack. The upgrade keeps your data (in Postgres) and your config (in `nocodb/db.json` and `docker.env`).

## TL;DR

If you used the [Single-server install](/docs/self-hosting/installation/single-server):

```bash
cd nocodb
./update.sh
```

If you used the [Quickstart](/docs/self-hosting/installation/quickstart) or [Custom infrastructure](/docs/self-hosting/installation/custom-infrastructure) examples:

```bash
docker compose pull
docker compose up -d
```

Both procedures cause a short downtime while the containers restart.

## Before upgrading

* **Back up your Postgres database.** Refer to [Backups](/docs/self-hosting/maintenance/backups). Always back up before a major version upgrade.
* **Check the [Changelog](/docs/changelog)** for breaking changes since your current version.
* **On an older bind-mount install?** If your deployment keeps data in `./postgres` and `./nocodb`, not in Docker named volumes, do the [bind-mount to named-volume migration](#migrating-from-bind-mounts-to-named-volumes) first. If you do not, NocoDB starts with an empty database.

## Step-by-step

```bash
# Move into your deployment directory (where docker-compose.yml lives)
cd nocodb

# 1. Pull the latest images
docker compose pull

# 2. Restart with the new images (NocoDB rolls forward; data is preserved)
docker compose up -d

# 3. Optional: clean up unused image layers to free disk
docker image prune -f
```

The `./update.sh` script from the installation wizard does exactly these three steps.

## Migrating from bind mounts to named volumes

Older NocoDB deployments stored data in host directories next to `docker-compose.yml` (`./postgres`, `./redis`, `./nocodb`). Current deployments use Docker-managed **named volumes** (`postgres_data`, `redis_data`, `nocodb_data`). You can pull a newer compose file, or run the installer again, over an older bind-mount deployment. Docker then mounts **new, empty** volumes, and NocoDB starts with a new, empty database.

<Callout type="warn">
  Your data is **not** lost: it stays in the old `./postgres` and `./nocodb` directories. But you must copy it into the new volumes **before** the first `docker compose up -d` on the new compose file, or NocoDB will initialize an empty database.
</Callout>

You do this migration one time, and only for the database and attachments. Redis holds only cache and queue state, so a new, empty `redis_data` volume is fine.

<Callout type="info">
  **Using an external (managed) Postgres?** Your database lives outside Docker and is unaffected, so skip the Postgres copy below and migrate only the attachments.
</Callout>

### 1. Stop the old stack

```bash
cd nocodb   # your deployment directory
docker compose down
```

### 2. Switch to the new compose file, then create the empty volumes

1. Run the installer again, or replace `docker-compose.yml` with the named-volume version.
2. Create the volumes, but do not start the stack:

```bash
docker compose up --no-start
```

3. Find the new volume names. Compose adds your deployment directory name before them:

```bash
docker volume ls | grep -E '_(postgres|nocodb)_data'
# e.g. nocodb_postgres_data, nocodb_nocodb_data
```

### 3. Copy your data into the new volumes

A temporary `alpine` container copies each old host directory into its volume. If the new stack uses the same Postgres major version as your old stack, the raw data directory is compatible and copies correctly:

```bash
# Database (skip this if you use an external Postgres)
docker run --rm \
  -v "$(pwd)/postgres:/from:ro" \
  -v nocodb_postgres_data:/to \
  alpine sh -c "cp -a /from/. /to/"

# Attachments and app data
docker run --rm \
  -v "$(pwd)/nocodb:/from:ro" \
  -v nocodb_nocodb_data:/to \
  alpine sh -c "cp -a /from/. /to/"
```

If your directory is not named `nocodb`, replace `nocodb_postgres_data` and `nocodb_nocodb_data` with the names from step 2.

### 4. Start and verify

```bash
docker compose up -d
```

Sign in and make sure that all your bases, records and attachments are present. After this check, you can safely remove the old data directories:

```bash
rm -rf ./postgres ./redis
```

Keep `./nocodb/`. It still holds `db.json`, the database connection that the new compose file mounts into the container.

<Callout type="note">
  A raw data-directory copy only works within the same Postgres major version. If you are also moving to a new major version, `pg_dump` from the old stack and restore into the new one instead. See [Backups](/docs/self-hosting/maintenance/backups).
</Callout>

## Pinning to a specific version

Production deployments often pin a specific version, and do not track `latest`:

```yaml
# In docker-compose.yml
services:
  nocodb:
    image: nocodb/nocodb:2026.06.0
  worker:
    image: nocodb/nocodb:2026.06.0
```

To upgrade to a version, change the tag and run `docker compose up -d`.

[Docker Hub](https://hub.docker.com/r/nocodb/nocodb/tags) lists the available tags.

## Upgrading on Kubernetes

If you installed NocoDB with the Helm chart, refer to
[Kubernetes: Upgrade and backup](/docs/self-hosting/installation/kubernetes/upgrade) for
`helm upgrade`, rollback and backup instructions.

---

## Related pages

- [Backups](https://nocodb.com/docs/self-hosting/maintenance/backups.md): Back up a self-hosted NocoDB instance: the Postgres data, the attachments and the config files.
