Code, Explained

How to Build a Docker SMTP Relay on Ubuntu Using Postfix

If your applications need to send emails reliably, an SMTP relay is one of the cleanest solutions.

In this tutorial, we will build a lightweight SMTP relay using Docker and Postfix on Ubuntu. Your applications will send email locally to the relay, and the relay will securely forward mail through providers like Amazon SES, SendGrid, Mailgun, or Gmail SMTP.

This setup is ideal for:

  • Laravel applications
  • WordPress websites
  • Node.js apps
  • Dockerized services
  • Internal notification systems
  • Transactional emails

Architecture

Application
    ↓ SMTP
Docker Postfix Relay
    ↓ TLS SMTP
Amazon SES / SendGrid / Mailgun
    ↓
Recipient Inbox

Prerequisites

Before starting, make sure you have:

  • Ubuntu server
  • Docker installed
  • Docker Compose plugin installed
  • SMTP provider credentials

Supported providers include:

  • Amazon SES
  • SendGrid
  • Mailgun
  • Gmail SMTP
  • Postmark

Step 1 — Install Docker

Update Ubuntu:

sudo apt update

Install Docker:

sudo apt install -y docker.io docker-compose-plugin

Enable Docker:

sudo systemctl enable --now docker

Verify installation:

docker --version

Optional: run Docker without sudo

sudo usermod -aG docker $USER
newgrp docker

Step 2 — Create Project Directory

Create a working directory:

sudo mkdir -p /opt/smtp-relay
cd /opt/smtp-relay

Step 3 — Create Persistent Storage

Create directories for mail queue and logs:

sudo mkdir -p relay
sudo mkdir -p logs

These directories ensure queued emails survive container restarts.


Step 4 — Create Docker Compose File

Create a docker-compose.yml file:


services:
  smtp-relay:
    image: boky/postfix
    container_name: smtp-relay
    restart: unless-stopped

    ports:
      - "25:25"

    environment:
      # Upstream SMTP provider
      RELAYHOST: smtp.gmail.com
      RELAYHOST_PORT: 587
      RELAYHOST_USERNAME: YourSMTPEnabledGmailUserID
      RELAYHOST_PASSWORD: YourGmailPassword

      # Allowed sender domains
      ALLOWED_SENDER_DOMAINS: wempro.com,pumpsandinstrumentations.com

      # Relay hostname
      POSTFIX_myhostname: relay.vmi3202307.local
      POSTFIX_mynetworks: 127.0.0.0/8 172.16.0.0/12 192.168.0.0/16

      POSTFIX_smtpd_recipient_restrictions: permit_mynetworks,reject_unauth_destination

      TZ: UTC

    volumes:
      # Mail queue persistence
      - ./relay:/var/spool/postfix

      # Optional logs
      - ./logs:/var/log

    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"



Save the file.


Step 5 — Start the SMTP Relay

Launch the container:

docker compose up -d

Verify container status:

docker ps

View logs:

docker logs -f smtp-relay

Step 6 — Test Email Sending

Install swaks:

sudo apt install -y swaks

Send a test email:

swaks \
  --to you@example.com \
  --from noreply@yourdomain.com \
  --server localhost:25 \
  --header "Subject: SMTP Relay Test" \
  --body "SMTP relay is working"

Successful output:

250 2.0.0 Ok: queued as ...

Step 7 — Configure Your Application

Your applications should connect to:

localhost:25

Example DSN:

smtp://localhost:25

Laravel .env example:

MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=noreply@yourdomain.com
MAIL_FROM_NAME="Your App"

Multiple Domain Support

To allow multiple sender domains:

ALLOWED_SENDER_DOMAINS: domain1.com,domain2.com,domain3.com

Why Use an SMTP Relay?

Benefits include:

  • centralized email handling
  • provider abstraction
  • email queueing
  • retry handling
  • cleaner application configuration
  • rate limiting
  • easier provider switching

Important Security Tips

Do NOT Create an Open Relay

Never use:

POSTFIX_mynetworks: 0.0.0.0/0

