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! 🚀