Have Docker create a missing volume host path (closes #235) #252
@@ -271,6 +271,13 @@ names Dockerfile frontend 1.3 or later, such as `docker/dockerfile:1`.
|
|||||||
Session secrets are automatically generated on first startup and persisted to
|
Session secrets are automatically generated on first startup and persisted to
|
||||||
`$UPAAS_DATA_DIR/session.key`.
|
`$UPAAS_DATA_DIR/session.key`.
|
||||||
|
|
||||||
|
### Volume mounts
|
||||||
|
|
||||||
|
An app's volume mounts are bind mounts of host paths. When a host path does not
|
||||||
|
exist yet, upaas has Docker create it as an empty directory, owned by root, when
|
||||||
|
the app's container starts; there is no need to create it first. An existing
|
||||||
|
host path is left as it is. This needs Docker Engine 23.0 or later.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
WTFPL
|
WTFPL
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ regress.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-01: An app's first deploy no longer fails when a volume's host path
|
||||||
|
does not exist yet: upaas asks Docker to create a missing host path when the
|
||||||
|
app's container starts and to leave an existing one alone, so nobody has to
|
||||||
|
create it on the host first. The README has a Volume mounts section saying so
|
||||||
|
(#235).
|
||||||
|
|
||||||
- 2026-09-29: The app page is 50% wider on large screens (84rem instead of
|
- 2026-09-29: The app page is 50% wider on large screens (84rem instead of
|
||||||
56rem), its build log and container log boxes are twice as tall, the build log
|
56rem), its build log and container log boxes are twice as tall, the build log
|
||||||
sits between the webhook URL and the environment variables, and the container
|
sits between the webhook URL and the environment variables, and the container
|
||||||
|
|||||||
@@ -222,7 +222,9 @@ func buildEnvSlice(env map[string]string) []string {
|
|||||||
return envSlice
|
return envSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildMounts converts volume mounts to Docker mount configuration.
|
// buildMounts converts volume mounts to Docker mount configuration. Docker,
|
||||||
|
// not upaas, creates a missing host path, because upaas runs in a container
|
||||||
|
// and cannot see the host's filesystem. An existing path is left alone.
|
||||||
func buildMounts(volumes []VolumeMount) []mount.Mount {
|
func buildMounts(volumes []VolumeMount) []mount.Mount {
|
||||||
mounts := make([]mount.Mount, 0, len(volumes))
|
mounts := make([]mount.Mount, 0, len(volumes))
|
||||||
|
|
||||||
@@ -232,6 +234,9 @@ func buildMounts(volumes []VolumeMount) []mount.Mount {
|
|||||||
Source: vol.HostPath,
|
Source: vol.HostPath,
|
||||||
Target: vol.ContainerPath,
|
Target: vol.ContainerPath,
|
||||||
ReadOnly: vol.ReadOnly,
|
ReadOnly: vol.ReadOnly,
|
||||||
|
BindOptions: &mount.BindOptions{
|
||||||
|
CreateMountpoint: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package docker //nolint:testpackage // tests unexported buildMounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/mount"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBuildMountsCreatesMissingHostPath checks that every mount asks Docker
|
||||||
|
// to create its host path if it is missing, and keeps the rest of the volume
|
||||||
|
// as configured.
|
||||||
|
func TestBuildMountsCreatesMissingHostPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
volumes := []VolumeMount{
|
||||||
|
{HostPath: "/srv/app/data", ContainerPath: "/data", ReadOnly: false},
|
||||||
|
{HostPath: "/srv/app/config", ContainerPath: "/etc/app", ReadOnly: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []mount.Mount{
|
||||||
|
{
|
||||||
|
Type: mount.TypeBind,
|
||||||
|
Source: "/srv/app/data",
|
||||||
|
Target: "/data",
|
||||||
|
ReadOnly: false,
|
||||||
|
BindOptions: &mount.BindOptions{CreateMountpoint: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: mount.TypeBind,
|
||||||
|
Source: "/srv/app/config",
|
||||||
|
Target: "/etc/app",
|
||||||
|
ReadOnly: true,
|
||||||
|
BindOptions: &mount.BindOptions{CreateMountpoint: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, want, buildMounts(volumes))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user