This will allow the internet to abuse your server for spam.


SPF, DKIM, and Deliverability

For production use, verify your domain with your SMTP provider and configure:

  • SPF
  • DKIM
  • DMARC

Without these, emails may land in spam folders.


Queue Management

View mail queue:

docker exec -it smtp-relay postqueue -p

Flush queue:

docker exec -it smtp-relay postqueue -f

Final Thoughts

A Dockerized SMTP relay is a lightweight and reliable solution for modern applications. By combining Postfix with providers like Amazon SES or SendGrid, you get:

  • reliable delivery
  • secure outbound SMTP
  • local application integration
  • retry and queue management
  • simplified infrastructure

This setup works especially well for Docker-based deployments and internal application stacks.

Happy emailing!

Posted in ubuntuTagged , , , , , ,

Testing MySQL Connectivity Using a Dockerized Python Container

When working with microservices, CI pipelines, or containerized environments, it’s often useful to verify MySQL connectivity independently of your application code.

In this post, we’ll build a lightweight Docker container that:

  • Connects to a MySQL database
  • Accepts all connection parameters at runtime
  • Waits and retries until MySQL is available
  • Fails fast if configuration is missing
  • Supports connection timeouts
  • Lists up to 5 tables if the user has visibility or proper privileges
  • Returns CI-friendly exit codes

This approach is ideal for smoke tests, health checks, and CI/CD pipelines.

Why a Dedicated MySQL Connection Tester?

Common scenarios where this is useful:

  • Verifying database access in CI before running migrations
  • Testing credentials in Kubernetes or Docker Compose
  • Debugging network or permission issues
  • Smoke-testing production or staging databases safely

Instead of baking logic into your app, we use a single-purpose container.

Solution Overview

We will build:

  • A Python-based Docker image
  • Runtime-configurable MySQL connection
  • Built-in retry and timeout logic
  • Optional table listing (non-fatal if permissions are missing)

Final Dockerfile

FROM python:3.11-slim

WORKDIR /app

RUN pip install mysql-connector-python

