Applications Azure

Campfire on Ubuntu 24.04 on Azure User Guide

| Product: Campfire on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Campfire on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Campfire is a web based chat application for teams. It gives a team the things it actually needs to talk all day: multiple rooms with access controls so a conversation can be open to everyone or closed to a few, one to one direct messages, file attachments with inline previews, @mentions that notify the right person, and full text search across the whole history so nothing said last month is lost. Web Push notifications reach people who have the tab closed, and a simple bot API lets scripts and services post into a room.

The cloudimg image ships the free and open source, MIT licensed Campfire from the official upstream container image, pinned by both tag and digest so the deployment can never silently float to a different build. Campfire is deliberately a single machine product: one instance holds the whole team's conversation, and all of it, including every uploaded file, stays inside your own Azure subscription. Nothing here ships with a known credential and, just as importantly, nothing ships claimable: a fresh Campfire database leaves the first run wizard open to whoever reaches the machine first, so on first boot this image creates the single administrator itself with a random per instance password, which is what closes that wizard before the instance is reachable. Every VM also generates its own application secret and Web Push key pair. Backed by 24/7 cloudimg support.

Campfire, ONCE and 37signals are trademarks of 37signals, LLC. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by 37signals, LLC. It ships the free and open source MIT licensed self hosted software, unmodified.

The docker, campfire-firstboot and campfire services all reporting active, and the campfire container running from the digest pinned image with port 80 published

What is included:

  • Campfire 1.4.5 (MIT licensed, from 37signals) deployed from the official upstream container image, pinned by tag and digest
  • A complete single machine stack inside that image: Thruster in front of Puma running Rails 8 on Ruby 3.4.5, a bundled Redis for live updates and caching, and background job workers
  • The web app published on port 80; all state, the SQLite database and every uploaded attachment, held in one Docker volume
  • campfire.service and campfire-firstboot.service as systemd units, enabled and active on boot
  • A unique application secret, a unique Web Push key pair and a single administrator account with a random password, all generated per VM on first boot and never baked into the image
  • No default login, no shipped secret, and no open first run wizard for a passer by to claim
  • Ubuntu 24.04 LTS base with latest security patches applied at build time
  • Azure Linux Agent for seamless cloud integration and SSH key injection
  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region
  • Subscription to the Campfire listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point for a small team. For a larger team or heavy attachment traffic use Standard_B2ms or Standard_D2s_v5. NSG inbound: allow 22/tcp from your management network and 80/tcp for the web app from the networks that use it.

Step 1: Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Campfire by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.

Step 2: Deploy from the Azure CLI

RG="campfire-prod"; LOCATION="eastus"; VM_NAME="campfire-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/campfire-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name cf-vnet --address-prefix 10.100.0.0/16 --subnet-name cf-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name cf-nsg
az network nsg rule create -g "$RG" --nsg-name cf-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name cf-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name cf-vnet --subnet cf-subnet --nsg cf-nsg --public-ip-sku Standard

Step 3: Connect to your VM

ssh azureuser@<vm-ip>

Step 4: Confirm the services are running

Campfire runs as one container under campfire.service. That single container is a complete deployment in itself: Thruster fronts Puma running the Rails application, a bundled Redis carries live updates and the cache, and background workers deliver notifications. Only port 80 is published. Confirm the services are active and see the container:

sudo systemctl is-active docker campfire-firstboot campfire
sudo docker compose -f /var/lib/campfire/docker-compose.yml ps

All three services report active and the campfire container reports Up, with 0.0.0.0:80->80/tcp published. campfire-firstboot.service is a one shot unit that has already finished; it stays active because it remains after exit, and it will not run again on this VM.

Step 5: Read the per instance credentials

On this VM's first boot, before the application was reachable, a unique application secret and Web Push key pair were generated, and a single administrator account was created with a random password and written to a root only file. Read it:

sudo cat /root/campfire-credentials.txt
sudo stat -c '%a %U:%G %n' /root/campfire-credentials.txt

The file (mode 0600 root:root) holds CAMPFIRE_ADMIN_EMAIL and CAMPFIRE_ADMIN_PASSWORD, the account you sign in to the web app with, plus the app url. None of these ship in the image; every value is unique to this VM, so no two instances share a credential and there is no default login to change.

The campfire-credentials.txt file at mode 0600 root root holding the per VM administrator email address and password, the password masked, alongside the application URL

Step 6: Verify the security model

Campfire's health endpoint is deliberately open so a load balancer can probe it, but every other route requires a signed in session, and the first run wizard that would let a visitor claim the instance is already closed because first boot created the administrator. Confirm all four:

B=http://localhost
curl -s -o /dev/null -w 'GET  /up                    (no auth): %{http_code}\n' $B/up
curl -s -o /dev/null -w 'GET  /first_run             (no auth): %{http_code}\n' $B/first_run
curl -s -o /dev/null -w 'GET  /rooms/1               (no auth): %{http_code}\n' $B/rooms/1
curl -s -o /dev/null -w 'POST /rooms/1/messages      (no auth): %{http_code}\n' -X POST $B/rooms/1/messages

/up returns 200. /first_run returns 302: the wizard redirects away because an account already exists, so nobody can claim this instance. Reading a room and posting into a room both return 302 to the sign in page for an anonymous visitor. That is the security model in four lines: one open health probe, everything else behind a session.

curl output showing the health endpoint returning 200 while the first run wizard, the room and the message endpoint all return 302 to sign in for an anonymous visitor

Step 7: Sign in from the command line

Prove the per instance credential authenticates before you open a browser. Campfire uses Rails' per form CSRF tokens, so take the session wide token from the page's csrf-token meta tag:

B=http://localhost
J=$(mktemp)
TOK=$(curl -s -c $J -b $J $B/session/new | grep -oE 'csrf-token" content="[^"]+"' | head -1 | sed -E 's/.*content="([^"]+)".*/\1/')
curl -s -c $J -b $J -o /dev/null -w 'sign in (per VM admin): %{http_code}\n' \
  --data-urlencode "authenticity_token=$TOK" \
  --data-urlencode "email_address=<CAMPFIRE_ADMIN_EMAIL>" \
  --data-urlencode "password=<CAMPFIRE_ADMIN_PASSWORD>" \
  -X POST $B/session
curl -s -b $J -c $J -o /dev/null -w 'landing page (session):  %{http_code}\n' $B/
rm -f $J

The sign in returns 302 (Campfire redirects you into your last room) and the landing page then returns 302 as well, redirecting to the room you belong to. A wrong password returns 401 instead.

Step 8: Open the web app and sign in

Browse to http://<vm-ip>/ to reach Campfire. Sign in with the email address and password from Step 5. The email address is shown on the sign in page by upstream design, so that anyone who forgets their password knows who to ask.

The Campfire sign in page with the Campfire wordmark, an email address field, a password field and a submit arrow, with the administrator email address shown below the card

Step 9: Talk in a room

You land in the first room, called All Talk, which every member of the account belongs to automatically. Type in the composer at the bottom and press enter. Messages appear live for everyone in the room without a refresh, the paperclip button attaches a file, and the A button turns on rich text. The right hand rail lists everyone in the account at the top and every room below it, and the + button creates a new room.

The All Talk room showing a live multi person conversation with avatars and timestamps, the room list and member list in the right hand rail, and the message composer with attachment and formatting controls

Step 10: Invite your team and manage the account

Open the settings gear in the bottom right corner to reach Account settings. From here you rename the account, upload a logo, decide whether ordinary members may create rooms, and copy the invite link. Anyone who opens that link creates their own account on this instance, so treat it like a password and regenerate it with the refresh button if it leaks. The member list below lets you promote someone to administrator with the crown button, or remove them.

The Campfire account settings page showing the account name, a toggle for whether creating rooms requires an administrator, the shareable invite link with QR and copy buttons, and the member list with role controls

Step 11: Search the whole history

Click the magnifying glass to the left of the composer and type. Campfire searches every message you can see across every room, and each hit shows the author, the time and the room it came from, so you can jump straight to it.

Campfire search results for the word release, showing two matching messages with their authors, timestamps and the rooms they were posted in

Step 12: Confirm which build you are running

The image reference is pinned by tag and by digest, so an upgrade is always something you choose rather than something that happens to you. Confirm what is actually running:

sudo docker compose -f /var/lib/campfire/docker-compose.yml images
sudo docker image inspect --format '{{index .RepoDigests 0}}' \
  "$(sudo docker compose -f /var/lib/campfire/docker-compose.yml images -q | head -1)"

Both the tag and the sha256: digest are shown. To move to a newer upstream release, edit the image: line in /var/lib/campfire/docker-compose.yml to the tag and digest you want, then restart the service. Campfire runs any pending database migrations automatically when the container starts.

docker compose images and docker image inspect showing the Campfire container running from the 1.4.5 tag together with its sha256 content digest

Step 13: Serve HTTPS with your own domain

The image ships plain HTTP on port 80 so the VM is reachable on its IP address the moment it boots. Campfire can obtain and renew its own Let's Encrypt certificate instead. Point a DNS name at the VM, open port 443 in the network security group, then edit /var/lib/campfire/.env: remove the DISABLE_SSL line, add your domain, publish 443 in the compose file, and restart.

# /var/lib/campfire/.env
# remove this line:
DISABLE_SSL=true
# and add:
TLS_DOMAIN=chat.example.com

# /var/lib/campfire/docker-compose.yml — publish 443 alongside 80:
    ports:
      - "80:80"
      - "443:443"

# then:
sudo systemctl restart campfire.service

Leave DISABLE_SSL=true in place if you terminate TLS in something in front of the VM, such as an Azure Application Gateway or your own reverse proxy. Removing it without a resolvable TLS_DOMAIN will make the app redirect every plain HTTP request to HTTPS and it will stop answering on its IP address.

Step 14: Back up your conversation

Everything Campfire keeps lives in one Docker volume: the SQLite database and every uploaded file. Because the database can be written to at any moment, take a consistent snapshot first rather than copying the file directly:

sudo docker exec campfire script/admin/prepare-backup
sudo docker exec campfire ls -la storage/backups

That writes a consistent snapshot into storage/backups inside the volume. Archive the whole volume to a file on the host and copy it somewhere safe, ideally off the machine, on whatever schedule suits you. Azure disk snapshots of the OS disk are a reasonable complement, but take the database snapshot first so the copy is consistent.

Troubleshooting

The web app does not answer. Check the container and the last log lines:

sudo systemctl status campfire.service --no-pager | head -20
sudo docker compose -f /var/lib/campfire/docker-compose.yml logs --tail 40 campfire

You lost the administrator password. Reset it from inside the container, replacing the address and the new password with your own:

sudo docker exec campfire script/admin/reset-password admin@example.com 'a-new-strong-password'

Everything redirects to https and the site will not load on the IP. DISABLE_SSL has been removed from /var/lib/campfire/.env without a working TLS_DOMAIN. Put it back and restart the service.

Support

cloudimg provides 24/7 support with a guaranteed 24 hour response SLA for this image. Contact us at support@cloudimg.co.uk or visit www.cloudimg.co.uk.