Self-Hosting FreeScout with Docker Compose

What Is FreeScout?

FreeScout is an open-source help desk and shared mailbox — think Help Scout or Zendesk, but free and self-hosted. It handles customer email conversations with a shared inbox, collision detection (so two agents don’t reply to the same ticket simultaneously), canned responses, customer satisfaction ratings, and a knowledge base. The module system extends it with Slack integration, LDAP, custom fields, and more.

Built on Laravel, FreeScout is lightweight enough to run on a $4/month VPS. It’s the most popular open-source Help Scout alternative by GitHub stars.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 512 MB RAM minimum
  • 5 GB free disk space
  • A domain name (required for email integration)
  • An SMTP server or relay for outbound email
ResourceSmall Team (<50 agents)Medium Team (50–500)
RAM512 MB1–2 GB
CPU1 core2 cores
Disk5 GB20 GB+ (email attachments grow)

FreeScout is remarkably light. It runs on hardware that would choke most help desk software.

Docker Compose Configuration

FreeScout needs MariaDB for data storage. The tiredofit/freescout community image handles PHP, Nginx, and cron inside a single container.

Create a docker-compose.yml:

services:
  freescout:
    image: tiredofit/freescout:1.17.148
    container_name: freescout
    depends_on:
      freescout-db:
        condition: service_healthy
    environment:
      # Database
      - DB_HOST=freescout-db
      - DB_NAME=freescout
      - DB_USER=freescout
      - DB_PASS=${DB_PASSWORD}

      # Admin account (created on first boot)
      - ADMIN_EMAIL=${ADMIN_EMAIL}
      - ADMIN_PASS=${ADMIN_PASS}
      - ADMIN_FIRST_NAME=Admin
      - ADMIN_LAST_NAME=User

      # Application
      - SITE_URL=https://support.example.com
      - TIMEZONE=UTC
      - ENABLE_AUTO_UPDATE=FALSE    # Pin versions for stability
      - DISPLAY_ERRORS=FALSE
    ports:
      - "8080:80"
    volumes:
      - freescout_data:/data
      - freescout_logs:/www/logs
    restart: unless-stopped

  freescout-db:
    image: mariadb:10.11
    container_name: freescout-db
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
      - MYSQL_DATABASE=freescout
      - MYSQL_USER=freescout
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - freescout_db:/var/lib/mysql
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  freescout_data:
  freescout_logs:
  freescout_db:

Create a .env file:

# Database passwords — change these
DB_PASSWORD=change_me_strong_password
DB_ROOT_PASSWORD=change_me_root_password

# Admin account credentials for first setup
ADMIN_EMAIL=[email protected]
ADMIN_PASS=change_me_admin_password

Start the stack:

docker compose up -d

First boot takes 30–60 seconds while FreeScout runs database migrations and creates the admin account.

Initial Setup

  1. Open https://support.example.com (or http://your-server:8080)
  2. Log in with the ADMIN_EMAIL and ADMIN_PASS from your .env file
  3. Go to Manage → Mailboxes → New Mailbox
  4. Configure your first mailbox:
    • Email Address: [email protected] (the address customers write to)
    • Outgoing Mail: SMTP settings for your email provider
    • Incoming Mail: IMAP settings to fetch customer emails

Email Configuration

FreeScout fetches incoming email via IMAP and sends replies via SMTP. Both must be configured per mailbox.

SettingExampleNotes
IMAP Serverimap.gmail.comYour email provider’s IMAP server
IMAP Port993Usually 993 (SSL) or 143 (STARTTLS)
SMTP Serversmtp.gmail.comYour email provider’s SMTP server
SMTP Port587Usually 587 (STARTTLS) or 465 (SSL)
AuthenticationOAuth2 or App PasswordGmail requires App Password or OAuth2

Tip: For production, use a dedicated SMTP relay like Amazon SES, Mailgun, or your own mail server instead of Gmail. Gmail limits you to 500 sends/day.

Extending with Modules

FreeScout’s power comes from its module ecosystem. Free and paid modules add:

ModuleWhat It DoesFree?
Satisfaction RatingsCustomer satisfaction surveys after ticket resolutionYes
Custom FieldsAdd custom data fields to conversationsPaid
Slack IntegrationNotify Slack channels on new ticketsPaid
LDAPAuthenticate agents via LDAP/Active DirectoryPaid
Knowledge BasePublic-facing help center with searchPaid
Time TrackingTrack time spent per conversationPaid
TagsOrganize conversations with labelsFree

Install modules in Manage → Modules. For the Docker setup, third-party modules go in the /assets/modules volume mount.

Reverse Proxy

FreeScout runs on port 80 inside the container (mapped to 8080 above). For SSL termination, use a reverse proxy.

Caddy:

support.example.com {
    reverse_proxy freescout:80
}

Set SITE_URL in your environment to match the public URL. If you use a reverse proxy, add the proxy’s IP to trusted proxies:

- APP_TRUSTED_PROXIES=172.18.0.0/16    # Your Docker network range

See Reverse Proxy Setup for more options.

Backup

Back up the MariaDB database and the data volume:

# Database backup
docker exec freescout-db mysqldump -u freescout -p${DB_PASSWORD} freescout > freescout-backup.sql

# Data volume (sessions, cache, attachments)
docker run --rm -v freescout_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/freescout-data.tar.gz /data

See Backup Strategy for automated approaches.

Troubleshooting

Emails not being fetched

Symptom: Customer emails don’t appear in FreeScout despite IMAP being configured.

Fix: FreeScout checks IMAP on a cron schedule (every minute by default). Verify cron is running inside the container:

docker exec freescout crontab -l

If no crontab exists, the tiredofit/freescout image handles this automatically — check the container logs for cron errors:

docker logs freescout | grep -i cron

Also verify IMAP credentials work by testing with curl:

curl -v imaps://imap.gmail.com --user "[email protected]:your_app_password"

SMTP sends fail with “Connection refused”

Symptom: Replies aren’t delivered. SMTP errors in logs.

Fix: Many hosting providers block outbound port 25. Try port 587 (STARTTLS) or 465 (SSL/TLS). If those are also blocked, use a third-party SMTP relay.

”Allowed memory size exhausted” error

Symptom: PHP crashes with memory limit error when processing large email threads.

Fix: The default PHP memory limit may be too low for large attachments. The tiredofit/freescout image supports overriding PHP settings. Add to your environment:

- PHP_MEMORY_LIMIT=512M

Collision detection not working

Symptom: Two agents reply to the same ticket without seeing each other’s draft.

Fix: Collision detection requires WebSockets. Ensure your reverse proxy forwards WebSocket connections. In Nginx, add:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Auto-update breaks the install

Symptom: After container restart, FreeScout shows errors or won’t load.

Fix: The ENABLE_AUTO_UPDATE=TRUE default means the container pulls the latest FreeScout code on every restart. Pin the Docker image version AND set ENABLE_AUTO_UPDATE=FALSE for production stability.

Verdict

FreeScout is the best self-hosted help desk for small to medium teams. It covers 90% of what Zendesk and Help Scout offer — shared inbox, collision detection, canned responses, customer satisfaction — at zero cost. The module ecosystem fills gaps for teams that need LDAP, Slack, or custom fields.

Use FreeScout if: You handle customer support via email and want a shared inbox without per-agent pricing. Works well for teams of 1–50 agents.

Look elsewhere if: You need a full ITSM ticketing system with SLA management and asset tracking — Zammad is more full-featured. Or if you need error/crash tracking, see GlitchTip (a Sentry alternative).

Comments