RUN echo "import os" > test_db.py && \
    echo "import sys" >> test_db.py && \
    echo "import time" >> test_db.py && \
    echo "import mysql.connector" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "REQUIRED_VARS = ['MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE']" >> test_db.py && \
    echo "missing = [v for v in REQUIRED_VARS if not os.getenv(v)]" >> test_db.py && \
    echo "if missing:" >> test_db.py && \
    echo "    print('Missing required environment variables: ' + ', '.join(missing))" >> test_db.py && \
    echo "    sys.exit(1)" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "MAX_RETRIES = int(os.getenv('MYSQL_MAX_RETRIES', 10))" >> test_db.py && \
    echo "RETRY_DELAY = int(os.getenv('MYSQL_RETRY_DELAY', 3))" >> test_db.py && \
    echo "CONNECT_TIMEOUT = int(os.getenv('MYSQL_CONNECT_TIMEOUT', 5))" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "for attempt in range(1, MAX_RETRIES + 1):" >> test_db.py && \
    echo "    try:" >> test_db.py && \
    echo "        print(f'Attempt {attempt}: Connecting to MySQL...')" >> test_db.py && \
    echo "        conn = mysql.connector.connect(" >> test_db.py && \
    echo "            host=os.getenv('MYSQL_HOST')," >> test_db.py && \
    echo "            user=os.getenv('MYSQL_USER')," >> test_db.py && \
    echo "            password=os.getenv('MYSQL_PASSWORD')," >> test_db.py && \
    echo "            database=os.getenv('MYSQL_DATABASE')," >> test_db.py && \
    echo "            port=int(os.getenv('MYSQL_PORT', 3306))," >> test_db.py && \
    echo "            connection_timeout=CONNECT_TIMEOUT" >> test_db.py && \
    echo "        )" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "        if conn.is_connected():" >> test_db.py && \
    echo "            print('MySQL connection successful!')" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "            try:" >> test_db.py && \
    echo "                cursor = conn.cursor()" >> test_db.py && \
    echo "                try:" >> test_db.py && \
    echo "                    cursor.execute('SHOW TABLES')" >> test_db.py && \
    echo "                    tables = cursor.fetchmany(5)" >> test_db.py && \
    echo "                    source = 'SHOW TABLES'" >> test_db.py && \
    echo "                except mysql.connector.Error:" >> test_db.py && \
    echo "                    cursor.execute(" >> test_db.py && \
    echo "                        'SELECT table_name FROM information_schema.tables WHERE table_schema = %s LIMIT 5'," >> test_db.py && \
    echo "                        (os.getenv('MYSQL_DATABASE'),)" >> test_db.py && \
    echo "                    )" >> test_db.py && \
    echo "                    tables = cursor.fetchall()" >> test_db.py && \
    echo "                    source = 'information_schema.tables'" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "                if tables:" >> test_db.py && \
    echo "                    print(f'Listing up to 5 tables using {source}:')" >> test_db.py && \
    echo "                    for t in tables:" >> test_db.py && \
    echo "                        print(' - ' + t[0])" >> test_db.py && \
    echo "                else:" >> test_db.py && \
    echo "                    print('Connected, but no tables found or insufficient privileges.')" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "            except mysql.connector.Error as e:" >> test_db.py && \
    echo "                print('Connected, but cannot list tables: ' + str(e))" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "            conn.close()" >> test_db.py && \
    echo "            sys.exit(0)" >> test_db.py && \
    echo "" >> test_db.py && \
    echo "    except Exception as e:" >> test_db.py && \
    echo "        print('Connection failed: ' + str(e))" >> test_db.py && \
    echo "        if attempt == MAX_RETRIES:" >> test_db.py && \
    echo "            print('Max retries reached. Exiting.')" >> test_db.py && \
    echo "            sys.exit(2)" >> test_db.py && \
    echo "        time.sleep(RETRY_DELAY)" >> test_db.py

CMD ["python", "test_db.py"]

Build the Image

docker build -t mysql-test-client .

Run the Container (Passing Parameters at Runtime)

sudo docker run --rm --network=esearchnetwork -e MYSQL_HOST=your_host -e MYSQL_PORT=port2connect -e MYSQL_USER=your_db -e MYSQL_PASSWORD=your_super_secret_password -e MYSQL_DATABASE=your_database -e MYSQL_CONNECT_TIMEOUT=10 -e MYSQL_MAX_RETRIES=5 -e MYSQL_RETRY_DELAY=2 mysql-test-client

Example Output

Successful connection with privileges

Attempt 1: Connecting to MySQL…
MySQL connection successful!
Listing up to 5 tables:
users
orders
products
invoices
logs

Successful connection without table privileges

MySQL connection successful!
Connected, but no tables found or insufficient privileges.

Exit Codes (CI-Friendly)

Exit CodeMeaning
0Connection successful
1Missing required environment variables
2MySQL unreachable after retries

Best Practices & Improvements

Recommended enhancements:

  • Replace echo with COPY test_db.py for maintainability
  • Output JSON for CI parsing
  • Add TLS / SSL parameters

Conclusion

This lightweight Docker-based MySQL connection tester is a reliable, reusable, and secure way to validate database access across environments.

It works equally well for:

  • Local development
  • Docker Compose
  • Kubernetes
  • CI/CD pipelines

If you need a cleaner version with a standalone Python file, or a Compose/Kubernetes variant, this setup is easy to extend.

Happy testing

Posted in mysql, PythonTagged , , ,

Create Docker Container for Hello World with Django and uWsgi Server

I was searching a Hello World implementation for Django of Python in Docker container, but can’t find any good resource at online. So, I plan to code it myself and document it.

This is pure Docker implementation, you don’t need to create any project for Django. You just need Dockerfile to see “Hello World” at browser which powered by Django and uWsgi module.

