DEV Community

TemplateMaster
TemplateMaster

Posted on

Run TemplateMaster Locally in 5 Minutes with Docker Compose (PDF & Email API)

Generating PDFs and emails from templates is something almost every business application ends up needing: invoices, contracts, notifications, statements… TemplateMaster provides a PDF & Email generation API paired with a visual template editor.

Good news: the whole stack can run locally with a single docker compose up. In this article we'll walk through the official configuration, start it, and I'll share a few tweaks to make it more robust.

πŸ“š Source: official documentation β€” Run local environment


🧱 What we're going to run

The local stack is made of 5 services:

Service Image Role Port(s)
editorTemplate-front templatemaster/editortemplate-front Web UI / visual editor 8080
editorTemplate-api templatemaster/editortemplate-api .NET API (PDF/email generation) 5000
mssql mcr.microsoft.com/mssql/server:2019-latest Database (app + Hangfire) 1433
rabbitmq-service rabbitmq:3-management Message bus for async processing 5672, 15672
smtp4dev rnwood/smtp4dev:v3 Fake SMTP server that captures emails 9000, 2525
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  Browser   β”‚  Front :8080 β”‚
            β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚ HTTP
            β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚  API  :5000  β”œβ”€β”€β”€β”€β”€β–Ίβ”‚ SQL Server   β”‚  (TemplateEditor + HangfireDb)
            β””β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚        β”‚
     AMQP      β”‚        β”‚ SMTP
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ RabbitMQ       β”‚  β”‚ smtp4dev    β”‚  β†’ email UI on :9000
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

It's a classic .NET architecture: Hangfire for scheduled jobs, RabbitMQ for the message queue, and smtp4dev to intercept every email without ever sending anything outside. Perfect for development.


⚠️ Before you start: no encryption locally

In production, TemplateMaster encrypts sensitive data in the database. Locally, this encryption is disabled to make debugging easier. Bottom line: never use this environment with real or sensitive data.


πŸ“¦ Prerequisites

  • Docker Desktop (Docker Compose is included)
  • These ports must be free: 5000, 8080, 1433, 5672, 15672, 9000 (and 2525 for SMTP)

A handy way to check whether a port is already in use:

# Linux / macOS
lsof -i :1433

# Windows (PowerShell)
netstat -ano | findstr :1433
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Already running SQL Server on your machine? It's probably holding port 1433. Just change the mapping to "14330:1433".


πŸ—‚οΈ Project structure

Two files are all you need:

/
β”œβ”€ docker-compose.yaml
└─ .env
Enter fullscreen mode Exit fullscreen mode

docker-compose.yaml

version: "3"
services:
  smtp4dev:
    image: rnwood/smtp4dev:v3
    container_name: smtp4dev
    restart: always
    ports:
      - "9000:80"
      - "2525:25"

  mssql:
    image: mcr.microsoft.com/mssql/server:2019-latest
    container_name: mssql
    environment:
      SA_PASSWORD: "YourStrong!Passw0rd"
      ACCEPT_EULA: "Y"
    ports:
      - "1433:1433"

  rabbitmq-service:
    image: rabbitmq:3-management
    container_name: rabbitmq_service
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: "${RABBIT_MQ_USER_NAME}"
      RABBITMQ_DEFAULT_PASS: "${RABBIT_MQ_USER_PASSWORD}"

  editorTemplate-api:
    image: templatemaster/editortemplate-api:latest
    container_name: editorTemplate-api
    ports:
      - "5000:8080"
    volumes:
      - "C:/EditorTemplate:/app/EditeurDoc"
      #- /home/EditorTemplate:/app/EditeurDoc
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      ConnectionStrings__EditeurBdd: "${EDITEUR_BDD}"
      ConnectionStrings__HangfireDb: "${HANGFIRE_BDD}"
      EmailSettings__SMTPSetting__Host: "smtp4dev"
      EmailSettings__Port: "${EDITEUR_EMAIL_PORT}"
      EmailSettings__UserName: "${EDITEUR_EMAIL_USER_NAME}"
      EmailSettings__Password: "${EDITEUR_EMAIL_PASSWORD}"
      RabbitMQ__HostName: "rabbitmq://rabbitmq-service"
      RabbitMQ__UserName: "${RABBIT_MQ_USER_NAME}"
      RabbitMQ__Password: "${RABBIT_MQ_USER_PASSWORD}"
      contactEmail: "${CONTACT_EMAIL}"
      Authentication__Issuer: "${ATHENTICATION_ISSUER}"
      Authentication__Audience: "${ATHENTICATION_AUDIENCE}"
      Authentication__ClientSecret: "${ATHENTICATION_CLIENT_SECRET}"
      BaseUrl: "${BASE_URL_API}"
      BaseUrlFront: "${BASE_URL_FRONT}"
      adminEmail: "${ADMIN_EMAIL}"
      adminPassword: "${ADMIN_PASSWORD}"
      LICENSE_KEY: "${LICENSE_KEY}"
    depends_on:
      - rabbitmq-service
      - smtp4dev
      - mssql

  editorTemplate-front:
    image: templatemaster/editortemplate-front:latest
    container_name: editortemplate-front
    ports:
      - "8080:80"
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      BASE_URL: "${BASE_URL_API}"
Enter fullscreen mode Exit fullscreen mode

