Skip to main content

Elixir 1.19.5 with Erlang/OTP 28 (CentOS Stream 9) AMI Administrator Guide

1. Quick Start Information

Connection Methods:

  • Access the instance via SSH using the ec2-user user. Use sudo to run commands requiring root privileges. To switch to the root user, use sudo su - root.

Install Information:

  • OS: CentOS Stream 9
  • Erlang/OTP version: 28.3.1
  • Elixir version: 1.19.5
  • Phoenix Framework: v1.8.3
  • Erlang Install Directory: /usr/local/erlang
  • Elixir Install Directory: /usr/local/elixir

Quick Verification Commands:

  • Check Erlang version: erl -version
  • Check Elixir version: elixir --version
  • Launch interactive shell: iex
  • Check Phoenix installer: mix phx.new --version

Development Tools:

  • Erlang Compiler: erlc
  • Elixir Compiler: elixirc
  • Mix Build Tool: mix
  • Interactive Shell: iex
  • Script Runner: elixir and escript

Firewall Configuration:

  • Please allow SSH port 22.
  • For security, it is recommended to limit SSH access to trusted IPs only.

2. Overview

Welcome to this Elixir 1.19.5 with Erlang/OTP 28 AMI. This image is based on CentOS Stream 9 and provides a complete, production-ready development environment for building high-performance, fault-tolerant applications.

This guide explains how to use this AMI and details its internal configuration.

What is Elixir?

Elixir is a dynamic, functional programming language designed for building scalable and maintainable applications. It runs on the Erlang Virtual Machine (BEAM), known for running low-latency, distributed, and fault-tolerant systems.

What is Erlang/OTP?

Erlang/OTP is a programming platform for building massively scalable soft real-time systems with requirements on high availability. OTP (Open Telecom Platform) is a collection of libraries, tools, and design principles for building robust applications.

Core Features of This AMI:

  • Erlang OTP 28.3.1: Compiled from source with JIT (Just-In-Time) compilation for superior performance
  • Elixir 1.19.5: Official precompiled release with full OTP 28 compatibility
  • Phoenix Framework: v1.8.3 installer included for rapid web development
  • OpenSSL 3.5.5: Modern cryptographic support for HTTPS and secure communications
  • Development Tools: Complete toolchain including Mix, Hex, Rebar3
  • Optimized Configuration: Production-ready settings without unnecessary GUI dependencies

Target Use Cases:

  • Building real-time web applications with Phoenix Framework
  • Developing distributed systems and microservices
  • Creating fault-tolerant backend services
  • Prototyping concurrent and parallel applications
  • Learning functional programming patterns

3. First Launch & Verification

Step 1: Connect to Your Instance

  1. Launch your instance in your cloud provider's console (e.g., AWS EC2)
  2. Ensure SSH port 22 is allowed in your security group
  3. Connect via SSH:
    ssh -i your-key.pem ec2-user@YOUR_PUBLIC_IP

Step 2: Verify Erlang Installation

Check the Erlang version:

erl -version

Expected Output:

Erlang (SMP,ASYNC_THREADS) (BEAM) emulator version 16.2

The output confirms:

  • SMP: Symmetric Multi-Processing enabled (uses all CPU cores)
  • ASYNC_THREADS: Asynchronous I/O thread pool enabled
  • BEAM: Erlang Virtual Machine
  • 16.2: ERTS (Erlang Runtime System) version

Step 3: Verify Elixir Installation

Check the Elixir version:

elixir --version

Expected Output:

Erlang/OTP 28 [erts-16.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Elixir 1.19.5 (compiled with Erlang/OTP 28)

Key information:

  • [source]: Compiled from source code (not package manager)
  • [64-bit]: 64-bit architecture
  • [smp:4:4]: 4 schedulers on 4 cores
  • [jit:ns]: JIT compiler enabled (native stack mode)
  • compiled with Erlang/OTP 28: Version compatibility verified

Step 4: Test Interactive Shell

Launch the Elixir interactive shell:

iex

Try some commands:

iex(1)> 100 + 200
300

iex(2)> String.upcase("hello elixir")
"HELLO ELIXIR"

iex(3)> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]

