Running Campfire alongside other web services on a VPS

I want to run Campfire (37signals Once) on my VPS with other web services. It's possible with Dokku.

I have Campfire running on my VPS. I added a new web service on that same VPS using Dokku. After that, Campfire would no longer start.

Campfire Once Error
"There is already another web application running on this machine. You can either stop the other application, or install Campfire on its own machine. Note that each ONCE product requires its own machine."

Once wants an exclusive relationship with my VPS. I want to leverage the VPS I'm already running - it has plenty of processing power for my multi-service use case.

It took some Internet sleuthing, some LLM help, and lots of trial and error, but I finally got it working. The rest of this post documents the steps that worked.

NOTE: I'm pretty new to Dokku and kind of stumbled into this solution. I'm writing it down for future me to reference if I need. If there are better ways to do any of the steps below I'd love to know!

The Problem & Solution

The once CLI expects to bind directly to ports 80 and 443, but Dokku's nginx reverse proxy is already using those ports.

The solution? Deploy Campfire as a regular Dokku app and let Dokku's nginx handle the proxying.

Step 1: Create the Dokku app

# Create the Dokku app
dokku apps:create campfire

# Mount your existing data volume (if you have Campfire data from a previous installation)
dokku storage:mount campfire /var/lib/docker/volumes/campfire-data/_data:/rails/storage

# Set required environment variables (use your Once token as the SECRET_KEY_BASE)
# Replace your-once-token-here with the token you received when purchasing Campfire
dokku config:set campfire SECRET_KEY_BASE="your-once-token-here"

# Deploy the Campfire Docker image
dokku git:from-image campfire registry.once.com/campfire:latest

# Set your domain
dokku domains:set campfire chat.yourdomain.com

# Enable SSL with Let's Encrypt
dokku letsencrypt:enable campfire

Step 2: Fix the Nginx Port Mapping

Here's where it gets tricky. The Campfire container has an internal reverse proxy (Thruster) on port 80 that forwards to Puma on port 3000. But Dokku's nginx config generator gets confused by the container's exposed ports and tries to proxy to port 443 (where nothing is listening).

We need to fix the nginx config to proxy to port 80 instead of 443.

Create the Fix Script

cat > /usr/local/bin/fix-campfire-nginx << 'EOF'
#!/bin/bash
sed -i 's/proxy_pass  http:\/\/campfire-443;/proxy_pass  http:\/\/campfire-80;/' /home/dokku/campfire/nginx.conf
nginx -t && systemctl reload nginx
EOF

chmod +x /usr/local/bin/fix-campfire-nginx

Run it Now

fix-campfire-nginx

Your Campfire should now be working at https://chat.yourdomain.com!

Step 3: Automate the Fix with a Dokku Plugin

To make sure the nginx config gets fixed automatically on every deploy, we'll create a custom Dokku plugin:

# Create the plugin directory
mkdir -p /var/lib/dokku/plugins/available/zzz-campfire-fix

# Create the plugin metadata
cat > /var/lib/dokku/plugins/available/zzz-campfire-fix/plugin.toml << 'EOF'
[plugin]
description = "Fix campfire nginx proxy configuration"
version = "0.1.0"
[plugin.config]
EOF

# Create the proxy-build-config hook
cat > /var/lib/dokku/plugins/available/zzz-campfire-fix/proxy-build-config << 'EOF'
#!/usr/bin/env bash
set -eo pipefail

APP="$1"

if [[ "$APP" == "campfire" ]] && [[ -f /home/dokku/campfire/nginx.conf ]]; then
    echo "-----> Fixing campfire nginx config..."
    sed -i 's/proxy_pass  http:\/\/campfire-443;/proxy_pass  http:\/\/campfire-80;/' /home/dokku/campfire/nginx.conf 2>/dev/null || true
    sudo nginx -t && sudo systemctl reload nginx
    echo "-----> Campfire nginx config fixed and reloaded"
fi
EOF

# Make it executable
chmod +x /var/lib/dokku/plugins/available/zzz-campfire-fix/proxy-build-config

