Hello dev.to community!
I've created a project called wayofdev/docker-shared-services that my team and I use to streamline our local development.
It simplifies the setup for Dockerized projects on macOS and Linux, and Iβm excited to share it with the community. Let's dive into how it can help you and your team.
ποΈ Table of Contents
- Key Features
- Problem
- The Solution: Docker Shared Services
- Requirements
- Quick Start Guide (macOS)
- Connecting Your Projects to Shared Services
- Example: Spin-up Laravel Sail Project
- Example: Want to See a Ready-Made Template?
- Linux
- Conclusion
Key Features π
By implementing this approach, you will have:
-
Automated Local DNS and SSL Setup: No more manual edits to
/etc/hosts
or dealing with self-signed certificate warnings. You'll have an automated solution for DNS and SSL. - Consistent Development Environment: All team members will have the same setup, reducing environment-related bugs and making onboarding new developers faster and easier.
- Elimination of Port Conflicts: Using Traefik, you can avoid port conflicts entirely, allowing multiple dockerized projects to run concurrently without issues.
-
User-Friendly Local URLs: Access your projects via custom local domains like
project.docker
instead oflocalhost:8000
, improving the overall development experience. - Simplified CORS and Cookie Management: With SSL in place for local domains, configuring CORS and Cookies will mirror production settings, reducing debugging time.
- Enhanced Testing Environment: Test OAuth, Secure Cookies, and HTTPS APIs locally, ensuring they work exactly as they will in production.
- Improved Service Discovery and Routing: Traefik provides automatic service discovery and routing, making it easier to manage and access various services within your Docker network.
-
Ease of Integration with Existing Projects: Quickly connect your existing Docker projects to the
docker-shared-services
setup, leveraging DNSMasq and Traefik for better service management.
Problem π€
β Common Challenges in Local Development
Manual DNS Configuration:
Developers often need to update their/etc/hosts
file to direct traffic for local domains likeyourproject.local
oryourproject.domain.local
to127.0.0.1
. This is tedious and requires admin access.Lack of SSL Support:
Local domains often lack SSL, making it hard to test secure connections and leading to issues with OAuth providers, CORS, and cookies.Port Conflicts:
Forwarding Docker service ports to the host machine can cause conflicts, especially when multiple services use common ports like 80 or 443.Cumbersome Hostnames:
Using hostnames likelocalhost:8000
for local projects is not user-friendly and complicates development.Complex CORS and Cookie Configuration:
Without SSL and proper DNS setup, configuring CORS and Cookies becomes more challenging.Consistency Across Team Members:
When a team is working on the same project, maintaining consistency in the local development environment across different machines is challenging. Differences in setup can lead to issues that are hard to debug and resolve, slowing down the development process.
β The Hard Way
Traditionally, setting up a local development environment with SSL involves a series of manual steps:
- Generating and trusting self-signed certificates in the system
- Editing the
/etc/hosts
file - Setting up and configuring Dockerized projects with custom ports
- Solving CORS and SSL related problems between local services
These steps are time-consuming and prone to errors, leading to a frustrating development experience.
The Solution: Docker Shared Services π³
The wayofdev/docker-shared-services simplifies local development by providing a Docker-powered environment that integrates Traefik, mkcert, and DNSMasq. This setup offers:
- Automated DNS and SSL support
- A consistent development environment across different machines
- Simplified service discovery and routing using Traefik
- Elimination of port conflicts on the host network
- The ability to test your Dockerized projects in an environment that closely mimics real-world scenarios, ensuring that your applications behave as expected before deployment
β Key Components
Traefik:
Traefik acts as a reverse proxy and load balancer, providing routing for your Docker services. It integrates with Docker via docker-compose labeling functionality to automatically discover and route traffic to your services.mkcert:
mkcert generates locally-trusted development certificates, enabling SSL support for your local domains.DNSMasq:
Dnsmasq provides lightweight DNS and DHCP services, allowing you to use custom local domains without editing the/etc/hosts
file.
β Going Further
You can go further and pack this project into an Ansible setup. This way, you can automate the provisioning of macOS or Linux hosts, ensuring that all necessary tools and configurations are in place. This not only saves time but also ensures consistency across different environments.
Requirements π©
-
macOS Monterey+ or Linux
- Tested on Ubuntu 22.04, but should also work on Debian or Arch variants
- Docker 26.0 or newer
- Homebrew (macOS only): Install via brew.sh
- Installed mkcert binary in system
- See full installation instructions in their official README.md
- Quick installation on macOS:
brew install mkcert nss
Quick Start Guide (macOS) π
-
Install Homebrew (if not installed):
If Homebrew is not already installed, run the following command:/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
Install Docker (if not installed):
Set up Docker Desktop via Homebrew:brew install --cask docker
-
Install
mkcert
andnss
:
mkcert
is a tool that creates locally-trusted development certificates, andnss
provides support for mkcert certificates in Firefox.brew install mkcert nss
-
Create shared project directory:
This repository should be run once per machine, so let's create a shared directory for this project:mkdir -p ~/projects/infra && cd ~/projects/infra
-
Clone this repository:
git clone \ git@github.com:wayofdev/docker-shared-services.git \ ~/projects/infra/docker-shared-services && \ cd ~/projects/infra/docker-shared-services
-
Create
.env
file:
Generate a default.env
file, which contains configuration settings for the project.make env
Open this file and read the notes inside to make any necessary changes to fit your setup.
-
Install root certificate and generate default project certs:
This step installs the root certificate into your system's trust store and generates default SSL certificates for your local domains, which are listed in the.env
file, under the variableTLS_DOMAINS
.make cert-install
Note:
Currently, on macOS, you may need to enter your password several times to allow
mkcert
to install the root certificate.
This is a one-time operation and details can be found in this upstream GitHub issue. -
Run this project:
Start the Docker services defined in the repository.make up
-
Check that all Docker services are running:
Ensure Docker is running and services are up by using themake ps
andmake logs
commands.make ps make logs
-
Add custom DNS resolver to your system:
This allows macOS to understand that*.docker
domains should be resolved by a custom resolver via127.0.0.1
, where our DNSMasq, which runs inside Docker, will handle all DNS requests.sudo sh -c 'echo "nameserver 127.0.0.1" > /etc/resolver/docker' sudo dscacheutil -flushcache sudo killall -HUP mDNSResponder
You can check that DNS was added by running:
scutil --dns
Example output:
resolver #8 domain : docker nameserver[0] : 127.0.0.1 flags : Request A records, Request AAAA records reach : 0x00030002 (Reachable,Local Address,Directly Reachable Address)
Note:
Instead of creating the
/etc/resolver/docker
file, you can add127.0.0.1
to your macOS DNS Servers in your Ethernet or Wi-Fi settings.Go to System Preferences β Network β Wi-Fi β Details β DNS and add
127.0.0.1
as the first DNS server.This allows you to do it one time, and if you need to create a new local domain, for example
*.mac
, in the future, it will be automatically resolved without creating a separate/etc/resolver/mac
file. -
Ping
router.docker
to check if DNS is working:
Ensure that the DNS setup is functioning correctly.ping router.docker -c 3 ping any-domain.docker -c 3
-
Access Traefik dashboard:
Open https://router.docker.You should see the Traefik Dashboard:
β Outcome
At this point, you should have a working local development environment with DNS and SSL support ready to be used with your projects.
Services will be running under a shared Docker network called network.ss
, and all projects or microservices that will share the same Docker network will be visible to Traefik. The local DNS, served by DNSMasq, will be available on *.docker
domains.
Connecting Your Projects to Shared Services π
To connect your projects to the shared services, configure your project's docker-compose.yaml
file to connect to the shared network and Traefik.
This project comes with an example Portainer service, which also starts by default with the make up
command. You can check the docker-compose.yaml
to see how Traefik labels and the shared network are used to spin up Portainer on the https://ui.docker host, which supports SSL by default.
β Sample Configuration
Your project should use the shared Docker network network.ss
and Traefik labels to expose services to the outside world.
-
Change your project's
docker-compose.yaml
file:--- services: web: image: wayofdev/nginx:k8s-alpine-latest restart: on-failure + networks: + - default + - shared volumes: - ./app:/app:rw,cached + labels: + - traefik.enable=true + - traefik.http.routers.api-my-project-secure.rule=Host(`api.my-project.docker`) + - traefik.http.routers.api-my-project-secure.entrypoints=websecure + - traefik.http.routers.api-my-project-secure.tls=true + - traefik.http.services.api-my-project-secure.loadbalancer.server.port=8880 + - traefik.docker.network=network.ss networks: + shared: + external: true + name: network.ss + default: + name: project.my-project
In this configuration, we added the shared network and Traefik labels to the web service. These labels help Traefik route the traffic to the service based on the specified rules.
-
Generate SSL certs for your project:
Go to thedocker-shared-services
directory:cd ~/projects/infra/docker-shared-services
Edit the
.env
file to add your custom domain:nano .env
It should look something like this:
TLS_DOMAINS="ui.docker router.docker *.my-project.docker"
Generate SSL certificates:
make cert-install restart
Example: Spin-up Laravel Sail Project π
Let's walk through an example of setting up a Laravel project using Sail and integrating it with the docker-shared-services
.
-
Create an example Laravel project based on Sail:
curl -s "https://laravel.build/example-app" | bash
-
Open the
docker-compose.yaml
file of theexample-app
project and make adjustments:services: laravel.test: build: context: ./vendor/laravel/sail/runtimes/8.3 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' image: sail-8.3/app - extra_hosts: - - 'host.docker.internal:host-gateway' ports: - - '${APP_PORT:-80}:80' - - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' networks: - sail + - shared depends_on: - ... + labels: + - traefik.enable=true + - traefik.http.routers.test-laravel-app-secure.rule=Host(`api.laravel-app.docker`) + - traefik.http.routers.test-laravel-app-secure.entrypoints=websecure + - traefik.http.routers.test-laravel-app-secure.tls=true + - traefik.http.services.test-laravel-app-secure.loadbalancer.server.port=80 + - traefik.docker.network=network.ss mailpit: image: 'axllent/mailpit:latest' networks: - sail + - shared ports: - - '${FORWARD_MAILPIT_PORT:-1025}:1025' - - '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025' + labels: + - traefik.enable=true + - traefik.http.routers.mail-laravel-app-secure.rule=Host(`mail.laravel-app.docker`) + - traefik.http.routers.mail-laravel-app-secure.entrypoints=websecure + - traefik.http.routers.mail-laravel-app-secure.tls=true + - traefik.http.services.mail-laravel-app-secure.loadbalancer.server.port=8025 + - traefik.docker.network=network.ss networks: sail: driver: bridge + shared: + external: true + name: network.ss
These changes connect the Laravel app and Mailpit docker services to the shared network and expose them via Traefik.
-
Run the Laravel project:
Navigate to theexample-app
directory and start the services using Sail../vendor/bin/sail up -d
-
Check Traefik routers:
Open https://router.docker/dashboard/#/http/routers and check that there are two routers:- Host(api.laravel-app.docker) β test-laravel-app-secure@docker
- Host(mail.laravel-app.docker)` β mail-laravel-app-secure@docker
Check the setup:
Ensure that your Laravel application and Mailpit services are running correctly by accessing their respective domains:
* **Laravel app** β https://api.laravel-app.docker
* **Mailpit** β https://mail.laravel-app.docker
At this point, your Laravel project is integrated with the wayofdev/docker-shared-services
, utilizing DNS and SSL support for local development.
Example: Want to See a Ready-Made Template? π
If you come from the PHP or Laravel world, or if you want to see how a complete project can be integrated with docker-shared-services
, check out wayofdev/laravel-starter-tpl.
This Dockerized Laravel starter template works seamlessly with wayofdev/docker-shared-services, providing a foundation with integrated DNS and SSL support, and can show you the way
to implement patterns, stated in this article, in your projects.
Linux π§
Linux Users, you are also covered!
If you're using Ubuntu or another Linux distribution, I've included a Linux Quick Start Guide in the README.md of the wayofdev/docker-shared-services
project.
This guide provides step-by-step instructions to help you set up the environment on a Linux system, ensuring you can have the same streamlined development experience as described in the macOS Quick Start Guide provided in this article.
Conclusion β¨
In conclusion, the wayofdev/docker-shared-services project offers a streamlined and automated solution for managing local development environments using Docker, mkcert, DNSMasq, and Traefik.
By integrating these tools into your workflow, you can ensure that your local environment closely mirrors production, leading to more reliable and predictable outcomes when deploying applications.
β What do you think? Would you approach this differently?
I hope this guide has been helpful in demonstrating the potential benefits of using this approach for your local development needs. If you have any suggestions or a better way of achieving this setup, please feel free to share your thoughts.
Feel free to fork this repository and adjust it to your needs, or use it as a template.
Thank you for reading, and I hope you find this setup as beneficial as I have.
Happy coding!
Top comments (0)