Skip to content

ajwtech/MarketForge

Repository files navigation

MarketForge Azure Deployment

This repo is used to produce production environments. This readme likely still has some issues. Don't assume it is 100%, let us know if you find any bugs or submit a PR.

This repository contains the infrastructure code for deploying MarketForge using Pulumi and Azure.

MarketForge is meant to be a turn-key webapp, marketing and sales platform comprised of OSS's. Currently we use Hubspot features as the target so that we don't require any significant market research. Being turn-key and low complexity requires opinions, and this stack has a lot of them. If you don't like them, feel free to submit issues to change it, we don't see them as hardened at this time. If we decide not to accept the change, then fork this repo and build something better.

The systems implemented in this are Strapi CMS, Mautic marketing automation platform, SuiteCRM, Launchpad Next.js template. All of these have commercial offerings and are scalable to accomodate any buisiness model.

WIP Launchpad (strapi + next.js) template implemented.

Architecture

Infrastructure Architecture

Note: This diagram uses the ELK layout which may not render correctly on GitHub. For best viewing:

---
config:
  layout: elk
  theme: neo-dark
  look: neo
---
flowchart TB
 subgraph Storage["Azure Storage"]
        MauticFiles["Mautic Files"]
        StrapiFiles["Strapi Files"]
        SuiteCRMFiles["SuiteCRM Files"]
        FrontendFiles["Frontend Files"]
        JumpboxFiles["Jumpbox Files"]
  end
 subgraph NginxConfig["NGINX Configs"]
        DefaultConf["default.conf"]
        StrapiConf["strapi.conf"]
        SuiteCRMConf["suitecrm.conf"]
        FrontendConf["frontend.conf"]
  end
 subgraph Containers["Container Applications"]
        NGINX["NGINX Container\nWeb & API Gateway"]
        Mautic["Mautic Container\nMarketing Automation"]
        Strapi["Strapi Container\nHeadless CMS"]
        SuiteCRM["SuiteCRM Container\nCRM Platform"]
        NginxConfig
  end
 subgraph ContainerApps["Azure Container Apps"]
        AzureFn["Azure Functions"]
        Containers
        
  end
 subgraph Azure["Azure Cloud"]
        Storage
        MySQL["Azure MySQL"]
        ACR["Azure Container Registry"]
        Logs["Azure Monitoring"]
        ManagedEnv["Container Apps\nManaged Environment"]
        Certs["TLS Certificates"]
        ContainerApps
  end
    Github["GitHub Repository"] -- Triggers CI/CD or Pulumi up --> CICD["GitHub Actions & Pulumi"]
    CICD -- Builds & Pushes\nImages --> ACR
    CICD -- Provisions --> ManagedEnv & MySQL & Storage
    CICD -- Configures --> Cloudflare["Cloudflare DNS"]
    ManagedEnv -- Issues --> Certs
    ManagedEnv -- Hosts --> ContainerApps
    ACR -- Provides Images --> Containers
    Certs -- Secure --> Containers
    MauticFiles -- Mounted to --> NGINX & Mautic
    StrapiFiles -- Mounted to --> Strapi
    SuiteCRMFiles -- Mounted to --> SuiteCRM & NGINX
    FrontendFiles -- Mounted to --> NGINX
    Mautic -- Stores Data --> MySQL
    Strapi -- Stores Data --> MySQL
    SuiteCRM -- Stores Data --> MySQL
    AzureFn -- Provides Dynamic Content --> NGINX
    NginxConfig -- Configures --> NGINX
    FrontendConf -- Routes to --> AzureFn
    NGINX -- Internet Traffic --> Cloudflare
    Strapi -- Direct Internet Traffic --> Cloudflare
    Containers -- Send Logs --> Logs
    MySQL -- Send Metrics --> Logs
    AzureFn -- Send Logs --> Logs
    Cloudflare -- Provides DNS records --> ManagedEnv
     MauticFiles:::storage
     StrapiFiles:::storage
     SuiteCRMFiles:::storage
     FrontendFiles:::storage
     JumpboxFiles:::storage
     DefaultConf:::config
     StrapiConf:::config
     SuiteCRMConf:::config
     FrontendConf:::config
     NGINX:::nginx
     Mautic:::mautic
     Strapi:::strapi
     SuiteCRM:::suitecrm
     NginxConfig:::config
     Containers:::container
     Storage:::storage
     MySQL:::azure
     AzureFn:::azureFn
     ACR:::azure
     Logs:::azure
     ManagedEnv:::azure
     Certs:::azure
     ContainerApps:::containerApps
     Github:::github
     CICD:::cicd
     Cloudflare:::cloudflare
    classDef azure fill:#0072C6,color:white,stroke:#0072C6,stroke-width:2px
    classDef storage fill:#3e95cd,color:white,stroke:#3e95cd,stroke-width:2px
    classDef container fill:#326CE5,color:white,stroke:#326CE5,stroke-width:2px
    classDef nginx fill:#009639,color:white
    classDef strapi fill:#8E75FF,color:white
    classDef mautic fill:#4E5E9E,color:white
    classDef suitecrm fill:#30AC2F,color:white
    classDef github fill:#333,color:white
    classDef cicd fill:#FF9900,color:white
    classDef cloudflare fill:#F48120,color:white
    classDef config fill:#E2B93D,color:#333
    classDef azureFn fill:#0062AD,color:white,stroke:#FFB900,stroke-width:2px
    classDef containerApps fill:#0E46AF,color:white,stroke:#D180FF,stroke-width:2px