# Create the core-post-deploy hook
cat > /var/lib/dokku/plugins/available/zzz-campfire-fix/core-post-deploy << 'EOF'
#!/usr/bin/env bash
set -eo pipefail

APP="$1"

if [[ "$APP" == "campfire" ]]; then
    echo "-----> Fixing campfire nginx config..."
    sed -i 's/proxy_pass  http:\/\/campfire-443;/proxy_pass  http:\/\/campfire-80;/' /home/dokku/campfire/nginx.conf 2>/dev/null || true
    sudo nginx -t && sudo systemctl reload nginx
    echo "-----> Campfire nginx config fixed and reloaded"
fi
EOF

chmod +x /var/lib/dokku/plugins/available/zzz-campfire-fix/core-post-deploy

# Enable the plugin
dokku plugin:enable zzz-campfire-fix

Note: The plugin is named zzz-campfire-fix (with "zzz-" prefix) to ensure it loads alphabetically after the nginx-vhosts plugin, so our fix runs after the config is generated. It's, uhh, hacky, but it works! 🙃

Step 4: Test the Automation

Verify the plugin works by rebuilding:

dokku ps:rebuild campfire

You should see "Fixing campfire nginx config..." in the output, and your site should continue working after the rebuild.

Understanding Why This Works

The Campfire Docker image has:

Thruster internally forwards requests from port 80 to Puma on port 3000. So by configuring Dokku's nginx to proxy to the container's port 80, we let Thruster handle the internal routing to Puma.

Setting Up Automatic Updates

By default, the once CLI handles automatic updates for Campfire. However, since we're now managing deployment through Dokku, we need to set up our own update mechanism.

Step 1: Disable Once Auto-Updates

once auto-update off

Step 2: Create the Auto-Update Script

cat > /usr/local/bin/campfire-auto-update << 'EOF'
#!/bin/bash
set -e

LOG_FILE="/var/log/campfire-auto-update.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "Checking for Campfire updates..."

# Capture the once update output
UPDATE_OUTPUT=$(once update 2>&1)

log "$UPDATE_OUTPUT"

# Check if an update was applied (look for success indicators)
if echo "$UPDATE_OUTPUT" | grep -q "Already up-to-date"; then
    log "No updates available. Campfire is already on the latest version."
    exit 0
elif echo "$UPDATE_OUTPUT" | grep -qE "(updated|Update complete|new version|Successfully updated)"; then
    log "Update detected! Redeploying Campfire via Dokku..."
    dokku ps:rebuild campfire >> "$LOG_FILE" 2>&1
    log "Campfire successfully redeployed with new version."
else
    log "Update check completed but status unclear. Manual review recommended."
    log "Full output: $UPDATE_OUTPUT"
fi
EOF

chmod +x /usr/local/bin/campfire-auto-update

Step 3: Test the Script

/usr/local/bin/campfire-auto-update

You should see output indicating whether an update is available.

Step 4: Set Up Daily Automatic Updates

Add a cron job to check for updates daily at 3 AM:

(crontab -l 2>/dev/null; echo "0 3 * * * /usr/local/bin/campfire-auto-update") | crontab -

Verify the cron job was added:

crontab -l | grep campfire

Monitoring Updates

Check the update logs anytime:

tail -f /var/log/campfire-auto-update.log

Or manually trigger an update check:

/usr/local/bin/campfire-auto-update

Managing Your Campfire Instance

Now that Campfire runs under Dokku, use these commands to manage it:

# View logs
dokku logs campfire -t

# Restart the app
dokku ps:restart campfire

# Rebuild the app
dokku ps:rebuild campfire

# Check status
dokku ps:report campfire

# View configuration
dokku config:show campfire

The once CLI won't work for starting/stopping anymore, but you have full control through Dokku.

Now I have Campfire happily running on the same VPS as my other projects, all managed through Dokku! 🔥

Hopefully you found this post helpful, if you have any questions or comments you can reach me via electronic mail.

HTML Input Validation is (maybe) Good