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
|
||||
`$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
|
||||
|
||||
WTFPL
|
||||
|
||||
@@ -20,6 +20,12 @@ regress.
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
@@ -222,7 +222,9 @@ func buildEnvSlice(env map[string]string) []string {
|
||||
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 {
|
||||
mounts := make([]mount.Mount, 0, len(volumes))
|
||||
|
||||
@@ -232,6 +234,9 @@ func buildMounts(volumes []VolumeMount) []mount.Mount {
|
||||
Source: vol.HostPath,
|
||||
Target: vol.ContainerPath,
|
||||
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