A few things worth noting:

  • Variables like ConnectionStrings__EditeurBdd or RabbitMQ__HostName follow the ASP.NET Core convention: the double underscore __ maps to the : separator in appsettings.json. So RabbitMQ__HostName overrides RabbitMQ:HostName.
  • Services talk to each other by name on the Docker network: the API reaches the database via Server=mssql, SMTP via smtp4dev (internal port 25) and RabbitMQ via rabbitmq-service.
  • The volume C:/EditorTemplate:/app/EditeurDoc persists generated files on the host. On Linux/macOS, uncomment the /home/EditorTemplate:/app/EditeurDoc line (and comment out the Windows one).

.env

EDITEUR_BDD='Server=mssql;Database=TemplateEditor;Trusted_Connection=False;TrustServerCertificate=True;User id=sa;Password=YourStrong!Passw0rd;'
HANGFIRE_BDD='Server=mssql;Database=HangfireDb;Trusted_Connection=False;TrustServerCertificate=True;User id=sa;Password=YourStrong!Passw0rd;'

EDITEUR_EMAIL_HOST='smtp4dev'
EDITEUR_EMAIL_PORT=25
EDITEUR_EMAIL_USER_NAME='contact@templatemaster.fr'
EDITEUR_EMAIL_PASSWORD=''

BASE_URL_API='http://localhost:5000/api/v1'
BASE_URL_FRONT='http://localhost:8080/'

RABBIT_MQ_USER_NAME='editorTemplate'
RABBIT_MQ_USER_PASSWORD='editorTemplatePassword'

ADMIN_EMAIL='admin@templatemaster.fr'
ADMIN_PASSWORD='admin'
CONTACT_EMAIL='admin@gmail.com'

ATHENTICATION_ISSUER='templatemaster.fr'
ATHENTICATION_AUDIENCE='account'
ATHENTICATION_CLIENT_SECRET='<generate-your-own-secret>'

LICENSE_KEY=''
Enter fullscreen mode Exit fullscreen mode

⚠️ Watch the spelling: the variables really are named ATHENTICATION_* (no "U"). Don't "fix" them in .env without also fixing docker-compose.yaml, or the API will start with empty values.

For the authentication secret, rather than copying a value found online, generate your own:

openssl rand -hex 32
Enter fullscreen mode Exit fullscreen mode

πŸš€ Start it up

Put both files in the same folder, then run:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

docker compose (with a space) is the Compose v2 syntax bundled with Docker Desktop. The legacy docker-compose command works too.

Once the containers are up:

URL What
http://localhost:8080/signin 🌐 TemplateMaster UI
http://localhost:9000 πŸ“¬ smtp4dev β€” every email sent by the app
http://localhost:15672 πŸ“¨ RabbitMQ management console (credentials from .env)
https://webhook.site 🌍 To test your outgoing webhooks

