├── .editorconfig ├── LICENSE ├── Makefile ├── README.md ├── bases ├── cool-node │ ├── .dockerignore │ ├── Dockerfile │ └── mycoollibrary │ │ ├── .gitignore │ │ ├── package.json │ │ └── src │ │ ├── cool.js │ │ └── index.js ├── fancy-rust │ ├── .dockerignore │ ├── Dockerfile │ └── smart_rust_library │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs └── nice-python │ ├── .dockerignore │ ├── Dockerfile │ └── fancy_python_library │ ├── .gitignore │ ├── Pipfile │ ├── Pipfile.lock │ ├── fancy_python_library │ ├── __init__.py │ └── fancy.py │ └── setup.py └── services ├── my-node-service ├── .dockerignore ├── .gitignore ├── Dockerfile ├── package-lock.json ├── package.json └── src │ └── main.js ├── my-python-service ├── Dockerfile ├── Pipfile ├── Pipfile.lock └── src │ └── main.py └── my-rust-service ├── .dockerignore ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile └── src └── main.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [Makefile] 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 4 | Version 2, December 2004 5 | 6 | Copyright (C) 2018 Clodéric Mars 7 | 8 | Everyone is permitted to copy and distribute verbatim or modified 9 | copies of this license document, and changing it is allowed as long 10 | as the name is changed. 11 | 12 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 13 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 14 | 15 | 0. You just DO WHAT THE FUCK YOU WANT TO. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GIT_SHA1 = $(shell git rev-parse --verify HEAD) 2 | IMAGES_TAG = ${shell git describe --exact-match 2> /dev/null || echo 'latest'} 3 | 4 | IMAGE_DIRS = $(wildcard services/* bases/*) 5 | 6 | # All targets are `.PHONY` ie allways need to be rebuilt 7 | .PHONY: all ${IMAGE_DIRS} 8 | 9 | # Build all images 10 | all: ${IMAGE_DIRS} 11 | 12 | # Build and tag a single image 13 | ${IMAGE_DIRS}: 14 | $(eval IMAGE_NAME := $(word 2,$(subst /, ,$@))) 15 | docker build -t monorepo/${IMAGE_NAME}:${IMAGES_TAG} -t monorepo/${IMAGE_NAME}:latest --build-arg TAG=${IMAGES_TAG} --build-arg GIT_SHA1=${GIT_SHA1} $@ 16 | 17 | 18 | # Specify dependencies between images 19 | services/my-node-service: bases/cool-node 20 | services/my-rust-service: bases/fancy-rust 21 | services/my-python-service: bases/nice-python 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A monorepo architecture compatible with Docker # 2 | 3 | This repository showcases a method to manage a **monorepo** of several Dockerized services. It enables cleanly sharing code between them. 4 | 5 | My goals are: 6 | 7 | - Ability to work on both the microservices and the shared libraries locally without docker. 8 | - Ability to build the Docker images very simply and reliably while managing the dependencies. 9 | - Make it language agnostic. 10 | 11 | The goals are fulfilled for Node.js and Rust libraries and service atm. 12 | 13 | ## How to build and run the Docker images ## 14 | 15 | To build a single image, run the following. 16 | 17 | ```console 18 | $ make path_to/service 19 | ``` 20 | 21 | This builds the target image and its dependencies, incremental builds rely on docker layer caches. 22 | 23 | for example 24 | 25 | ```console 26 | $ make services/my-rust-service 27 | ``` 28 | 29 | To build all the images simply run 30 | 31 | ```console 32 | $ make all 33 | ``` 34 | 35 | To run the Node.js service 36 | 37 | ```console 38 | $ docker run monorepo/my-node-service 39 | ``` 40 | 41 | To run the Rust service 42 | 43 | ```console 44 | $ docker run monorepo/my-rust-service 45 | ``` 46 | 47 | in both cases any changes to the underlying libraries (in `bases`) will be taken into account properly in the next build and run. 48 | 49 | ## How to build and run the Node.js service without Docker ## 50 | 51 | ```console 52 | $ cd ./services/my-node-service/ 53 | $ npm install 54 | $ npm start 55 | ``` 56 | 57 | Thanks to the way `npm install` deals with local dependencies you don't even need to rerun it when a change is done in the shared library (e.g. in `/bases/cool-node/mycoollibrary/src/cool.js`). 58 | 59 | ## How to build and run the Rust service without Docker ## 60 | 61 | ```console 62 | $ cd ./services/my-rust-service/ 63 | $ cargo run 64 | ``` 65 | 66 | Thanks to the way `cargo run` works, changes to the shared library are detected, there's nothing additional to do. 67 | 68 | ## How to build and run the Python service without Docker ## 69 | 70 | ```console 71 | $ cd ./services/my-python-service/ 72 | $ pipenv install 73 | $ pipenv run start 74 | ``` 75 | 76 | Thanks to the way `pipenv install` works on local dependencies, you don't even need to rerun it when a change is done in the shared library (e.g. in `/bases/nice-python/fancy_python_library/fancy_python_library/fancy.py`). 77 | -------------------------------------------------------------------------------- /bases/cool-node/.dockerignore: -------------------------------------------------------------------------------- 1 | /*/node_modules 2 | -------------------------------------------------------------------------------- /bases/cool-node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8 2 | 3 | ARG TAG=latest 4 | ARG GIT_SHA1=latest 5 | 6 | LABEL maintainer="cloderic.mars@gmail.com" 7 | 8 | ENV MONOREPO_SHA1 ${GIT_SHA1} 9 | ENV MONOREPO_VERSION ${TAG} 10 | 11 | ENV ROOT_DIR /app 12 | 13 | WORKDIR ${ROOT_DIR}/bases/cool-node 14 | COPY . . 15 | -------------------------------------------------------------------------------- /bases/cool-node/mycoollibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /bases/cool-node/mycoollibrary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mycoollibrary", 3 | "version": "0.1.0", 4 | "author": "Clodéric Mars ", 5 | "license": "WTFPL", 6 | "main": "src/index.js" 7 | } 8 | -------------------------------------------------------------------------------- /bases/cool-node/mycoollibrary/src/cool.js: -------------------------------------------------------------------------------- 1 | function getCool() { 2 | return 'kinda cool'; 3 | } 4 | 5 | function getVersion() { 6 | return process.env.MONOREPO_VERSION || 'unknown'; 7 | } 8 | 9 | module.exports = { 10 | getCool, 11 | getVersion 12 | } 13 | -------------------------------------------------------------------------------- /bases/cool-node/mycoollibrary/src/index.js: -------------------------------------------------------------------------------- 1 | const { getCool, getVersion } = require('./cool'); 2 | 3 | module.exports = { 4 | getCool, 5 | getVersion 6 | } 7 | -------------------------------------------------------------------------------- /bases/fancy-rust/.dockerignore: -------------------------------------------------------------------------------- 1 | /*/target 2 | -------------------------------------------------------------------------------- /bases/fancy-rust/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.28 2 | 3 | ARG TAG=latest 4 | ARG GIT_SHA1=latest 5 | 6 | LABEL maintainer="cloderic.mars@gmail.com" 7 | 8 | ENV MONOREPO_SHA1 ${GIT_SHA1} 9 | ENV MONOREPO_VERSION ${TAG} 10 | 11 | ENV ROOT_DIR /app 12 | 13 | WORKDIR ${ROOT_DIR}/bases/fancy-rust 14 | COPY . . 15 | -------------------------------------------------------------------------------- /bases/fancy-rust/smart_rust_library/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /bases/fancy-rust/smart_rust_library/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "smart_rust_library" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /bases/fancy-rust/smart_rust_library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smart_rust_library" 3 | version = "0.1.0" 4 | authors = ["Clodéric Mars "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /bases/fancy-rust/smart_rust_library/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn get_the_answer() -> i32 { 2 | 42 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | #[test] 9 | fn it_works() { 10 | assert_eq!(get_the_answer(), 42); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /bases/nice-python/.dockerignore: -------------------------------------------------------------------------------- 1 | /fancy_python_library/fancy_python_library.egg-info 2 | -------------------------------------------------------------------------------- /bases/nice-python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | ARG TAG=latest 4 | ARG GIT_SHA1=latest 5 | 6 | LABEL maintainer="cloderic.mars@gmail.com" 7 | 8 | ENV MONOREPO_SHA1 ${GIT_SHA1} 9 | ENV MONOREPO_VERSION ${TAG} 10 | 11 | RUN pip install pipenv 12 | 13 | ENV ROOT_DIR /app 14 | 15 | WORKDIR ${ROOT_DIR}/bases/nice-python 16 | COPY . . 17 | -------------------------------------------------------------------------------- /bases/nice-python/fancy_python_library/.gitignore: -------------------------------------------------------------------------------- 1 | /fancy_python_library.egg-info 2 | -------------------------------------------------------------------------------- /bases/nice-python/fancy_python_library/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | "e1839a8" = {path = ".", editable = true} 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.6" 13 | -------------------------------------------------------------------------------- /bases/nice-python/fancy_python_library/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "a166821031f60007a08b6fec9ba3caa9dc601786a8eb3ed72bbd9342668d2b3d" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "e1839a8": { 20 | "editable": true, 21 | "path": "." 22 | } 23 | }, 24 | "develop": {} 25 | } 26 | -------------------------------------------------------------------------------- /bases/nice-python/fancy_python_library/fancy_python_library/__init__.py: -------------------------------------------------------------------------------- 1 | from .fancy import get_fancy 2 | 3 | __title__ = "fancy_python_library" 4 | 5 | __all__ = [ 6 | "get_fancy" 7 | ] 8 | -------------------------------------------------------------------------------- /bases/nice-python/fancy_python_library/fancy_python_library/fancy.py: -------------------------------------------------------------------------------- 1 | def get_fancy(): 2 | return "oh la la" 3 | -------------------------------------------------------------------------------- /bases/nice-python/fancy_python_library/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name="fancy_python_library", 5 | version="0.1.0", 6 | author="Clodéric Mars", 7 | author_email="cloderic.mars@gmail.com", 8 | description="", 9 | long_description="", 10 | long_description_content_type="text/markdown", 11 | url="https://github.com/cloderic/docker-monorepo", 12 | packages=setuptools.find_packages(), 13 | classifiers=( 14 | "Programming Language :: Python :: 3", 15 | "License :: WTFPL", 16 | "Operating System :: OS Independent", 17 | ), 18 | install_requires=[ 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /services/my-node-service/.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /services/my-node-service/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /services/my-node-service/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG TAG=latest 2 | FROM monorepo/cool-node:${TAG} 3 | 4 | LABEL maintainer="cloderic.mars@gmail.com" 5 | 6 | WORKDIR ${ROOT_DIR}/services/my-node-service 7 | 8 | COPY ./package.json ./ 9 | RUN npm install 10 | 11 | COPY . . 12 | CMD npm start 13 | -------------------------------------------------------------------------------- /services/my-node-service/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-node-service", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "mycoollibrary": { 8 | "version": "file:../../bases/cool-node/mycoollibrary" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /services/my-node-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-node-service", 3 | "version": "0.1.0", 4 | "author": "Clodéric Mars ", 5 | "license": "WTFPL", 6 | "scripts": { 7 | "start": "node ./src/main.js" 8 | }, 9 | "dependencies": { 10 | "mycoollibrary": "file:../../bases/cool-node/mycoollibrary" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /services/my-node-service/src/main.js: -------------------------------------------------------------------------------- 1 | const { getCool, getVersion } = require('mycoollibrary'); 2 | 3 | console.log(`This is ${getCool()}!`); 4 | console.log(`This is coming from version '${getVersion()}'!`); 5 | -------------------------------------------------------------------------------- /services/my-python-service/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG TAG=latest 2 | FROM monorepo/nice-python:${TAG} 3 | 4 | LABEL maintainer="cloderic.mars@gmail.com" 5 | 6 | WORKDIR ${ROOT_DIR}/services/my-python-service 7 | 8 | COPY Pipfile . 9 | COPY Pipfile.lock . 10 | RUN pipenv install --system 11 | 12 | COPY . . 13 | CMD python ./src/main.py 14 | -------------------------------------------------------------------------------- /services/my-python-service/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | "ab47816" = {path = "./../../bases/nice-python/fancy_python_library", editable = true} 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.6" 13 | 14 | [scripts] 15 | start = "python src/main.py" 16 | -------------------------------------------------------------------------------- /services/my-python-service/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "b450790c7b798f5075fc1528d963b6793fbec4aa822d8d8d61e13d834d0dc7ed" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "ab47816": { 20 | "editable": true, 21 | "path": "./../../bases/nice-python/fancy_python_library" 22 | } 23 | }, 24 | "develop": {} 25 | } 26 | -------------------------------------------------------------------------------- /services/my-python-service/src/main.py: -------------------------------------------------------------------------------- 1 | from fancy_python_library import get_fancy 2 | 3 | print('- This is so very fancy!') 4 | print('- {}!'.format(get_fancy())) 5 | -------------------------------------------------------------------------------- /services/my-rust-service/.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /services/my-rust-service/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /services/my-rust-service/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "my-rust-service" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "smart_rust_library 0.1.0", 6 | ] 7 | 8 | [[package]] 9 | name = "smart_rust_library" 10 | version = "0.1.0" 11 | 12 | -------------------------------------------------------------------------------- /services/my-rust-service/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "my-rust-service" 3 | version = "0.1.0" 4 | authors = ["Clodéric Mars "] 5 | 6 | [dependencies] 7 | smart_rust_library = { path = "../../bases/fancy-rust/smart_rust_library" } 8 | -------------------------------------------------------------------------------- /services/my-rust-service/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG TAG=latest 2 | FROM monorepo/fancy-rust:${TAG} 3 | 4 | LABEL maintainer="cloderic.mars@gmail.com" 5 | 6 | WORKDIR ${ROOT_DIR}/services/my-rust-service 7 | 8 | COPY . . 9 | RUN cargo build --release 10 | 11 | CMD target/release/my-rust-service 12 | -------------------------------------------------------------------------------- /services/my-rust-service/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate smart_rust_library; 2 | 3 | use smart_rust_library::get_the_answer; 4 | 5 | fn main() { 6 | println!("Hello, world!"); 7 | println!("The answer is {}!", get_the_answer()); 8 | } 9 | --------------------------------------------------------------------------------