How to configure Docker to have a shared folder between two containers?
Posted on: 2022-05-09
In this article, I explain how to configure Docker to have two independent services in two different containers, but both of them reference a shared folder.
repository
|----> service1
|----> service2
|----> shared
The article focus on:
- Docker configuration
- TypeScript configuration
The challenge with sharing a folder when using a different container is to bring the shared folder into the respective containers and have TypeScript working at design time (inside your developer editor) and at compilation. The solution revolves around the path
capability of TypeScript, allowing to create aliases. With aliases, you can import modules from an absolute point instead of a relative path. The relativity issue is to refer to a folder that is outside the container realm. Hence, to refer to a folder outside the Docker's context, we need to use TypeScript path
.
TypeScript Configuration
The first step is to go into each service tsconfig.json and add the path. In my case, both services are at the same level: sibling to the shared folder. Thus, they have inside their tsconfig.json the same new configuration:
"baseUrl": "./",
"paths": {
"@shared/*": ["../shared/*"]
}
There is a caveat in the configuration of paths,
and it is that you must have a baseUrl
. In both services, the base is the root of the folder. So, what the configuration means is that any time we are using import {xyz} from "@shared/folder/file"
that the import will refer to the file from the baseUrl
and then apply the @shared
.
import {xyz} from "@shared/folder/file" ---> import {xyz} from "./..shared/folder/file"
Absolute Reference
ViteJS
Once the TypeScript configuration is in place, the next hurdle is to have the code that executes to know what to do with the paths
. The generated Javascript remains with the @shared
prefix. While TypeScript compilers (build time and design time) can interpret the paths
, the browser or Node does not.
One of the services was using ViteJS. There is a plugin for ViteJS that can be used. In my case, it didn't work. So, I had to resolve the path manually. In the vite.config.js
, you need to add a resolve
. For simplicity, I removed the rest of the configuration so you will have more than the required configuration for the paths
. As you can see, the paths
in ViteJS is named alias
. The configuration is very similar to TypeScript.
export default (conf: any) => {
return defineConfig({
resolve: {
alias: [{ find: "@shared", replacement: path.resolve(__dirname, "../shared") }],
},
});
};
If you are using Webpack instead of ViteJS, something similar would be needed.
NodeJS
The other service was a NodeJS Express server. A similar configuration was needed. This time, the configuration is with Node. Here are the two commands I had to modify. One modifies the nodemon
, the other one node
. In the end, the same idea: rely on a code modifier that will hand the path.
"start:development": "npx tsc -v && npx tsc && concurrently \"npx tsc -w --preserveWatchOutput\" \"npx nodemon --inspect=0.0.0.0:9229 --watch ./build -r ts-node/register/transpile-only -r tsconfig-paths/register ./build/app/src/index.js\"",
"start:production": "node -r ts-node/register/transpile-only -r tsconfig-paths/register build/node/src/index.js"
The fix was to add -r tsconfig-paths/register
which will apply the transformation.
Docker
Finally, the shared folder must be included in both Docker containers. Because in development, we want to be able to quickly modify, add or remove code, we want to rely on Docker's volume. However, when we deploy for production, we must copy the shared folder into the container as a sibling to keep the reference intact.
The configuration of the docker-compose.yml
maps the local development path ./services/shared
to the docker folder /node/shared
, which is a sibling to the actual code in /node/app
volumes:
- "./services/shared:/node/shared:delegated"
The configuration within each DockerFile
. Next to the command that copied the service1
, a new entry for shared
is added.
COPY services/service1 /node
COPY services/shared /shared
Conclusion
Sharing interfaces, classes, types, constants, or logics between a frontend and a backend service is natural. Leveraging the possibility to reuse code because both projects are using the same languages is a huge time saver. Even if Docker divides the two services, it is possible to keep sharing by allowing during development a common volume. Copying the shared folder into each container at production time is the key. Once the configuration of TypeScript and your builder, like ViteJS, the future will be smooth and enjoyable and will foster a great habit of code sharing because the hurdle to execute is reduced to none. You can see an example of the pattern described in this article in my Real Time Pixel repository.