Backend Framework Guide

    Deploy Phoenix Framework

    Build scalable, fault-tolerant real-time applications with Elixir and the BEAM VM. Deploy production-ready on RamNode's VPS hosting.

    Elixir 1.17
    Erlang/OTP 27
    PostgreSQL
    Nginx
    Let's Encrypt SSL
    1

    Prerequisites

    Phoenix is a high-performance web framework built on Elixir and the Erlang VM (BEAM), designed for building scalable, fault-tolerant real-time applications. Its lightweight process model and built-in WebSocket support via Phoenix Channels make it ideal for chat apps, live dashboards, IoT backends, and low-latency APIs.

    • A RamNode KVM VPS with Ubuntu 24.04 LTS installed
    • Root SSH access to your server
    • A registered domain name with DNS pointed to your VPS IP address
    • A Phoenix application ready for deployment (Phoenix 1.7+)
    • Basic familiarity with Linux command line and Elixir/Phoenix development

    💡 Recommended VPS Plan

    A RamNode KVM 2GB plan ($10/month) provides ample resources for most Phoenix applications. The BEAM VM is highly memory-efficient — a typical Phoenix app serves thousands of concurrent connections on 1–2 GB of RAM. For high-traffic or LiveView-heavy applications, consider the 4GB plan.

    Use CasePlanRAMBest For
    Small API / personal project$5/mo1 GBREST/GraphQL APIs, small LiveView apps
    Production web app$20/mo4 GBFull-stack Phoenix with LiveView
    High-concurrency realtime$40/mo+8 GB+Chat, IoT, high-traffic Channels
    2

    Initial Server Setup

    Update system packages
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y build-essential git curl wget \
      autoconf m4 libncurses-dev libssl-dev \
      libwxgtk3.2-dev libgl1-mesa-dev libglu1-mesa-dev \
      libpng-dev libssh-dev unixodbc-dev xsltproc fop

    Create a Deploy User

    Avoid running your application as root. Create a dedicated deploy user with sudo privileges.

    Create deploy user
    sudo adduser deploy
    sudo usermod -aG sudo deploy
    sudo su - deploy

    Configure the Firewall

    Enable UFW
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable
    sudo ufw status
    3

    Install Erlang and Elixir

    We recommend installing Erlang and Elixir via asdf version manager for flexible version management in production.

    Install asdf

    Install asdf version manager
    git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.1
    echo ". $HOME/.asdf/asdf.sh" >> ~/.bashrc
    echo ". $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc
    source ~/.bashrc

    Install Erlang

    Build and install Erlang/OTP 27
    asdf plugin add erlang
    asdf install erlang 27.2
    asdf global erlang 27.2

    ⚠️ Note: Erlang compilation takes 10–15 minutes on a 2-core VPS. This is normal.

    Install Elixir

    Install Elixir 1.17
    asdf plugin add elixir
    asdf install elixir 1.17.3-otp-27
    asdf global elixir 1.17.3-otp-27

    Verify Installation

    Check versions
    elixir --version
    # Expected output:
    # Erlang/OTP 27 [erts-15.x]
    # Elixir 1.17.3 (compiled with Erlang/OTP 27)
    4

    Install Node.js

    Phoenix uses esbuild by default for asset bundling, but Node.js is still needed for certain dependencies and tooling.

    Install Node.js 22 via asdf
    asdf plugin add nodejs
    asdf install nodejs 22.12.0
    asdf global nodejs 22.12.0
    node --version
    5

    Install and Configure PostgreSQL

    PostgreSQL is the recommended database for Phoenix applications.

    Install PostgreSQL
    sudo apt install -y postgresql postgresql-contrib
    sudo systemctl enable postgresql
    sudo systemctl start postgresql

    Create Database and User

    Configure PostgreSQL
    sudo -u postgres psql
    CREATE USER myapp WITH PASSWORD 'your_secure_password';
    CREATE DATABASE myapp_prod OWNER myapp;
    GRANT ALL PRIVILEGES ON DATABASE myapp_prod TO myapp;
    \q

    🔒 Security Note: Use a strong, randomly generated password. Generate one with: openssl rand -base64 32

    6

    Build Your Phoenix Release

    Phoenix releases are self-contained packages that include the Erlang runtime, your compiled application, and all dependencies. This is the recommended approach for production.

    Clone Your Application

    Clone and configure
    cd /home/deploy
    git clone https://github.com/youruser/myapp.git
    cd myapp

    Set Environment Variables

    Create /home/deploy/.env.prod
    export DATABASE_URL="ecto://myapp:your_secure_password@localhost/myapp_prod"
    export SECRET_KEY_BASE="$(mix phx.gen.secret)"
    export PHX_HOST="yourdomain.com"
    export PHX_SERVER=true
    export PORT=4000
    export MIX_ENV=prod
    Secure and load env file
    chmod 600 /home/deploy/.env.prod
    source /home/deploy/.env.prod

    Compile and Build the Release

    Build production release
    export MIX_ENV=prod
    
    # Install dependencies
    mix local.hex --force
    mix local.rebar --force
    mix deps.get --only prod
    
    # Compile
    mix compile
    
    # Build assets
    mix assets.deploy
    
    # Build the release
    mix release

    Run Database Migrations

    Execute migrations
    _build/prod/rel/myapp/bin/myapp eval \
      "MyApp.Release.migrate()"
    7

    Configure systemd Service

    Use systemd to manage your Phoenix application as a service, ensuring it starts on boot and restarts on failure.

    Create /etc/systemd/system/myapp.service
    [Unit]
    Description=MyApp Phoenix Application
    After=network.target postgresql.service
    Requires=postgresql.service
    
    [Service]
    Type=exec
    User=deploy
    Group=deploy
    WorkingDirectory=/home/deploy/myapp
    EnvironmentFile=/home/deploy/.env.prod
    ExecStart=/home/deploy/myapp/_build/prod/rel/myapp/bin/server
    ExecStop=/home/deploy/myapp/_build/prod/rel/myapp/bin/myapp stop
    Restart=on-failure
    RestartSec=5
    SyslogIdentifier=myapp
    RemainAfterExit=no
    
    # Hardening
    NoNewPrivileges=true
    ProtectSystem=full
    ProtectHome=read-only
    
    [Install]
    WantedBy=multi-user.target
    Enable and start the service
    sudo systemctl daemon-reload
    sudo systemctl enable myapp
    sudo systemctl start myapp
    sudo systemctl status myapp
    8

    Nginx Reverse Proxy

    Nginx serves as a reverse proxy in front of your Phoenix application, handling SSL termination, static file serving, and WebSocket upgrades for Phoenix Channels and LiveView.

    Install Nginx
    sudo apt install -y nginx
    sudo systemctl enable nginx
    Create /etc/nginx/sites-available/myapp
    upstream phoenix {
        server 127.0.0.1:4000;
    }
    
    server {
        listen 80;
        server_name yourdomain.com;
    
        location / {
            proxy_pass http://phoenix;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
    
            # WebSocket support (Channels & LiveView)
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 86400;
        }
    
        # Static files (served by Nginx)
        location /assets/ {
            alias /home/deploy/myapp/priv/static/assets/;
            gzip_static on;
            expires max;
            add_header Cache-Control public;
        }
    }
    Enable site and test config
    sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
    sudo rm /etc/nginx/sites-enabled/default
    sudo nginx -t
    sudo systemctl reload nginx
    9

    SSL with Let's Encrypt

    Install Certbot and obtain certificate
    sudo apt install -y certbot python3-certbot-nginx
    sudo certbot --nginx -d yourdomain.com
    
    # Verify auto-renewal is configured
    sudo certbot renew --dry-run

    Certbot automatically modifies your Nginx configuration to enable HTTPS and set up a redirect from HTTP. Certificates auto-renew via a systemd timer.

    10

    Automated Deployment Script

    Create /home/deploy/deploy.sh
    #!/bin/bash
    set -e
    
    APP_DIR=/home/deploy/myapp
    source /home/deploy/.env.prod
    
    echo "==> Pulling latest code..."
    cd $APP_DIR
    git pull origin main
    
    echo "==> Installing dependencies..."
    mix deps.get --only prod
    
    echo "==> Compiling..."
    mix compile
    
    echo "==> Building assets..."
    mix assets.deploy
    
    echo "==> Building release..."
    mix release --overwrite
    
    echo "==> Running migrations..."
    _build/prod/rel/myapp/bin/myapp eval 'MyApp.Release.migrate()'
    
    echo "==> Restarting service..."
    sudo systemctl restart myapp
    
    echo "==> Deployment complete!"
    Make executable and run
    chmod +x /home/deploy/deploy.sh
    
    # Run whenever you push updates
    /home/deploy/deploy.sh
    11

    Monitoring & Maintenance

    Application Logs

    View logs
    # View live logs
    sudo journalctl -u myapp -f
    
    # View last 100 lines
    sudo journalctl -u myapp -n 100 --no-pager

    BEAM Remote Console

    Elixir releases include a remote console for live debugging and inspection:

    Connect to running app
    _build/prod/rel/myapp/bin/myapp remote
    
    # Inside the console:
    iex> :observer_cli.start()  # if observer_cli is a dependency
    iex> Process.list() |> length()

    Health Check Endpoint

    Add a health check route for uptime monitoring:

    lib/myapp_web/controllers/health_controller.ex
    defmodule MyAppWeb.HealthController do
      use MyAppWeb, :controller
      def index(conn, _params), do: json(conn, %{status: "ok"})
    end
    
    # In your router:
    # get "/health", HealthController, :index

    BEAM VM Configuration

    Add to rel/vm.args.eex to optimize the BEAM VM for your VPS:

    rel/vm.args.eex
    # Scheduler configuration
    +S 2:2            # Match your VPS CPU cores
    +sbt db           # Bind schedulers to CPUs
    +swt very_low     # Low scheduler wakeup threshold
    
    # Memory
    +MBas aobf        # Allocation strategy
    +MBlmbcs 512      # Largest multi-block carrier size
    
    # Network
    +A 64             # Async thread pool size

    PostgreSQL Tuning (2GB VPS)

    ParameterDefaultRecommendedPurpose
    shared_buffers128MB512MBShared memory for caching
    effective_cache_size4GB1GBQuery planner memory estimate
    work_mem4MB8MBPer-operation sort/hash memory
    maintenance_work_mem64MB128MBMaintenance operations memory
    max_connections10050Match Ecto pool size + overhead
    12

    Troubleshooting

    App won't start

    Run sudo journalctl -u myapp -n 50 to check logs. Verify env vars in .env.prod — ensure DATABASE_URL and SECRET_KEY_BASE are set correctly.

    502 Bad Gateway

    Run curl http://localhost:4000 to confirm the Phoenix app is running and bound to port 4000. Check systemd status with sudo systemctl status myapp.

    WebSocket errors

    Check browser dev console. Verify Nginx proxy_set_header Upgrade and Connection headers are set in the site config.

    Database connection refused

    Run sudo systemctl status postgresql. Check pg_hba.conf allows local connections and verify credentials match .env.prod.

    Asset 404 errors

    Run ls priv/static/assets/ to verify assets exist. Run mix assets.deploy before building the release. Check the Nginx alias path matches.

    High memory usage

    Use :erlang.memory() in the remote console. Tune pool_size in your Repo config. Check for process leaks with Process.list().

    Phoenix Application Deployed Successfully!

    Your Phoenix application is now running in production on a RamNode VPS with PostgreSQL, Nginx reverse proxy, SSL encryption, and automated deployments.

    Next Steps:

    • Set up CI/CD with GitHub Actions for automated deployments
    • Configure log rotation with logrotate
    • Add APM with AppSignal, New Relic, or Prometheus + Grafana
    • Implement database backups with pg_dump on a cron schedule
    • Explore Phoenix LiveView for real-time UI without JavaScript
    • Scale horizontally with multiple VPS nodes and libcluster for distributed Erlang