πŸ”‘ First login

On first startup, an admin account is created automatically from ADMIN_EMAIL and ADMIN_PASSWORD. With the .env above:

Email:    admin@templatemaster.fr
Password: admin
Enter fullscreen mode Exit fullscreen mode

Head over to http://localhost:8080/signin… and start building your first template! Send a test email: it will show up instantly in smtp4dev.


πŸ› οΈ Everyday commands

# Follow the API logs
docker compose logs -f editorTemplate-api

# Check that .env variables are interpolated correctly
docker compose config

# Stop the stack
docker compose down

# Update the TemplateMaster images (:latest tag)
docker compose pull && docker compose up -d
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ The official docs mention docker compose up -d --build. Since every service uses a prebuilt image (no build: section), --build has no effect here. To get a new version, run docker compose pull.


🧠 Going further: making the stack more robust

The official configuration works, but it has a classic pitfall: depends_on waits for the SQL Server container to start, not for it to be *ready. SQL Server often takes 10–30 seconds before accepting connections, and the API can crash on startup. The docs actually hint at this: *make sure the database is initialized before the API starts.

The fix: add healthchecks and make the API wait for them.

services:
  mssql:
    image: mcr.microsoft.com/mssql/server:2019-latest
    environment:
      MSSQL_SA_PASSWORD: "${SA_PASSWORD}"
      ACCEPT_EULA: "Y"
    ports:
      - "1433:1433"
    volumes:
      - mssql-data:/var/opt/mssql
    healthcheck:
      test: >
        /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -Q "SELECT 1"
        || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -Q "SELECT 1"
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 20s

  rabbitmq-service:
    image: rabbitmq:3-management
    # ...
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  editorTemplate-api:
    # ...
    restart: unless-stopped
    depends_on:
      mssql:
        condition: service_healthy
      rabbitmq-service:
        condition: service_healthy
      smtp4dev:
        condition: service_started

volumes:
  mssql-data:
Enter fullscreen mode Exit fullscreen mode

What changes:

  1. βœ… Healthchecks on SQL Server and RabbitMQ β†’ the API only starts once its dependencies actually respond.
  2. βœ… Named volume mssql-data β†’ your templates survive a docker compose down (without -v).
  3. βœ… SA password in .env (SA_PASSWORD=...) instead of hardcoded in the compose file. Remember to update it in EDITEUR_BDD and HANGFIRE_BDD too.
  4. βœ… MSSQL_SA_PASSWORD is the variable name Microsoft currently recommends (SA_PASSWORD is deprecated).
  5. βœ… The version: "3" line can be removed: Compose v2 ignores it.

The double sqlcmd in the healthcheck covers both possible tool locations depending on the image version (mssql-tools18 on recent images, mssql-tools on older ones).


🩺 Quick troubleshooting

Symptom Likely cause Fix
Bind for 0.0.0.0:1433 failed: port is already allocated A local SQL Server is already running Change the host port (14330:1433)
The API keeps restarting on first launch SQL Server isn't ready yet Healthcheck + condition: service_healthy (see above)
The mssql container exits immediately SA password too weak 8+ chars with upper/lowercase, digits and symbols
Empty variables in the API Typo / .env not in the right folder docker compose config to see resolved values
No email received That's expected! They're captured Open http://localhost:9000
Volume error on Linux/macOS Windows C:/... path Use /home/EditorTemplate:/app/EditeurDoc

🎯 Wrapping up

With two files and one command, you get a complete environment to try TemplateMaster: visual editor, PDF/email generation API, SQL Server database, RabbitMQ bus and email capture. Great for prototyping templates or validating an integration (for example via the .NET NuGet package) before going to production.

Security reminder: this environment is for development only β€” no database encryption, default credentials (admin/admin), well-known SA password. Never expose it to the Internet.

πŸ‘‰ Full documentation: templatemaster.fr/fr/documentation

Have you tried it? Tell me in the comments what kind of templates you're generating! πŸ‘‡

Top comments (0)