storage:report gains a flat dotted key per attachment field (entry-name, host-path, container-path, phases, process-type, subpath, readonly, volume-options, volume-chown) under the --storage-attachment.<index>.<field> shape. Both stdout and JSON pick the keys up through the existing common.ReportSingleApp pipeline. Attachments that reference a missing storage entry emit a warning and are skipped, so the rest of the report still renders. Closes #8710.
351 lines
17 KiB
Markdown
351 lines
17 KiB
Markdown
# Persistent Storage
|
|
|
|
> [!IMPORTANT]
|
|
> New as of 0.5.0. Named storage entries new as of 0.38.0.
|
|
|
|
The preferred method to attach persistent storage to a Dokku-managed container is the Dokku storage plugin.
|
|
|
|
```
|
|
storage:create <name> [<path>] [flags] # Register a named storage entry
|
|
storage:destroy <name> [--force] # Remove a named storage entry (must be unmounted from every app first)
|
|
storage:ensure-directory [--chown option] <directory> # [DEPRECATED] use storage:create instead
|
|
storage:exec <name> [-- <cmd>...] # Run a command (or shell) in a temporary container that mounts the entry
|
|
storage:info <name> [--format text|json] # Show details for one storage entry
|
|
storage:list <app> [--format text|json] # List bind mounts for an app's container(s) (legacy host:container view)
|
|
storage:list-entries [--scheduler s] [--format text|json] # List registered storage entries
|
|
storage:mount <app> <name> --container-dir <path> [flags] # Mount a named entry into an app
|
|
storage:mount <app> <host-dir:container-dir> # [LEGACY] colon-form mount, docker-local only
|
|
storage:report [<app>] [<flag>] # Display a storage report for one or more apps
|
|
storage:report --global # Display a cluster-wide entry inventory
|
|
storage:set <name> [flags] # Update a storage entry in place
|
|
storage:unmount <app> <name> [--container-dir <path>] # Remove an attachment
|
|
storage:wait <name> # Block until a k3s entry's PVC is bound
|
|
```
|
|
|
|
A storage entry is the source of truth for the underlying volume - a host directory on docker-local, or a PersistentVolumeClaim on k3s. Multiple apps can mount the same entry, and an attachment carries the per-app details (container path, phases, subpath, readonly, process type). Names are globally unique across the install and must be DNS-1123 labels (lowercase letters, digits, dashes) of 45 characters or less so they can be used verbatim as Helm release and PVC names.
|
|
|
|
The legacy `storage:mount <app> <host>:<container>` form continues to work on docker-local. On a k3s app it is rejected; create a named entry with `storage:create --scheduler k3s` and mount it instead. Existing colon-form mounts are migrated automatically the first time the new code runs - they show up as `legacy-<hash>` entries in `storage:list-entries`.
|
|
|
|
The storage plugin supports the following mount points:
|
|
|
|
- explicit paths that exist on the host (docker-local)
|
|
- docker volumes (docker-local)
|
|
- PersistentVolumeClaims provisioned via a StorageClass (k3s)
|
|
- hostPath-backed PVs (k3s)
|
|
|
|
## Usage
|
|
|
|
### Listing persistent storage
|
|
|
|
Persistent storage bind mounts are specified on a per-app basis, and can be listed with the `storage:list` command:
|
|
|
|
```shell
|
|
dokku storage:list node-js-app
|
|
```
|
|
|
|
```
|
|
-----> node-js-app volume bind-mounts:
|
|
/var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
```
|
|
|
|
The output format can also be set to `json` for programmatic access:
|
|
|
|
```shell
|
|
dokku storage:list node-js-app --format json
|
|
```
|
|
|
|
```
|
|
[
|
|
{
|
|
"entry_name": "node-js-app",
|
|
"host_path": "/var/lib/dokku/data/storage/node-js-app",
|
|
"container_path": "/app/storage"
|
|
}
|
|
]
|
|
```
|
|
|
|
Each entry mirrors the underlying attachment. `readonly` (boolean) and `volume_options` (string) reflect `Attachment.Readonly` and `Attachment.VolumeOptions` directly and are only present when set, so external tooling can drift-detect attachments against the raw attachment fields. For example, a mount created with `--volume-options noexec,nosuid --volume-readonly` renders as:
|
|
|
|
```
|
|
[
|
|
{
|
|
"entry_name": "node-js-data",
|
|
"host_path": "/var/lib/dokku/data/storage/node-js-data",
|
|
"container_path": "/app/storage",
|
|
"readonly": true,
|
|
"volume_options": "noexec,nosuid"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Creating storage directories
|
|
|
|
> [!IMPORTANT]
|
|
> New as of 0.25.5
|
|
|
|
A storage directory can be created with the `storage:ensure-directory` command. This command will create a subdirectory in the recommended `/var/lib/dokku/data/storage` path - created during Dokku installation - and prepare it for use with an app.
|
|
|
|
```shell
|
|
dokku storage:ensure-directory node-js-app
|
|
```
|
|
|
|
```
|
|
-----> Ensuring /var/lib/dokku/data/storage/node-js-app exists
|
|
Setting directory ownership to 32767:32767
|
|
Directory ready for mounting
|
|
```
|
|
|
|
By default, permissions are set for usage with Herokuish buildpacks. These permissions can be changed via the `--chown` option according to the following table:
|
|
|
|
- `--chown herokuish` (default): Use `32767:32767` as the folder permissions.
|
|
- This is used for apps deployed with Buildpacks via Herokuish.
|
|
- `--chown heroku`: Use `1000:1000` as the folder permissions.
|
|
- This is used for apps deployed with Cloud Native Buildpacks using the `heroku/builder` builder.
|
|
- `--chown paketo`: Use `2000:2000` as the folder permissions.
|
|
- This is used for apps deployed with Cloud Native Buildpacks using the `cloudfoundry/cnb` or `paketo` builders.
|
|
- `--chown root`: Use `0:0` as the folder permissions.
|
|
- This is used for containers that run their processes as root, as is typical for most Dockerfile or Docker image deploys.
|
|
- `--chown false`: Skips the `chown` call.
|
|
|
|
Users deploying via Dockerfile will want to specify `--chown false` and manually `chown` the created directory if the user and/or group id of the runnning process in the deployed container do not correspond to any of the above options.
|
|
|
|
The `--chown` flag - whether on `storage:create` or `storage:ensure-directory` - only manages the default `/var/lib/dokku/data/storage/<name>` location. If a custom `<path>` is passed to `storage:create`, the chown call is refused and the operator must chown the path themselves.
|
|
|
|
> [!WARNING]
|
|
> Failing to set the correct directory ownership may result in issues in persisting files written to the mounted storage directory.
|
|
|
|
### Mounting storage into apps
|
|
|
|
Dokku supports mounting both explicit host paths as well as docker volumes via the `storage:mount` command. This takes two arguments, an app name and a `host-path:container-path` or `docker-volume:container-path` combination.
|
|
|
|
```shell
|
|
# mount the directory into your container's /app/storage directory, relative to the container root (/)
|
|
# explicit host paths _must_ exist prior to usage.
|
|
dokku storage:mount node-js-app /var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
|
|
# mount the docker volume into your container's /app/storage directory, relative to the container root (/)
|
|
# docker volumes _must_ exist prior to usage.
|
|
dokku storage:mount node-js-app some-docker-volume:/app/storage
|
|
```
|
|
|
|
In the first example, Dokku will then mount the shared contents of `/var/lib/dokku/data/storage/node-js-app` to `/app/storage` inside the container. The mount point is *not* relative to your app's working directory, and is instead relative to the root (`/`) of the container. Mounts are only available for containers created via `run` and by the deploy process, and not during the build process. In addition, the host path is never auto-created by either Dokku or Docker, and should be an explicit path, not one relative to the current working directory.
|
|
|
|
> If the `/storage` path within the container had pre-existing content, the container files will be over-written. This may be an issue for users that create assets at build time but then mount a directory at the same place during runtime. Files are not merged.
|
|
|
|
For named storage entries, additional Docker mount options can be passed via `--volume-options`. The value is a comma-separated mount-options string stored verbatim on the attachment and rendered into the `-v` flag at deploy time. This is useful for SELinux labels (`Z`, `z`) or hardening flags (`noexec,nosuid`):
|
|
|
|
```shell
|
|
dokku storage:create node-js-data
|
|
dokku storage:mount node-js-app node-js-data --container-dir /app/storage --volume-options Z
|
|
```
|
|
|
|
When combined with `--volume-readonly`, the rendered options become `ro,<volume-options>` - for example, `--volume-options noexec,nosuid --volume-readonly` renders as `:ro,noexec,nosuid`.
|
|
|
|
Re-running `storage:mount` against a named entry with the same `--container-dir` and `--process-type` updates the existing attachment's mount-time attributes (`--phase`, `--volume-subpath`, `--volume-readonly`, `--volume-chown`, `--volume-options`) in place rather than appending a duplicate. This is the idempotent equivalent of `storage:set` for entries, and lets declarative tooling change a mount-time attribute without an unmount-then-remount dance that would briefly drop the volume from `storage:report`. Mount-time fields are rewritten wholesale, not merged - omitting a flag on a re-mount clears any previously-set value. The legacy `host:container[:opts]` form still rejects duplicates with `Mount path already exists.`.
|
|
|
|
Once persistent storage is mounted, the app requires a restart. See the [process scaling documentation](/docs/processes/process-management.md) for more information.
|
|
|
|
```shell
|
|
dokku ps:restart app-name
|
|
```
|
|
|
|
### Unmounting storage
|
|
|
|
If an app no longer requires a mounted volume or directory, the `storage:unmount` command can be called. This takes the same arguments as the `storage:mount` command, an app name and a `host-path:container-path` or `docker-volume:container-path` combination.
|
|
|
|
```shell
|
|
# unmount the directory from your container's /app/storage directory, relative to the container root (/)
|
|
dokku storage:unmount node-js-app /var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
|
|
# unmount the docker volume from your container's /app/storage directory, relative to the container root (/)
|
|
dokku storage:unmount node-js-app some-docker-volume:/app/storage
|
|
```
|
|
|
|
Once persistent storage is unmounted, the app requires a restart. See the [process scaling documentation](/docs/processes/process-management.md) for more information.
|
|
|
|
```shell
|
|
dokku ps:restart app-name
|
|
```
|
|
|
|
### Destroying storage entries
|
|
|
|
A named storage entry can be removed with the `storage:destroy` command. The entry must first be unmounted from every app that mounts it.
|
|
|
|
```shell
|
|
dokku storage:destroy rdmtest-entry
|
|
```
|
|
|
|
As the command is destructive - removing the registry entry and, depending on the scheduler and reclaim policy, the underlying volume - it will default to asking for confirmation before executing the removal.
|
|
|
|
```
|
|
! WARNING: Potentially Destructive Action
|
|
! This command will destroy storage entry rdmtest-entry.
|
|
! To proceed, type "rdmtest-entry"
|
|
> rdmtest-entry
|
|
-----> Storage entry rdmtest-entry destroyed
|
|
```
|
|
|
|
The confirmation may be avoided by providing the `--force` flag, which is useful for non-interactive or automated callers:
|
|
|
|
```shell
|
|
dokku storage:destroy rdmtest-entry --force
|
|
```
|
|
|
|
The global `--force` flag is also supported:
|
|
|
|
```shell
|
|
dokku --force storage:destroy rdmtest-entry
|
|
```
|
|
|
|
### Displaying storage reports for an app
|
|
|
|
> [!IMPORTANT]
|
|
> New as of 0.8.1
|
|
|
|
You can get a report about the app's storage status using the `storage:report` command:
|
|
|
|
```shell
|
|
dokku storage:report
|
|
```
|
|
|
|
```
|
|
=====> node-js-app storage information
|
|
Storage build mounts:
|
|
Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
Storage run mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
=====> python-sample storage information
|
|
Storage build mounts:
|
|
Storage deploy mounts:
|
|
Storage run mounts:
|
|
=====> ruby-sample storage information
|
|
Storage build mounts:
|
|
Storage deploy mounts:
|
|
Storage run mounts:
|
|
```
|
|
|
|
You can run the command for a specific app also.
|
|
|
|
```shell
|
|
dokku storage:report node-js-app
|
|
```
|
|
|
|
```
|
|
=====> node-js-app storage information
|
|
Storage build mounts:
|
|
Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
Storage run mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage
|
|
```
|
|
|
|
You can pass flags which will output only the value of the specific information you want. For example:
|
|
|
|
```shell
|
|
dokku storage:report node-js-app --storage-deploy-mounts
|
|
```
|
|
|
|
In addition to the aggregated `Storage build/deploy/run mounts:` lines, the report emits one flat dotted key per attachment field, indexed from `1`. The key shape is `--storage-attachment.<index>.<field>` for each of `entry-name`, `host-path`, `container-path`, `phases`, `process-type`, `subpath`, `readonly`, `volume-options`, and `volume-chown`. Fields render as empty strings when unset, and attachments are ordered by lex-sort of the index (so `10` sorts before `2`):
|
|
|
|
```shell
|
|
dokku storage:create node-js-data
|
|
dokku storage:mount node-js-app node-js-data --container-dir /app/storage --volume-options Z --volume-chown herokuish --volume-subpath uploads
|
|
dokku storage:report node-js-app
|
|
```
|
|
|
|
```
|
|
=====> node-js-app storage information
|
|
Storage attachment 1 container path: /app/storage
|
|
Storage attachment 1 entry name: node-js-data
|
|
Storage attachment 1 host path: /var/lib/dokku/data/storage/node-js-data
|
|
Storage attachment 1 phases: deploy,run
|
|
Storage attachment 1 process type: _default_
|
|
Storage attachment 1 readonly: false
|
|
Storage attachment 1 subpath: uploads
|
|
Storage attachment 1 volume chown: herokuish
|
|
Storage attachment 1 volume options: Z
|
|
Storage build mounts:
|
|
Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-data:/app/storage:Z
|
|
Storage run mounts: -v /var/lib/dokku/data/storage/node-js-data:/app/storage:Z
|
|
```
|
|
|
|
The same keys are exposed in JSON output, both in the stripped (`attachment.1.volume-options`) and legacy (`storage-attachment.1.volume-options`) forms:
|
|
|
|
```shell
|
|
dokku storage:report node-js-app --format json | jq '. | with_entries(select(.key | startswith("attachment.")))'
|
|
```
|
|
|
|
```json
|
|
{
|
|
"attachment.1.container-path": "/app/storage",
|
|
"attachment.1.entry-name": "node-js-data",
|
|
"attachment.1.host-path": "/var/lib/dokku/data/storage/node-js-data",
|
|
"attachment.1.phases": "deploy,run",
|
|
"attachment.1.process-type": "_default_",
|
|
"attachment.1.readonly": "false",
|
|
"attachment.1.subpath": "uploads",
|
|
"attachment.1.volume-chown": "herokuish",
|
|
"attachment.1.volume-options": "Z"
|
|
}
|
|
```
|
|
|
|
A single attachment field can be fetched directly via the info-flag form:
|
|
|
|
```shell
|
|
dokku storage:report node-js-app --storage-attachment.1.volume-options
|
|
```
|
|
|
|
```
|
|
Z
|
|
```
|
|
|
|
## Use Cases
|
|
|
|
### Sharing storage across deploys
|
|
|
|
Dokku is powered by Docker containers, which recommends in their [best practices](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#containers-should-be-ephemeral) that containers be treated as ephemeral. In order to manage persistent storage for web apps, like user uploads or large binary assets like images, a directory outside the container should be mounted.
|
|
|
|
### Shared storage between containers
|
|
|
|
When scaling your app, you may require a common location to access shared assets between containers, a storage mount can be used in this situation.
|
|
|
|
### Shared storage across environments
|
|
|
|
Your app may be used in a cluster that requires containers or resources not running on the same host access your data. Mounting a shared file service (like S3FS or EFS) inside your container will give you great flexibility.
|
|
|
|
### Backing up
|
|
|
|
Your app may have services that are running in memory and need to be backed up locally (like a key store). Mount a non ephemeral storage mount will allow backups that are not lost when the app is shut down.
|
|
|
|
### Build phase
|
|
|
|
By default, Dokku will only bind storage mounts during the deploy and run phases. Under certain conditions, one might want to bind a storage mount during the build phase. This can be accomplished by using the `docker-options` plugin directly.
|
|
|
|
```shell
|
|
dokku docker-options:add node-js-app build "-v /tmp/python-test:/opt/test"
|
|
```
|
|
|
|
You cannot use mounted volumes during the build phase of a Dockerfile deploy. This is because Docker does not support volumes when executing `docker build`.
|
|
|
|
> [!WARNING]
|
|
> **This can cause data loss** if you bind a mount under `/app` in buildpack apps as herokuish will attempt to remove the original app path during the build phase.
|
|
|
|
## App User and Persistent Storage file ownership (buildpack apps only)
|
|
|
|
> [!IMPORTANT]
|
|
> New as of 0.7.1
|
|
|
|
By default, Dokku will execute your buildpack app processes as the `herokuishuser` user. You may override this by setting the `DOKKU_APP_USER` config variable.
|
|
|
|
> [!NOTE]
|
|
> this user must exist in your herokuish image.
|
|
|
|
Additionally, the default `docker-local` scheduler that comes with Dokku will ensure your storage mounts are owned by either `herokuishuser` or the overridden value you have set in `DOKKU_APP_USER`. See the [docker-local scheduler documentation](/docs/deployment/schedulers/docker-local.md#disabling-chown-of-persistent-storage) docs for more information.
|
|
|
|
## Properties
|
|
|
|
### Internal properties
|
|
|
|
The following property is recorded internally by the storage plugin and is not exposed via `storage:report`:
|
|
|
|
| Property | Scope | Description | Source |
|
|
|---|---|---|---|
|
|
| `legacy-mounts-migrated` | per-app | Per-app marker recording that the app's legacy `-v` docker-options entries were drained into named storage entries plus attachments. Only set when at least one `-v` line was actually migrated; apps that have never had legacy mounts never receive this marker | `plugins/storage/migrate.go` writes `"true"` after a successful drain |
|