Have Docker create a missing volume host path (closes #235) #252

Merged
clawbot merged 1 commits from issue-235-bind-create-mountpoint into next 2026-10-01 20:48:13 +02:00
4 changed files with 58 additions and 1 deletions
+7
View File
@@ -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
+6
View File
@@ -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
+6 -1
View File
@@ -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,
},
}) })
} }
+39
View File
@@ -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))
}