Here is high level explanation that I’m going TODO –

  • Python, Pip and setuptools installation and upgrade
  • Create requirement.txt file
  • Execute requirement.txt file with Pip
  • Create Django project
  • Modify project settings to allow our domain in Django
  • Replace project’s urls.py to send “Hello World” string to output
  • Code to run server through uWsgi module

Entire steps I’ll do into a single Dockerfile, which we need to build and run through Docker. Here is step by step implementation of Dockerfile.

FROM python:3.11.3
WORKDIR /code
RUN pip install --upgrade pip
RUN pip install setuptools
RUN pip install -U setuptools

Its pretty straight forward, we are using Python 3.11.3 and install Pip and setuptools here.

RUN echo "Django==4.2" >> requirements.txt
RUN echo "uWSGI==2.0.25" >> requirements.txt
RUN pip install -r requirements.txt

Here we create requirement.txt file where we instruct to install Django version 4.2 and uWSGI module version 2.0.25 and then we execute the newly created requirements.txt through Pip.

RUN django-admin startproject helloworlddjango
WORKDIR /code/helloworlddjango
RUN echo "ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'helpabodessltest.shahadathossain.com']" >> helloworlddjango/settings.py

In this stage we created helloworld project with django-admin (we already Django installed) also we append our project’s settings.py to allow our domain. For this we just append “ALLOWED_HOSTS” variable value.

RUN echo "from django.urls import path" > helloworlddjango/urls.py
RUN echo "from django.shortcuts import HttpResponse" >> helloworlddjango/urls.py
RUN echo "def home_page_view_hello_world(request):" >> helloworlddjango/urls.py
RUN echo "    return HttpResponse('Hello World')" >> helloworlddjango/urls.py
RUN echo "urlpatterns = [path('', home_page_view_hello_world, name='helloworld'),]" >> helloworlddjango/urls.py

This part actually pure Python code we (re)writing our urls.py file where we actually put “Hello World” string when user visit home page of our project.

RUN adduser --disabled-password --no-create-home django
USER django
ENTRYPOINT ["uwsgi", "--http", ":9000", "--workers", "4", "--master", "--enable-threads", "--module", "helloworlddjango.wsgi"]

This is another part where we run our project through uwsgi module. We can run straightly by Django’s builtin server by “manage.py” but here I covered to run uwsgi server.

Here is link https://github.com/razonklnbd/django-hello-world-with-docker where you found complete Dockerfile

To build docker container you have to have docker in your system. After ensuring docker into system you can use following commands to build and run –

sudo docker build -t django-hello-world-mshk .
sudo docker run --name djangohelloworldmshk -d --network=host django-hello-world-mshk:latest

You need to execute into the location where you put your Dockerfile. Please feel free to change container tag and name. You may like following command of docker to see the log and to delete running container (in case you are debugging something)

sudo docker logs djangohelloworldmshk
sudo docker rm $(sudo docker stop $(sudo docker ps -a -q --filter ancestor=django-hello-world-mshk --format="{{.ID}}"))
sudo docker rmi django-hello-world-mshk

That’s all for today! Thanks.

Posted in linux, Python, webdevelopmentTagged , , , ,

Execute React JS in ntfs partition

I do development in mounted hard drive which is different than OS partition, also I like to use that mounted drive within different OS like windows and linux simultaneously. Which allow me portability of my code backup in different system.

Challenge is, I have to use such file system for that mounted drive which can accessible in most of the OS like windows, linux or iOS. And in this case NTFS is the best file system which is really portable. Problem for this FS is, it can’t support executable bit of linux which lead to raise many problem in React development. Like if you put any React project into that mounted drive, you can’t execute that code.

To solve this problem I take Docker as a solution. Simply, I create docker image of React project and execute that image. Here is the simple Dockerfile for React project –

FROM node:18
WORKDIR /app
COPY package.json ./
RUN npm install
RUN npm install -g npm@9.7.2
COPY . ./
EXPOSE 3000
CMD ["npm", "start"]

You just need to build this docker image and need to run. You can see docker log for any output for your React project.

Posted in ubuntu, windowsTagged , , , ,