Loading

Process and Data Flow (Sequence Diagram)

sequenceDiagram
    actor Customer as Customer/Prospect
    actor Marketer as Marketing Team
    actor Sales as Sales Team
    participant Strategy as Planning Phase
    participant CMS as Strapi CMS
    participant Web as EpicWebStack
    participant Marketing as Mautic
    participant CRM as SuiteCRM
    
    %% Planning and Strategy Phase
    Marketer ->> Strategy: Define marketing strategy
    Marketer ->> Strategy: Create campaign plans
    Marketer ->> Strategy: Set goals & KPIs
    
    %% Content Creation Phase
    Marketer ->> CMS: Create website content
    Marketer ->> CMS: Design landing pages
    Marketer ->> CMS: Develop marketing assets
    CMS ->> Web: Publish content & assets
    
    %% Marketing Campaign Setup
    Marketer ->> Marketing: Configure lead scoring
    Marketer ->> Marketing: Build email templates
    Marketer ->> Marketing: Set up automation workflows
    Marketer ->> Marketing: Define segments
    
    %% Customer Acquisition Phase
    Customer ->> Web: Discover website (organic/ads)
    Customer ->> Web: Browse product pages
    Web ->> Marketing: Track behavior & engagement
    Customer ->> Web: Download resources
    Web ->> Marketing: Capture lead information
    
    %% Lead Nurturing
    Marketing ->> Marketing: Score leads
    Marketing ->> Marketing: Segment contacts
    Marketing ->> Customer: Send targeted emails
    Customer ->> Web: Re-engage with website
    Web ->> Marketing: Update lead activity
    Marketing ->> Marketing: Qualify leads
    
    %% Marketing to Sales Handoff
    Marketing ->> CRM: Transfer qualified leads
    Marketing ->> Sales: Alert about hot leads
    
    %% Sales Process
    Sales ->> CRM: Review lead information
    Sales ->> Customer: Initial outreach
    Customer ->> Sales: Schedule meeting
    Sales ->> CRM: Record interactions
    Sales ->> CRM: Update opportunity status
    Sales ->> Customer: Deliver presentation
    Sales ->> CRM: Generate quote/proposal
    Customer ->> Sales: Request modifications
    Sales ->> CRM: Update quote
    
    %% Deal Closing
    Customer ->> Sales: Accept proposal
    Sales ->> CRM: Mark opportunity as won
    Sales ->> CRM: Process contract
    Sales ->> Customer: Welcome kit & onboarding
    
    %% Post-Sale Activities
    Sales ->> Marketing: Add to customer segment
    Marketing ->> Customer: Send nurturing content
    Sales ->> CRM: Schedule follow-ups
    
    %% Analytics
    Web -->> Marketing: Analytics data
    Marketing -->> Marketer: Campaign performance reports
    CRM -->> Sales: Sales pipeline metrics
    Marketing -->> CRM: Attribution data
    CRM -->> Marketer: Customer acquisition metrics
Loading

User Journey (Journey Diagram)

journey
    title Customer Journey Through MarketForge
    section Website Visit
        Browse product pages: 5: Customer
        Read blog content: 4: Customer
        Download resources: 3: Customer
    section Form Submission
        Complete contact form: 3: Customer
        Schedule demo: 2: Customer
        Live chat question: 3: Customer
    section Marketing Nurturing
        Receive emails: 3: Customer
        Open emails: 2: Customer
        Click links: 2: Customer 
    section Sales Conversion
        Sales call: 5: Sales
        Product demo: 4: Sales
        Receive quote: 3: Sales, Customer
        Purchase decision: 1: Customer
