Elixir with Phoenix on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Elixir with Phoenix on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Elixir is a dynamic, functional language that runs on the Erlang virtual machine (the BEAM), and Phoenix is its productive, real-time web framework — together the stack behind fault-tolerant, massively concurrent web applications and WebSocket channels at scale.
The image installs Elixir 1.20.2 and Phoenix 1.8.9 on Erlang/OTP 28.5, all pinned by exact version and verified against sha256 checksums (Erlang/OTP is compiled from the official source release; Elixir is the official precompiled build). Because Phoenix is a web framework, this image ships a genuinely browsable web UI: a demo Phoenix application is pre-deployed as a production Mix release — the self-contained BEAM artifact that bundles its own Erlang runtime — and served behind nginx on port 80. You can point a browser at the VM and see the standard Phoenix welcome page, a live JSON status route, and Phoenix LiveDashboard, the real-time BEAM observability console.
What is included:
-
Elixir 1.20.2 (Apache-2.0) and Phoenix 1.8.9 (MIT), pinned and sha256-verified
-
Erlang/OTP 28.5.0.3 compiled from the official source release, installed to
/usr/local -
elixir,iexandmixon the default system PATH for every user -
A demo Phoenix app built as a production Mix release and deployed to
/var/lib/phoenix-demo/rel/demo -
phoenix-demo.servicesystemd unit auto-starting on boot, running as the unprivilegedphoenix:phoenixsystem user -
elixir-phoenix-firstboot.servicesystemd oneshot that generates a per-VM secret, starts the release + nginx and confirms the app answers before completing -
Phoenix endpoint bound to loopback only (
127.0.0.1:4000) — nginx fronts it on:80with WebSocket upgrade for LiveView -
Unauthenticated
/healthzendpoint (nginx-native, HTTP 200) for load balancer / probe checks -
The standard Phoenix welcome landing page at
/ -
A live JSON status route at
/api/status— server timestamp, Elixir/OTP/Phoenix versions and a concurrency-safe request counter (proof the BEAM executes code per request) -
Phoenix LiveDashboard at
/dashboard— live BEAM memory, processes, ETS and run-queues over a LiveView WebSocket -
The Mix release and runtime data on a dedicated 20 GiB Azure data disk at
/var/lib/phoenix-demo, independent of the OS disk and independently resizable -
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 Elixir with Phoenix listing on Azure Marketplace
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development. Production Phoenix workloads should use Standard_D4s_v5 (4 vCPU, 16 GB RAM) or larger.
Step 1: Deploy from the Azure Portal
Search Elixir with Phoenix in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin), TCP 80 (HTTP), TCP 443 (reserved for TLS) from your client networks.
Step 2: Deploy from the Azure CLI
RG="phoenix-prod"; LOCATION="eastus"; VM_NAME="phoenix-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/elixir-phoenix-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 phoenix-vnet --address-prefix 10.100.0.0/16 --subnet-name phoenix-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name phoenix-nsg
az network nsg rule create -g "$RG" --nsg-name phoenix-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 phoenix-nsg --name allow-http --priority 110 \
--destination-port-ranges 80 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name phoenix-nsg --name allow-https --priority 120 \
--destination-port-ranges 443 --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 phoenix-vnet --subnet phoenix-subnet --nsg phoenix-nsg --public-ip-sku Standard
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
Step 4: Verify the Phoenix and nginx Services
Confirm the demo release and nginx are both running, that Phoenix is bound to loopback only, that the health endpoint answers, and that the data disk is mounted.
for s in phoenix-demo.service nginx.service elixir-phoenix-firstboot.service; do
printf ' %-34s %s\n' "$s" "$(systemctl is-active $s)"
done
ss -tlnH | awk '{print $4}' | grep -E ':4000$|:80$' | sort -u
curl -s -o /dev/null -w 'GET /healthz -> HTTP %{http_code}\n' http://127.0.0.1/healthz
findmnt -no SOURCE,TARGET,FSTYPE,SIZE /var/lib/phoenix-demo

Step 5: Check the Health Endpoint
The /healthz endpoint is served by nginx itself (no round-trip to the BEAM), so it is ideal for Azure Load Balancer health probes.
curl -i http://127.0.0.1/healthz
Step 6: Open the Phoenix Landing Page in a Browser
Browse to http://<vm-public-ip>/. The demo Phoenix application serves the standard Phoenix framework welcome page, rendered by the running release on the BEAM VM.