Exit with Ctrl+C twice.

Step 5: Verify Cryptographic Support

Test OpenSSL integration:

elixir -e "IO.inspect :crypto.info()"

Expected Output:

%{
otp_crypto_version: ~c"5.8",
compile_type: :normal,
link_type: :dynamic,
cryptolib_version_compiled: ~c"OpenSSL 3.5.5 27 Jan 2026",
cryptolib_version_linked: ~c"OpenSSL 3.5.5 27 Jan 2026",
fips_provider_available: false
}

This confirms HTTPS and SSL/TLS functionality is fully operational.

Step 6: Verify Phoenix Framework

Check the Phoenix installer:

mix phx.new --version

Expected Output:

Phoenix installer v1.8.3

Step 7: Create and Run a Test Script

Create a simple Elixir script:

echo 'IO.puts "Success! Elixir 1.19.5 is running on OTP 28."' > test.exs

Run it:

elixir test.exs

Expected Output:

Success! Elixir 1.19.5 is running on OTP 28.

Clean up:

rm test.exs

4. Architecture & Detailed Configuration

4.1. Component Locations

ComponentInstallation PathPurpose
Erlang Home/usr/local/erlang/Erlang runtime and libraries
Erlang Binaries/usr/local/erlang/bin/erl, erlc, escript, ct_run
Elixir Home/usr/local/elixir/Elixir language and standard library
Elixir Binaries/usr/local/elixir/bin/elixir, elixirc, iex, mix
Mix Archives~/.mix/Hex package manager and Phoenix generator
System Symlinks/usr/bin/Global command aliases

All executables are linked to /usr/bin/ for global access:

Erlang Symlinks:

/usr/bin/erl -> /usr/local/erlang/bin/erl
/usr/bin/erlc -> /usr/local/erlang/bin/erlc
/usr/bin/escript -> /usr/local/erlang/bin/escript
/usr/bin/ct_run -> /usr/local/erlang/bin/ct_run

Elixir Symlinks:

/usr/bin/elixir -> /usr/local/elixir/bin/elixir
/usr/bin/elixirc -> /usr/local/elixir/bin/elixirc
/usr/bin/iex -> /usr/local/elixir/bin/iex
/usr/bin/mix -> /usr/local/elixir/bin/mix

This allows you to run commands from any directory without setting PATH.

4.3. Erlang OTP Compilation Configuration

Erlang was compiled from source with the following options:

./configure --prefix=/usr/local/erlang --without-javac --without-wx

Configuration Flags Explained:

  • --prefix=/usr/local/erlang: Install location
  • --without-javac: Exclude Java support (not needed for server applications)
  • --without-wx: Exclude wxWidgets GUI (reduces dependencies on headless servers)

Why Compile from Source?

  1. JIT Optimization: Enables Just-In-Time compilation for 20-50% performance improvement
  2. Custom Configuration: Fine-tuned for server environments
  3. Latest Version: Get OTP 28 features immediately (not available in CentOS repos)
  4. Production Quality: Same build process used by Erlang Solutions and WhatsApp

4.4. Elixir Installation Method

Elixir uses the official precompiled package specifically built for OTP 28:

  • Source: https://github.com/elixir-lang/elixir/releases/download/v1.19.5/elixir-otp-28.zip
  • Why Precompiled: Guarantees binary compatibility with Erlang OTP 28
  • No Compilation: Instant installation, no build tools required

4.5. System Dependencies

The following packages were installed to support compilation and runtime:

# Development Tools
Development Tools group (gcc, make, autoconf, etc.)

# Required Libraries
openssl-devel # HTTPS and cryptographic functions
ncurses-devel # Terminal UI support for Erlang shell
wget # Downloading source files
tar # Archive extraction
git # Version control (optional but recommended)
unzip # Elixir package extraction

