EDIT: Turns out this was a lot simpler than I was making it.
When you go to create a volume on ECS, it asks you for a name for the volume, and a "source path". When pressed for explanation it will specify that the source path is "The path on the host container instance that is presented to the container for this volume. If omitted, then the Docker daemon assigns a host path for you."
All very well and good, but it turns out that the difference is more than just "specifying a directory" vs "Docker picking a directory for you." This is the difference between a docker volume and a bind mount, and in fact if you docker inspect
the container you will see that volumes for which you give ECS a "source path" get "Type": "bind"
, whereas volumes that don't specify get "Type": "volume"
.
One key difference between bind mounts and volumes is that while bind mounts inherit their ownership from the host filesystem, volumes inherit their ownership from the container filesystem. So the incredibly, frustratingly simple solution to my problem is just to make sure the directory exists in the image with the proper ownership, then create the volume in ECS without specifying a source path.
Incidentally, if your application involves multiple containers sharing the same volume, the volume will derive its permissions from the existing directory structure on whichever container gets up and running first. So you need to make sure that either a) the directory exists on all containers where the volume will be mounted, or b) the container that does have the directory in question is always launched first.
I will leave my original solution below in case it's ever useful to anybody.
Original solution 1: tmpfs mounts
Docker volumes accept a driver_opts
parameter which works similarly to the mount
command on Linux systems. So one option is to use a tmpfs
mount, which allows for options that set the owner and group of the resulting files. On ECS, this can be accomplished thusly:
{
"name": "myvolume",
"dockerVolumeConfiguration": {
"scope": "task",
"driver": "local",
"driverOpts": {
"type": "tmpfs",
"device": "tmpfs",
"o": "uid=1000,gid=1000"
}
}
}
This will create a volume owned by user and group 1000 within the container.
The downside of this method is that, being tmpfs
, it stores files in the host memory. Depending on your use case, this may or may not be acceptable - for me it's not ideal, because I need to store log files which can grow quite large.
(Note that the type
and device
parameters under driverOpts
here are equivalent to the type
and device
parameters for the Linux mount
command. This took me quite some time and frustration to figure out.)
Original solution 2: Matching UID over NFS
NFS simply stores the owner/group of a file as a numeric id. The reason the group was showing up as xfs
for me was because as part of my redeployment, I'm moving from Ubuntu to Alpine. In both cases I want to use www-data
for the group, but www-data
is user/group 33 on Ubuntu and 82 on Alpine. On Alpine, 33 already exists as the "X font server" user, hence, xfs
.
I still don't have a perfect solution for a non-persistent, shared "scratch work" directory where I can dump logs while they wait to be sent up to Cloudwatch. I may simply end up using the tmpfs solution and then running logrotate with a very aggressive set of parameters, so that the log files never consume more than a few MB of memory.