Step 7: Call the Live Status API
Browse to http://<vm-public-ip>/api/status, or curl it. The route returns live JSON: a server timestamp, the Elixir, Erlang/OTP and Phoenix version numbers, and a request counter held in a supervised process. Reload it and the counter increments and the timestamp advances — proof the BEAM VM is executing code per request, not serving a static file.
curl -s http://127.0.0.1/api/status
echo
curl -s http://127.0.0.1/api/status


Step 8: Explore Phoenix LiveDashboard
Browse to http://<vm-public-ip>/dashboard. Phoenix LiveDashboard is the real-time observability console for the BEAM VM, updating live over a LiveView WebSocket. The Home page shows the Erlang/OTP, Elixir and Phoenix versions, atom and process counts, memory breakdown and run-queue depth.

The Processes page lists the live Erlang processes inside the BEAM VM with their memory, reduction count, message-queue length and current function, refreshing in real time.

LiveDashboard is exposed unauthenticated as a demo/showcase surface. Before production use, restrict it (see Step 13) — put it behind authentication or remove the route.
Step 9: Confirm the Toolchain and Deployed Release
The Elixir toolchain is on the system PATH, and the demo app is a self-contained production Mix release that bundles its own Erlang runtime.
elixir --version
ls -1 /var/lib/phoenix-demo/rel/demo/
test -x /var/lib/phoenix-demo/rel/demo/bin/demo && echo "release binary present"

Step 10: Read the Endpoint Notes
First boot writes a non-secret notes file documenting the demo URLs and how to deploy your own release.
cat /var/lib/cloudimg/elixir-phoenix-endpoints.notes

Step 11: Build and Deploy Your Own Phoenix Release
The image is a full Elixir/Phoenix build environment. To ship your own application, generate it, build a production release, and point the systemd unit at it. Because a Mix release bundles its own runtime, nothing else is needed at runtime.
# On a build host (or this VM), generate and build a release, then browse http://<vm-ip>/ :
mix phx.new myapp
cd myapp
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix release
# Deploy it onto the data disk and repoint the service:
sudo systemctl stop phoenix-demo.service
sudo cp -a _build/prod/rel/myapp /var/lib/phoenix-demo/rel/myapp
sudo chown -R phoenix:phoenix /var/lib/phoenix-demo/rel/myapp
sudo sed -i 's#/rel/demo#/rel/myapp#g; s#/bin/demo#/bin/myapp#g' /etc/systemd/system/phoenix-demo.service
sudo systemctl daemon-reload
sudo systemctl start phoenix-demo.service
The per-VM SECRET_KEY_BASE, PHX_HOST and PORT live in /var/lib/phoenix-demo/demo.env (read by the systemd unit as an EnvironmentFile).
Step 12: Managing the Phoenix Service
sudo systemctl status phoenix-demo.service --no-pager
sudo systemctl restart phoenix-demo.service
sudo journalctl -u phoenix-demo.service -n 50 --no-pager
Use sudo journalctl -u phoenix-demo.service -f to follow the logs live, and sudo -u phoenix /var/lib/phoenix-demo/rel/demo/bin/demo remote to attach a remote IEx shell to the running node.
Step 13: Security Recommendations
-
Restrict or remove LiveDashboard. The
/dashboardroute is unauthenticated in this demo image. For production, put it behind authentication (a Plug that checks a basic-auth header or an admin session) or remove thelive_dashboardroute from your router. -
Add TLS. Terminate HTTPS at nginx (port 443) with your certificate, or front the VM with Azure Application Gateway. Port 443 is reserved in the NSG for this.
-
Lock down the NSG. Restrict TCP 22 to your management CIDR, and expose TCP 80/443 only to the clients or load balancer that need them.
-
Keep the OS patched. Unattended security upgrades are enabled by default.
-
Rotate the Phoenix secret if you clone the VM: delete
/var/lib/phoenix-demo/demo.envand restartelixir-phoenix-firstboot.serviceto regenerateSECRET_KEY_BASE.
Support
cloudimg provides 24/7 commercial support with a guaranteed 24 hour response SLA, separate from the upstream open-source projects. Contact support@cloudimg.co.uk.
Elixir is licensed under Apache-2.0 and Phoenix under the MIT license — free and open source with no per-CPU or per-deployment fee. The cloudimg charge covers packaging, security patching, image maintenance and support.