5. How-To-Create: Building This AMI from Scratch

This section explains exactly how this AMI was created, allowing you to:

  • Understand the configuration
  • Reproduce the setup on other systems
  • Customize the installation

Step 1: System Preparation

Update the system and install development tools:

# Update all packages
sudo dnf update -y

# Install development tools (gcc, make, etc.)
sudo dnf groupinstall "Development Tools" -y

# Install core dependencies
sudo dnf install -y openssl-devel ncurses-devel wget tar git unzip

Why These Packages:

  • openssl-devel: Required for Erlang's :crypto module (HTTPS support)
  • ncurses-devel: Required for Erlang shell features (color output, line editing)
  • unzip: Required to extract the Elixir precompiled package
  • Development Tools: Provides gcc, make, autoconf needed to compile Erlang

Step 2: Compile Erlang OTP 28.3.1

Download the official source code:

wget https://github.com/erlang/otp/releases/download/OTP-28.3.1/otp_src_28.3.1.tar.gz

Extract the archive:

tar -xzf otp_src_28.3.1.tar.gz
cd otp_src_28.3.1

Configure the build:

./configure --prefix=/usr/local/erlang --without-javac --without-wx

Configuration Deep Dive:

  • --prefix=/usr/local/erlang: Installs all files under /usr/local/erlang/
  • --without-javac: Skips Java interface compilation (saves time, reduces dependencies)
  • --without-wx: Skips wxWidgets GUI toolkit (not needed for server environments)

What Gets Built:

  • BEAM virtual machine with JIT compiler
  • Standard library (stdlib, kernel, compiler, etc.)
  • Crypto module with OpenSSL integration
  • Network modules (ssl, inets, ssh)
  • Database modules (mnesia, odbc)

Compile using all available CPU cores:

make -j$(nproc)

Compilation Time:

  • t3.micro (1 vCPU): ~45 minutes
  • t3.small (2 vCPU): ~25 minutes
  • t3.medium (4 vCPU): ~15 minutes

Install to /usr/local/erlang/:

sudo make install

Create global symbolic links:

sudo ln -sf /usr/local/erlang/bin/erl /usr/bin/erl
sudo ln -sf /usr/local/erlang/bin/erlc /usr/bin/erlc
sudo ln -sf /usr/local/erlang/bin/escript /usr/bin/escript
sudo ln -sf /usr/local/erlang/bin/ct_run /usr/bin/ct_run

Verify the installation:

erl -version

Expected Output:

Erlang (SMP,ASYNC_THREADS) (BEAM) emulator version 16.2

Step 3: Install Elixir 1.19.5

Return to the home directory:

cd ~

Download the precompiled Elixir package for OTP 28:

wget https://github.com/elixir-lang/elixir/releases/download/v1.19.5/elixir-otp-28.zip

Why This Specific Package:

The Elixir team provides multiple precompiled packages per release:

  • elixir-otp-27.zip - For Erlang/OTP 27
  • elixir-otp-28.zip - For Erlang/OTP 28 (this one)
  • elixir-otp-29.zip - For future OTP 29

Using the correct package ensures binary compatibility and prevents version mismatch errors.

Create the installation directory:

sudo mkdir -p /usr/local/elixir

Extract the package:

sudo unzip elixir-otp-28.zip -d /usr/local/elixir

Package Contents:

  • /usr/local/elixir/bin/ - Executables (elixir, elixirc, iex, mix)
  • /usr/local/elixir/lib/ - Standard library (Kernel, Enum, String, etc.)
  • /usr/local/elixir/man/ - Manual pages

Create global symbolic links:

sudo ln -sf /usr/local/elixir/bin/iex /usr/bin/iex
sudo ln -sf /usr/local/elixir/bin/mix /usr/bin/mix
sudo ln -sf /usr/local/elixir/bin/elixir /usr/bin/elixir
sudo ln -sf /usr/local/elixir/bin/elixirc /usr/bin/elixirc

Verify the installation:

elixir --version

Expected Output:

Erlang/OTP 28 [erts-16.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Elixir 1.19.5 (compiled with Erlang/OTP 28)

Critical Verification:

The second line must say compiled with Erlang/OTP 28. If it shows a different version, you downloaded the wrong package.

Step 4: Install Phoenix Framework Tools

Install Hex package manager:

mix local.hex --force

What is Hex:

Hex is Elixir's package manager, similar to npm for Node.js or pip for Python. It manages dependencies defined in mix.exs files.

Install Rebar3 build tool:

mix local.rebar --force

What is Rebar3:

Rebar3 is Erlang's build tool. Some Elixir packages include Erlang code or NIFs (Native Implemented Functions) that require Rebar3 to compile.

Install Phoenix project generator:

mix archive.install hex phx_new --force

What is phx_new:

This Mix archive provides the mix phx.new command for scaffolding new Phoenix web applications.

Verify Phoenix installer:

mix phx.new --version

Expected Output:

Phoenix installer v1.8.3

Step 5: Cleanup (Critical for AMI Size Reduction)

Remove Erlang source code and archive:

cd ~
rm -rf otp_src_28.3.1
rm -f otp_src_28.3.1.tar.gz

Space Saved: ~500 MB

Remove Elixir archive:

rm -f elixir-otp-28.zip

Space Saved: ~15 MB

Clean system package cache:

sudo dnf clean all

Space Saved: ~200 MB

Clear log files:

sudo truncate -s 0 /var/log/messages
sudo truncate -s 0 /var/log/secure
sudo truncate -s 0 /var/log/dnf.log

Clear shell history:

history -c
rm -f ~/.bash_history

Total Space Saved: ~700+ MB

This cleanup is essential before creating an AMI snapshot. Without it, your AMI will contain temporary build artifacts that buyers will never use.


6. Using the Development Environment

6.1. Creating a New Phoenix Web Application

Generate a new Phoenix project:

mix phx.new my_app

You'll be prompted:

Fetch and install dependencies? [Yn]

Type Y and press Enter.

Navigate to the project:

cd my_app

Start the development server:

mix phx.server

Expected Output:

[info] Running MyAppWeb.Endpoint with cowboy 2.12.0 at http://localhost:4000

Access the Application:

If running locally: http://localhost:4000

If on a remote server:

  1. Open port 4000 in your security group
  2. Visit http://YOUR_PUBLIC_IP:4000

6.2. Interactive Development with IEx

Start a Phoenix server with interactive shell:

iex -S mix phx.server

Now you can interact with your application while it's running:

iex(1)> MyApp.Repo.all(MyApp.User)  # Query database
iex(2)> MyAppWeb.Endpoint.config(:url) # Check config
iex(3)> recompile() # Recompile code changes

6.3. Running Tests

Phoenix projects include ExUnit test framework:

mix test

Run with coverage:

mix test --cover

Run specific test file:

mix test test/my_app_web/controllers/page_controller_test.exs

6.4. Database Management

Create database:

mix ecto.create

Run migrations:

mix ecto.migrate

Rollback migration:

mix ecto.rollback

Reset database (drop, create, migrate):

mix ecto.reset

6.5. Dependency Management

Add a dependency to mix.exs:

defp deps do
[
{:jason, "~> 1.4"} # Add this line
]
end

Install new dependencies:

mix deps.get

Update all dependencies:

mix deps.update --all

List dependencies:

mix deps

6.6. Building for Production

Compile for production:

MIX_ENV=prod mix compile

Build a release:

MIX_ENV=prod mix release

The release will be in _build/prod/rel/my_app/.

Start the release:

_build/prod/rel/my_app/bin/my_app start

7. Important File Locations

PathPurposeNotes
/usr/local/erlang/Erlang installationContains bin/, lib/, erts-16.2/
/usr/local/elixir/Elixir installationContains bin/, lib/, man/
/usr/bin/erlErlang shell symlinkGlobal access
/usr/bin/elixirElixir runtime symlinkGlobal access
/usr/bin/iexInteractive shell symlinkGlobal access
/usr/bin/mixBuild tool symlinkGlobal access
~/.mix/Mix configuration and archivesPer-user, auto-created
~/.hex/Hex package cachePer-user, auto-created

8. Troubleshooting

Problem: elixir: command not found

Cause: Symbolic links not created or PATH not updated.

Solution:

# Verify symlinks exist
ls -la /usr/bin/elixir
ls -la /usr/bin/iex
ls -la /usr/bin/mix

# If missing, recreate them
sudo ln -sf /usr/local/elixir/bin/elixir /usr/bin/elixir
sudo ln -sf /usr/local/elixir/bin/iex /usr/bin/iex
sudo ln -sf /usr/local/elixir/bin/mix /usr/bin/mix

Problem: Incompatible Erlang/OTP version error

Cause: Elixir package doesn't match Erlang version.

Solution:

Check versions:

erl -version
elixir --version

If Erlang is OTP 28, you must use elixir-otp-28.zip. Re-download the correct package.

Problem: mix commands fail with SSL errors

Cause: Hex package manager not installed or SSL certificates missing.

Solution:

# Reinstall Hex
mix local.hex --force

# Update CA certificates
sudo dnf update -y ca-certificates

Problem: Phoenix server fails to start on port 4000

Cause: Port already in use or firewall blocking.

Check if port is in use:

sudo netstat -tulpn | grep 4000

Kill existing process:

sudo kill -9 <PID>

Or change port in config/dev.exs:

config :my_app, MyAppWeb.Endpoint,
http: [ip: {0, 0, 0, 0}, port: 5000] # Change to 5000

Problem: mix deps.get fails with network errors

Cause: Hex package server unreachable or proxy issues.

Solution:

# Use CDN mirror
export HEX_MIRROR=https://hexpm.global.ssl.fastly.net

# Retry
mix deps.get

Problem: Application crashes with eaddrinuse error

Cause: Another process is using the same port.

Solution:

Find and stop the conflicting process:

sudo lsof -i :4000
sudo kill <PID>

Or restart the server with a different port:

PORT=5000 mix phx.server

9. Advanced Topics

9.1. Concurrent Programming with Tasks

Elixir makes concurrency simple with the Task module:

# Spawn 1000 concurrent tasks
tasks = for i <- 1..1000 do
Task.async(fn ->
:timer.sleep(1000)
i * 2
end)
end

# Wait for all results
results = Task.await_many(tasks)

This code spawns 1000 lightweight processes (not OS threads), each sleeping 1 second, but completes in ~1 second total.

9.2. Building Distributed Systems

Start multiple Erlang nodes:

Node 1:

iex --sname node1 --cookie secret

Node 2 (in another terminal):

iex --sname node2 --cookie secret

Connect nodes:

Node.connect(:node1@hostname)
Node.list() # See connected nodes

Call functions remotely:

:rpc.call(:node1@hostname, IO, :puts, ["Hello from node2"])

9.3. Performance Monitoring

Observer is a GUI tool for monitoring BEAM:

# Start from iex
:observer.start()

Note: This requires X11 forwarding for remote servers:

ssh -X ec2-user@YOUR_IP

For production servers, use alternative tools:

  • :etop.start() - Terminal-based process monitor
  • Erlang's :recon library

9.4. Memory and CPU Profiling

Check memory usage:

:erlang.memory()

Profile CPU usage:

# Start profiler
:fprof.start()

# Profile a function
:fprof.apply(MyModule, :my_function, [arg1, arg2])

# Analyze results
:fprof.profile()
:fprof.analyse()

9.5. Hot Code Reloading

One of Erlang/OTP's killer features is updating code without stopping the system:

# In production server
iex --sname prod@localhost --cookie prod_secret
# Compile new module version
c("lib/my_app/my_module.ex")

# The new code is now live, old processes still use old version
# New processes use new version

9.6. Optimizing Compilation

For faster development, use parallel compilation in mix.exs:

def project do
[
# ... other settings
compilers: Mix.compilers(),
consolidate_protocols: Mix.env() != :dev # Faster dev compilation
]
end

9.7. Understanding the BEAM VM

Check scheduler information:

:erlang.system_info(:schedulers)
:erlang.system_info(:schedulers_online)

Check process count:

:erlang.system_info(:process_count)
:erlang.system_info(:process_limit) # Default: 262,144

Check atom count (atoms are never garbage collected):

:erlang.system_info(:atom_count)
:erlang.system_info(:atom_limit) # Default: 1,048,576

10. Security Considerations

10.1. Production Security Checklist

When deploying Elixir/Phoenix applications:

  1. Set SECRET_KEY_BASE: Use mix phx.gen.secret to generate a secure key
  2. Enable HTTPS: Never run production apps over HTTP
  3. Disable Debug: Set debug_errors: false in prod.exs
  4. Enable CSRF Protection: Phoenix enables this by default
  5. Use Environment Variables: Never hardcode secrets
  6. Limit SSH Access: Use key-based authentication only
  7. Update Dependencies: Run mix hex.audit to check for vulnerabilities

10.2. Firewall Best Practices

Only expose necessary ports:

# SSH (restrict to your IP)
Source: YOUR_IP/32, Port: 22

# Web application (if public)
Source: 0.0.0.0/0, Port: 443 (HTTPS)

# Do NOT expose these ports:
# - 4369 (EPMD - Erlang Port Mapper Daemon)
# - 9000-9100 (Distributed Erlang)
# - 4000 (Development server)

10.3. Running as Non-Root User

Always run production applications as a non-root user:

# Create application user
sudo useradd -m -s /bin/bash myapp

# Deploy your release to /home/myapp/
sudo cp -r _build/prod/rel/my_app /home/myapp/
sudo chown -R myapp:myapp /home/myapp/

# Run as that user
sudo -u myapp /home/myapp/my_app/bin/my_app start

11. Final Notes

What You've Got

This AMI provides a complete, production-ready Elixir development environment:

  • ✅ Erlang OTP 28.3.1 with JIT compilation (20-50% performance boost)
  • ✅ Elixir 1.19.5 with full OTP 28 compatibility
  • ✅ Phoenix Framework v1.8.3 for rapid web development
  • ✅ OpenSSL 3.5.5 for modern cryptography
  • ✅ All development tools (Mix, Hex, Rebar3, IEx)
  • ✅ Optimized for server workloads (no GUI dependencies)
  1. Learn Elixir Basics:

  2. Build a Phoenix App:

  3. Explore OTP:

    • GenServer for stateful processes
    • Supervisors for fault tolerance
    • Applications for packaging code
  4. Join the Community:

Performance Expectations

Elixir/Erlang can handle:

  • Millions of concurrent connections (WhatsApp handled 2M+ per server)
  • Sub-millisecond latency for most operations
  • Soft real-time performance for chat, gaming, IoT
  • High availability with 99.9999999% uptime (nine nines)

Cost Optimization

For development:

  • t3.micro (1 vCPU, 1GB RAM): Sufficient for learning and small projects
  • t3.small (2 vCPU, 2GB RAM): Better for Phoenix apps with databases

For production:

  • t3.medium+ (2+ vCPU, 4GB+ RAM): Recommended starting point
  • c5.large+: For CPU-intensive workloads
  • r5.large+: For memory-intensive workloads

The BEAM VM scales efficiently with CPU cores, so vertical scaling (bigger instances) is often more cost-effective than horizontal scaling.


Enjoy building with Elixir! 🚀