The idea is to have a single entry point to start and stop all your services, without having to cd into each directory.
Let's organize our project like this:
masterImages/
├── Makefile
├── postgres/
│ ├── docker-compose.yml
│ ├── init-scripts/
│ │ └── 01-init-schema.sql
│ └── Makefile
└── kafka/
├── docker-compose.yml
└── Makefile
The masterImages directory will contain our master Makefile and subdirectories for each service.
First, create the masterImages directory and the main Makefile within it.
mkdir masterImages
cd masterImages
touch Makefile
Now, add the following content to the masterImages/Makefile:
.PHONY: start stop
start:
@$(MAKE) -C $(filter-out $@,$(MAKECMDGOALS)) start
stop:
@$(MAKE) -C $(filter-out $@,$(MAKECMDGOALS)) stop
%:
@:
This master Makefile is the heart of our setup. It redirects make commands to the Makefile inside the respective service directory. For instance, make postgres start will execute the start target in the postgres/Makefile.
Now, let's set up the postgres service.
mkdir -p postgres/init-scripts
cd postgres
touch Makefile docker-compose.yml init-scripts/01-init-schema.sql
The postgres/Makefile is simple:
.PHONY: start stop
start:
docker compose up
stop:
docker compose down
Here is the content for postgres/docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:17
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
POSTGRES_SCHEMA: digitalassets
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
volumes:
postgres_data:
And the initialization script postgres/init-scripts/01-init-schema.sql:
-- Create the schema
CREATE SCHEMA IF NOT EXISTS mySchema;
-- Grant all permissions to postgres user
GRANT ALL PRIVILEGES ON SCHEMA mySchema TO postgres;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA mySchema TO postgres;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA mySchema TO postgres;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA mySchema TO postgres;
-- Set default search path for postgres user
ALTER USER postgres SET search_path TO mySchema,public;
Similarly, for the kafka service:
mkdir kafka
cd kafka
touch Makefile docker-compose.yml
The kafka/Makefile is identical to the Postgres one:
.PHONY: start stop
start:
docker compose up
stop:
docker compose down
And the kafka/docker-compose.yml:
version: '2.1'
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.7.0
container_name: zookeeper
ports:
- 2181:2181
environment:
ZOOKeeper_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
kafka-1:
image: confluentinc/cp-kafka:7.7.0
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-1:19092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
KAFKA_LOG4J_ROOT_LOGLEVEL: ERROR
KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: "false"
KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
depends_on:
- zookeeper
kafka-2:
image: confluentinc/cp-kafka:7.7.0
ports:
- "9093:9093"
environment:
KAFKA_BROKER_ID: 2
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-2:19093,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9093
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
KAFKA_LOG4J_ROOT_LOGLEVEL: ERROR
KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: "false"
depends_on:
- zookeeper
kafka-3:
image: confluentinc/cp-kafka:7.7.0
ports:
- "9094:9094"
environment:
KAFKA_BROKER_ID: 3
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-3:19094,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9094
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
KAFKA_LOG4J_ROOT_LOGLEVEL: ERROR
KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: "false"
depends_on:
- zookeeper
Now, from the masterImages directory, you can manage your services like this:
To start the Postgres service:
make postgres start
To stop the Postgres service:
make postgres stop
To start the Kafka cluster:
make kafka start
And to stop it:
make kafka stop
This approach keeps your projects organized and makes them easy to manage from a single place.