Setting up isolated local testing environments using Docker Compose, DNSMasq, and self-signed local SSL on openSUSE
Prerequisites and System Setup
This guide assumes a working openSUSE Leap or Tumbleweed installation. We’ll be leveraging Docker Compose for service orchestration, DNSMasq for local DNS resolution, and Nginx for SSL termination with self-signed certificates. Ensure you have the following packages installed. If not, use zypper to install them:
- Docker and Docker Compose:
docker-compose(ordocker composefor newer versions) - DNSMasq:
dnsmasq - Nginx:
nginx
Verify your Docker installation:
docker --versiondocker compose version(ordocker-compose --version)
Verify DNSMasq installation:
dnsmasq --version
Verify Nginx installation:
nginx -v
Configuring DNSMasq for Local Domain Resolution
We need DNSMasq to resolve custom local domains (e.g., app1.local, db.local) to 127.0.0.1. This allows our applications within Docker containers to refer to each other using hostnames, and for our local machine to access them directly.
First, create a configuration file for DNSMasq. It’s good practice to keep custom configurations separate from the main dnsmasq.conf. We’ll use /etc/dnsmasq.d/local.conf.
- Create the directory if it doesn’t exist:
sudo mkdir -p /etc/dnsmasq.d/ - Create and edit the configuration file:
sudo nano /etc/dnsmasq.d/local.conf
Add the following lines to /etc/dnsmasq.d/local.conf:
# Listen only on the loopback interface listen=127.0.0.1 # Specify a domain for local resolution local=/local/ # Resolve all .local domains to 127.0.0.1 address=/local/127.0.0.1 # Cache DNS queries cache-size=1000 # Log DNS queries (optional, for debugging) log-queries log-dhcp
Next, we need to configure our system to use DNSMasq for DNS resolution. Edit /etc/resolv.conf. It’s crucial to note that on many modern Linux systems, /etc/resolv.conf is managed by NetworkManager or systemd-resolved. If it’s a symlink, you’ll need to configure the managing service. For a direct setup or if /etc/resolv.conf is not a symlink:
Backup the original file:
sudo cp /etc/resolv.conf /etc/resolv.conf.backup
Edit the file:
sudo nano /etc/resolv.conf
Ensure the first nameserver points to 127.0.0.1:
nameserver 127.0.0.1 options edns0 trust-ad
If /etc/resolv.conf is managed by NetworkManager, you’ll need to configure NetworkManager to use DNSMasq. Edit /etc/NetworkManager/NetworkManager.conf and ensure the dns setting is set to dnsmasq:
[main] plugins=ifcfg-suse dns=dnsmasq
After making changes to NetworkManager, restart the service:
sudo systemctl restart NetworkManager
Finally, restart DNSMasq to apply the new configuration:
sudo systemctl enable dnsmasqsudo systemctl restart dnsmasq
Test your DNS resolution. You should be able to ping a local domain:
ping app1.local
This should resolve to 127.0.0.1 and show successful pings.
Generating Self-Signed SSL Certificates
For secure local communication, we’ll generate self-signed SSL certificates. This is essential for testing HTTPS endpoints. We’ll create a certificate for a wildcard domain (e.g., *.local) to cover all our local subdomains.
First, create a directory to store your certificates:
sudo mkdir -p /etc/nginx/ssl/localcd /etc/nginx/ssl/local
Generate the private key and the certificate signing request (CSR) using OpenSSL. We’ll create a configuration file for OpenSSL to specify the Subject Alternative Names (SANs), which is crucial for modern browsers.
Create an OpenSSL configuration file, e.g., /etc/nginx/ssl/local/openssl.cnf:
[req] distinguished_name = req_distinguished_name req_extensions = req_extensions prompt = no [req_distinguished_name] C = US ST = California L = San Francisco O = MyCompany OU = IT CN = *.local [req_extensions] basicConstraints = CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = *.local DNS.2 = localhost
Now, generate the private key and the self-signed certificate:
# Generate private key sudo openssl genrsa -out local.key 2048 # Generate CSR and self-signed certificate # The -days parameter sets the validity period (e.g., 3650 days for 10 years) sudo openssl req -x509 -new -nodes -key local.key -sha256 -days 3650 -out local.crt -config openssl.cnf
You should now have local.key (private key) and local.crt (certificate) in /etc/nginx/ssl/local/. Secure the private key:
sudo chmod 600 /etc/nginx/ssl/local/local.key
To trust this certificate on your local machine (and avoid browser warnings), you need to import it into your system’s trust store. The exact steps vary by OS and browser, but generally involve:
- Linux (System-wide): Copy the certificate to
/etc/pki/trust-source/anchors/and runsudo update-ca-certificates. - Firefox: Go to Settings -> Privacy & Security -> Certificates -> View Certificates -> Authorities -> Import.
- Chrome/Chromium: Use the system trust store.
For openSUSE, you can import it system-wide:
sudo cp /etc/nginx/ssl/local/local.crt /etc/pki/trust-source/anchors/sudo update-ca-certificates
Setting up Nginx as a Local Reverse Proxy
Nginx will act as our reverse proxy, handling SSL termination and routing requests to the appropriate Docker containers. We’ll configure Nginx to listen on ports 80 and 443, and use our self-signed certificate.
Create a new Nginx configuration file for our local environments, e.g., /etc/nginx/conf.d/local-proxy.conf.
sudo nano /etc/nginx/conf.d/local-proxy.conf
Add the following configuration. This is a template; you’ll add specific server blocks for each application.
# Redirect HTTP to HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _; # Catch all for HTTP
return 301 https://$host$request_uri;
}
# HTTPS server block for local domains
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name _; # Catch all for HTTPS
ssl_certificate /etc/nginx/ssl/local/local.crt;
ssl_certificate_key /etc/nginx/ssl/local/local.key;
# Recommended SSL settings for security
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_stapling off; # OCSP stapling is not relevant for self-signed certs
# Default fallback if no specific server_name matches
location / {
return 404 "Not Found: No specific server block configured for this host.";
}
# Add specific server blocks below for your applications
# Example for app1.local:
# include /etc/nginx/conf.d/app1.local.conf;
}
Test your Nginx configuration:
sudo nginx -t
If the test is successful, reload Nginx:
sudo systemctl reload nginx
Docker Compose for Isolated Environments
Now, let’s define our isolated testing environments using Docker Compose. Each environment can have its own set of services (web servers, databases, APIs, etc.). We’ll create a docker-compose.yml file for each distinct application or service group.
Consider an example scenario: a web application (app1) that depends on a database (db1). We’ll create a docker-compose.yml file for this.
Create a directory for your application’s Docker Compose setup, e.g., ~/dev/app1.
mkdir -p ~/dev/app1cd ~/dev/app1
Create the docker-compose.yml file:
version: '3.8'
services:
app1:
build: . # Assumes a Dockerfile in the current directory
container_name: app1_web
ports:
- "8000:80" # Map container port 80 to host port 8000 for direct access if needed
environment:
DATABASE_HOST: db1
DATABASE_USER: appuser
DATABASE_PASSWORD: securepassword
DATABASE_NAME: appdb
depends_on:
- db1
networks:
- app1_net
db1:
image: postgres:14-alpine
container_name: app1_db
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: securepassword
POSTGRES_DB: appdb
volumes:
- db1_data:/var/lib/postgresql/data
networks:
- app1_net
networks:
app1_net:
driver: bridge
volumes:
db1_data:
In this docker-compose.yml:
app1service: This is your application. It’s configured to build from a localDockerfile. It depends ondb1and uses environment variables to connect to it. We map port 8000 on the host to port 80 in the container.db1service: A PostgreSQL database instance. It uses environment variables for initial setup and a named volumedb1_datafor persistent storage.app1_net: A custom bridge network for this environment. This ensures that services within this compose file can communicate using their service names (e.g.,db1) and are isolated from other Docker networks by default.
You’ll also need a Dockerfile in the same directory (~/dev/app1/) for your app1 service. Here’s a basic example for a PHP application:
FROM php:8.2-apache WORKDIR /var/www/html COPY . /var/www/html # Install any necessary PHP extensions or system packages # RUN docker-php-ext-install pdo pdo_pgsql # RUN apt-get update && apt-get install -y --no-install-recommends some-package && rm -rf /var/lib/apt/lists/* # Ensure Apache can serve files RUN chown -R www-data:www-data /var/www/html
Integrating Docker Compose with Nginx and DNSMasq
The final step is to configure Nginx to proxy requests for app1.local to the Docker container. We’ll create a separate Nginx configuration file for each application.
Create a new Nginx configuration file for app1.local, e.g., /etc/nginx/conf.d/app1.local.conf:
sudo nano /etc/nginx/conf.d/app1.local.conf
server {
listen 443 ssl http2;
server_name app1.local;
ssl_certificate /etc/nginx/ssl/local/local.crt;
ssl_certificate_key /etc/nginx/ssl/local/local.key;
location / {
proxy_pass http://127.0.0.1:8000; # Proxy to the host port mapped by Docker Compose
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Optional: If your app serves static files directly, configure that here
# location /static/ {
# alias /path/to/your/static/files/;
# }
}
In this Nginx configuration:
server_name app1.local;: This directive tells Nginx to use this block for requests toapp1.local.proxy_pass http://127.0.0.1:8000;: This forwards the request to the host port (8000) that we mapped in ourdocker-compose.yml. Nginx doesn’t need to know about Docker’s internal networking directly; it just talks to the host port.- The
proxy_set_headerdirectives are crucial for passing client information to the backend application.
After creating this file, test Nginx again and reload it:
sudo nginx -tsudo systemctl reload nginx
Now, you can start your Docker Compose environment:
- Navigate to your application’s directory:
cd ~/dev/app1 - Start the services:
docker compose up -d(the-dflag runs containers in detached mode)
You should now be able to access your application securely via https://app1.local in your browser. The browser might show a warning initially if you haven’t fully trusted the self-signed certificate system-wide, but it should be accessible.
Managing Multiple Isolated Environments
To manage multiple isolated environments, simply repeat the process:
- Create a new directory for each environment (e.g.,
~/dev/app2). - Create a unique
docker-compose.ymlfile for each environment, ensuring distinct network names and service names. - Create a corresponding Nginx configuration file (e.g.,
/etc/nginx/conf.d/app2.local.conf) that proxies to the host port exposed by that environment’s Docker Compose setup. - Ensure each application’s
server_namein Nginx is unique (e.g.,app2.local). - Start each environment using
docker compose up -din its respective directory.
For example, for a second application app2.local:
- Create
~/dev/app2/docker-compose.ymland~/dev/app2/Dockerfile. - Start it with
cd ~/dev/app2 && docker compose up -d. - Create
/etc/nginx/conf.d/app2.local.confwithserver_name app2.local;andproxy_pass http://127.0.0.1:8001;(assuming port 8001 is mapped in app2’s compose file). - Reload Nginx:
sudo systemctl reload nginx.
This setup provides a robust, isolated, and secure local development and testing environment, mimicking production setups with custom domains and SSL, all managed efficiently on your openSUSE workstation.
Leave a Reply
You must be logged in to post a comment.