Loading

Known Issues and manual steps needed

  • Cloudflare DNS entries need manual intervention and are not being made at the right time. it should be after the containers and before the certs and binding so that the containers are created with the a disabled sub domain, the dns entries get made in cloudflare then the certs are created validated and assigned to the container.

    • The config variable createSubdomains needs to be run as false on the initial run, then set to true for the second run. After that, leave it on true.
    • It seems that what is happening is the containerApps are initially trying to validate the DNS records before they are "committed" to Cloudflare. I am not sure how yet because the container apps shouldn't have an affect on that. So to get past it I have to manually create the entries in cloudflare.
    • On the second run I get an error saying that the DNS record is already present, but it creates the certs and binds them.
    • Then I have to remove the entries in cloudflare and let the code update it. I'm sure this is all dependencies and timing. I just havent dug in yet.
    • Pulumi up --target is likely a better solution to this, I just have not yet tried it.
  • Availability zone for the mysqlServer in "mysqlServer.ts" is set to "" because, if not, then the lowest tier subscription is a lot more limited in regions it can be deployed in.

  • database admin and user need a clearer separation. To start you can set the same password to get it all to install then change the password after it is setup.

  • Mautic's config files still seem to have some issues. the persistant config files are being created as directories because they are not pre-existing in the azure storage. for now you have to delete them and start mautic without them mounted. Let mautic create the files and then move them to storage. Then you can add the mount back and restart.

Prerequisites

  1. Install Pulumi: Ensure you have Pulumi installed. Pulumi website to install Pulumi.
  2. Install Node.js: Ensure you have Node.js >=20.0.0 installed. You can download it from nodejs.org.
  3. Install Docker: Ensure Docker is installed and running on your machine. You can download it from docker.com.
  4. Azure CLI: Install the Azure CLI from docs.microsoft.com.
  5. Cloudflare Account: You must have a Cloudflare account with an API token. The token should be configured as a secret in Pulumi.
  6. Azure Account: You must have an Azure account since that is the cloud this is built to run on.
  7. Fork this repository

Setup

  1. Clone the repository:

    git clone https://github.com/yourusername/MarketForge.git
    cd MarketForge/azure-deploy
  2. Install dependencies:

    npm install
  3. Log in and initialize Pulumi stack:

    pulumi login
    pulumi stack init dev # replace dev with the name for your stack
  4. Set configuration values:

    Required Configuration

    Configure the settings that do not have defaults:

    pulumi config set --secret azure:subscriptionId <your-azure-subscription-id>
    pulumi config set --secret azure:dbPassword <your-db-password>
    pulumi config set --secret azure:storageAccountName <your-storage-account-name>
    pulumi config set azure:resourceGroupName <your-resource-group-name>
    pulumi config set --secret azure:mysqlAdminPassword <your-mysql-admin-password>
    pulumi config set azure:mysqlAdminUser <your-mysql-admin-user>
    pulumi config set azure:mysqlServerName <your-mysqlServerName>
    pulumi config set azure:mysqlSkuName <sku-name>
    pulumi config set azure:mysqlSkuTier <sku-tier>
    pulumi config set azure:domain <your-domain> 
    pulumi config set --secret cloudflare:apiToken <your-cloudflare-api-token>
    pulumi config set --secret strapi:adminJwtSecret <your-adminJwtSecret>
    pulumi config set --secret strapi:jwtSecret <your-jwtSecret>
    pulumi config set --secret strapi:appKeys <your-appKeys>
    pulumi config set azure:dbClient mysql    # dbClient currently needs to be mysql changing this shouldn't be much it just needs the right drives to be installed. 

    Optional Configuration

    pulumi config set azure:location <your-azure-location>
    pulumi config set azure:appEnv <dev|prod>
    pulumi config set azure:cmsSubdomain cms     # override defaults if needed
    pulumi config set azure:crmSubdomain crm
    pulumi config set azure:mapSubdomain map
    pulumi config set azure:imageTag <your-image-tag>
    pulumi config set azure:mysqlDbName <your-mysql-db-name>

    Database name configuration

    If you are running multiple instances of Marketforge but using the same database server you will need to make sure your database names do not clash with the defaults

