Search code examples
c#dockerasp.net-coredockerfile

Cannot write to volume in docker container


I'm trying to understand better how docker works and have an asp.net core Web API that runs in a docker container. When the API gets requests some of the data should be stored in .json files in order to use them again in the next request. This is done by looping through a list of objects, serializing them and then using a StreamWriter. Here the code snippet:

    foreach (MyType item in fulldata) { 
        JsonConvert.SerializeObject(item); 
        StreamWriter writer = new StreamWriter(@"demo_data/demo_data_store/" + item.Tag+".json",false);
        writer.AutoFlush = true;
        writer.Write(JsonConvert.SerializeObject(item));           
    }

As long as my docker-container is started through debugging in Visual Studio this all works fine and the files are created.

However as soon as I build the image from the docker file and then start the container I get an System.UnauthorizedAccessException: Access to the path '/app/demo_data/demo_data_store/myfile.json' is denied.

Even though I don't have a clue why there is no issue during debugging, I guess I have to use a volume in the image that is built. As far as I understood the simplest way to do this is to add a volume in the docker file, which now looks like this:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["WebAPI1/WebAPI1.csproj", "WebAPI1/"]
RUN dotnet restore "./WebAPI1/WebAPI1.csproj"
COPY . .
WORKDIR "/src/WebAPI1"
RUN dotnet build "./WebAPI1.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./WebAPI1.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebAPI1.dll"]

VOLUME /app/demo_data/demo_data_store

It's simply the original dockerfile created by VS asp.net core template + the one line added to create the volume. From what I see now, the folder of the volume is created in the image, however I still get the same error. Can anyone give me a hint how to set up a volume that my API can write to correctly? All the tutorials I found use docker compose but I would like to keep it simple and accomplish that directly from the dockerfile.

EDIT

ok, I tried now something different: I removed the "Volume..." line from the dockerfile. Then when starting my container from the image in Docker Desktop I simply selected the path that I want to store the data in on the host and the one that is used in my app in the Volumes section of the optional settings. Doing that I get the following section once I inspect the container:

    "Mounts": [
    {
        "Type": "bind",
        "Source": "C:\\the_local_directory_I_want_to_use\\_testdata",
        "Destination": "/app/demo_data/demo_data_store",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }

Now everything works like a charme - I'm aware it's now just a bind mount and not a volume. But can anyone enlighten me, how I can accomplish the same thing simply by changing my dockerfile? I thought RUN --mount=bind should do the job but that only causes an error during build because the Source directory can't be found. Or would it even be possible to achieve the same thing using a volume and adding this in the dockerfile?


Solution

  • If you are running your Docker without binding the volume it might be why you cannot access it.

    The VOLUME instruction does create a volume but if no binding is specified during run, Docker will address a random directory to it. You might want to add a -v to your docker run command for binding the volume inside your docker to a specific path to your host. For example:

    docker run -v /path/on/host:/app/demo_data/demo_data_store yourimagename
    

    The permission error you encounter might be related to the fact that you are trying to access the volume via the directory Docker created randomly according to your Dockerfile config

    EDIT: answer to @Hatzegopteryx comments.

    You don't have to use compose for what I propose, I think you are misunderstanding the difference between docker build, docker run, an image and a running container.

    First, I am curious to know what command do you use for starting your docker ?

    Now, an image is a collection of layers put together, with these layers being the result of the Dockerfile used for creating the image. You can think of an image as a car blueprint, it contains everything needed for putting the car on the road but it isn't operational by itself. You create an image with the docker build command.

    A running container, on the other hand, is the result of your image that is running while executing the piece of software it is supposed to. The same car from the blueprint, now driving on the road, would be the running container. You start a container with docker run.

    docker run -v /path/on/host:/app/demo_data/demo_data_store yourimagename
    

    Looking back at this command, you now understand that we are running a container from the image "yourimagename" which you previously built.

    Moreover, this running container has a volume. Docker already knew that since you specified it in your Dockerfile. But the main thing here is that you are BINDING the volume to a directory where your user has Read/Write permissions.

    This means that your host can access files from /app/demo_data/demo_data_store inside the container and vice versa

    Answer to your edit on your original question

    It seems that it was what I was suspected, you volume was binding to a directory where you didn't have the permissions.

    As I explained above,you cannot achieve what you want just using a Dockerfile and whtihout the -v option. Only Docker compose can directly bind a specific directory from the host to the container in its configuration file.

    You have to remember that your Dockerfile intruction are like a blueprint, they mean something if assembled together, this is why your volume has to be bind via the -v or --volume option