pulumi config set mautic:mysqlDbName <your-mysql-db-name>
pulumi config set strapi:mysqlDbName <your-mysql-db-name>
pulumi config set suitecrm:mysqlDbName <your-mysql-db-name>
  1. Note on Cloudflare:
    The Cloudflare API token is used to create DNS records for your application's custom domains. Ensure you have the token available in your environment or Pulumi's secure store. It will be referenced in code as marketing:apiToken.

Deployment

  1. Build and deploy the infrastructure:

    pulumi up
  2. Verify deployment: After the deployment is complete, verify your resources in the Azure portal and check that Cloudflare DNS records have been created correctly.

Nginx Logging Configuration

The Nginx container includes a flexible, centralized logging system that can be controlled through environment variables without rebuilding images or changing configuration files.

Logging Overview

The logging system defines several types of logs:

  • Access logs: HTTP request activity
  • Error logs: Nginx errors and warnings
  • Debug logs: Detailed debugging information
  • Static content logs: Access logs for static files (can be high volume)

Each component in the system (Mautic, SuiteCRM, Strapi, etc.) has its own dedicated log files when logging is enabled.

Controlling Logging

Environment Variables

Logging is controlled through these environment variables:

Environment Variable Description Values
NGINX_LOGGING_ENABLED Master switch for all logging off (default), on, debug, full
NGINX_ACCESS_LOG_ENABLED Control access logs off, on, or empty (follow master switch)
NGINX_ERROR_LOG_ENABLED Control error logs off, on, or empty (follow master switch)
NGINX_DEBUG_LOG_ENABLED Control debug logs off, on, or empty (follow master switch)
NGINX_STATIC_LOG_ENABLED Control static content logs off (default), on

Log Levels

  • off: All logging disabled
  • on: Normal logging (access and error logs)
  • debug: Includes detailed debug logs
  • full: All logs enabled, including static content

Usage Examples

  1. Production environment (minimal logging):

    NGINX_LOGGING_ENABLED=off
    
  2. Normal monitoring:

    NGINX_LOGGING_ENABLED=on
    
  3. Troubleshooting an issue:

    NGINX_LOGGING_ENABLED=debug
    
  4. Debugging static content issues:

    NGINX_LOGGING_ENABLED=on
    NGINX_STATIC_LOG_ENABLED=on
    

Configuring in Pulumi

The Pulumi infrastructure automatically sets sensible defaults based on environment:

  • Development environments use debug level
  • Production environments use off to minimize log storage

To change logging settings:

  1. Edit the mauticNginx.ts file to modify the environment variables
  2. Or update your Pulumi config file:
    pulumi config set --path 'marketforge:loggingEnabled' 'on'

Viewing Logs

Log files are stored in the shared volume at /var/log/nginx/ and include:

  • General logs: access.log, error.log, debug.log
  • Application-specific logs: mautic_access.log, strapi_debug.log, etc.

You can view logs using Azure Portal's storage explorer or by connecting to the container:

az containerapp exec -n mautic-nginx -g <resource-group> --command "cat /var/log/nginx/access.log"

For continuous monitoring:

az containerapp exec -n mautic-nginx -g <resource-group> --command "tail -f /var/log/nginx/error.log"

Cleanup

To clean up the resources created by Pulumi, run:

pulumi destroy
pulumi stack rm

Additional Notes

  • Cloudflare DNS Records: The deployment creates both CNAME and TXT records in Cloudflare for domain verification. If you need to adjust the record types or values, refer to the customDomains.ts file.
  • Secrets Management: All sensitive configuration (API tokens, passwords, secret keys) is managed securely using Pulumi's secret mechanism.
  • Environment Specific Configs: Use the corresponding config file (e.g., Pulumi.test.yaml) for different environments.

For more detailed information, refer to the Pulumi and Azure documentation.

✅ Ensure Pulumi.dev.yaml or your stack YAML doesn’t include plain secrets — right now, you're good.

✅ In your GitHub repo settings, store your PULUMI_ACCESS_TOKEN (one-time setup).

✅ In pulumi.yaml, double-check this line is present:

cli:
  cloud-url: https://api.pulumi.com

✅ Add pulumi login and pulumi stack select in your workflow only if you're not using ESC. If you are, use pulumi up --yes --stack esc://marketforge.

🔄 Add sync-app-files step before pulumi up, so that the Azure File Share has the updated files for Strapi/Vite.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors