Phoenix/Elixir Development on Windows: Your Complete Journey
From Windows User to Phoenix Developer - A Guide Written by Someone Who's Been There
Hey there! So you want to learn Phoenix and Elixir, but you're on Windows. I get it - I was in your exact shoes. Everyone kept telling me "just use Linux" or "set up WSL," but nobody really explained why or walked me through it properly.
Here's the truth: Phoenix and Elixir were born in the Unix/Linux world. They use tools that expect Unix-style file systems, Unix signals, and Unix conventions. While you can technically run Phoenix on native Windows, you're going to hit weird issues:
- Compilation times are painfully slow
- File watchers don't work reliably
- Some dependencies just... won't compile
- Terminal colors look broken
- You'll pull your hair out debugging Windows-specific issues that nobody else has
But here's the good news: Windows Subsystem for Linux (WSL) gives you a real Linux environment running inside Windows. It's not a VM (so it's fast), and it shares your Windows filesystem. You get the best of both worlds.
This guide will take you on the journey I wish I had when I started. Let's do this properly.
Part 1: Setting Up Your Linux Environment (WSL)
Why WSL Instead of Native Windows?
Before we dive in, let me tell you what happened when I tried to run Phoenix on native Windows. I spent three days troubleshooting why inotify wasn't working (it's a Linux thing that watches files for changes). The Phoenix documentation assumed I was on Linux. Every StackOverflow answer assumed Linux. The error messages were cryptic.
Then I switched to WSL. Everything worked in 20 minutes.
WSL is Microsoft's way of saying "we know developers need Linux." It's officially supported, actively maintained, and it's honestly amazing.
Step 1: Installing WSL
Open PowerShell as Administrator. This is important - right-click PowerShell and choose "Run as Administrator."
wsl --install
That's it. Seriously. This one command:
- Enables WSL on your Windows machine
- Downloads Ubuntu (the most popular Linux distribution)
- Sets everything up for you
What's happening behind the scenes? Windows is creating a lightweight Linux kernel that runs alongside Windows. It's not emulation or virtualization in the traditional sense - it's a compatibility layer. Think of it like having a Linux computer living inside your Windows computer, sharing resources.
After running this command, restart your computer. Windows needs to enable some features that require a reboot.
Step 2: First Boot - Creating Your Linux User
When your computer restarts, Ubuntu will automatically open in a terminal window. Don't panic - this is normal!
You'll be asked to create a username and password. This is important:
- Username: Pick something simple (like your first name). You'll type this a lot.
- Password: Make it something you'll remember. You'll need it whenever you use
sudo(which is like "Run as Administrator" in Linux).
Pro tip from experience: I initially made my password super complex. Then I realized I was typing it 50 times a day for sudo commands. Pick something secure but not annoying to type.
When you type your password, nothing will appear on screen - no dots, no asterisks, nothing. This is a Linux security feature, not a bug. Just type your password and press Enter.
Step 3: Understanding Your New Environment
Congratulations! You now have a Linux terminal. This is bash (Bourne Again Shell), the standard Linux command line.
Let's make sure everything is up to date:
sudo apt update && sudo apt upgrade -y
Let me break this down:
sudo= "Super User DO" - run this command with administrator privilegesapt= Ubuntu's package manager (like the App Store, but for software)update= refresh the list of available softwareupgrade -y= install updates for all installed software, automatically say "yes" to prompts
This might take a few minutes. Let it run - it's updating your Linux system with the latest security patches and bug fixes.
Why do we need this? When WSL installs Ubuntu, it uses a snapshot from a few months ago. We want the latest versions to avoid security issues and bugs.
Part 2: The Build Tools - Setting Up the Foundation
Why Do We Need Build Tools?
Here's something that confused me at first: Elixir and Erlang aren't just downloaded and installed. They need to be compiled from source code on your machine.
Think of it like baking a cake. We're not buying a pre-made cake (that would be downloading a binary). We're getting the recipe and ingredients (source code) and baking it ourselves. This ensures it's optimized for your exact system.
To compile Erlang, we need compilers, libraries, and development tools. This is what we're installing now:
sudo apt install -y build-essential autoconf m4 \
libncurses5-dev libwxgtk3.0-gtk3-dev \
libwxgtk-webview3.0-gtk3-dev libgl1-mesa-dev \
libglu1-mesa-dev libpng-dev libssh-dev \
unixodbc-dev xsltproc fop libxml2-utils \
libncurses-dev openjdk-11-jdk inotify-tools
What is all this stuff?
build-essential- The basics: C compiler, make, etc.autoconf,m4- Tools that help configure software before buildinglibncurses5-dev- For terminal UI stuff (colors, cursor control)libwxgtk*- GUI libraries (Erlang's Observer tool needs these)libssl-dev- SSL/encryption supportinotify-tools- File watching (this is what makes Phoenix auto-reload when you change code!)
My experience: I initially skipped some of these, thinking "I don't need GUI tools." Then I tried to use Erlang's Observer (amazing debugging tool) and it didn't work. Don't skip this step.
This installation takes 5-10 minutes. Go grab coffee.
Part 3: Version Management with asdf - The Smart Way
Why Not Just Install Erlang and Elixir Directly?
You could. But here's what happens:
- You're stuck with whatever version Ubuntu provides (usually old)
- Different projects might need different versions
- Updating is a pain
- You can't easily test your code against multiple Elixir versions
asdf is a version manager. It lets you install and switch between multiple versions of Erlang, Elixir, Node.js, and dozens of other languages. It's like having a time machine for your programming tools.
Installing asdf
# Download asdf
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
# Add to your bash profile
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
echo '. "$HOME/.asdf/completions/asdf.bash"' >> ~/.bashrc
# Reload your terminal configuration
source ~/.bashrc
What just happened?
- We cloned asdf into a hidden folder in your home directory (
~/.asdf) - We added two lines to
.bashrc(this file runs every time you open a terminal) - We reloaded
.bashrcso the changes take effect now
The .bashrc file is like your terminal's startup script. Every time you open a terminal, Linux reads this file and runs the commands in it. By adding asdf here, we're making sure it's always available.
Part 4: Installing Erlang - The Foundation of Elixir
What Is Erlang and Why Do We Need It?
Elixir runs on the Erlang VM (called BEAM). Think of it this way:
- Erlang is like the engine
- Elixir is like the car body and controls
- You're driving the car (Elixir), but the engine (Erlang) is what makes it move
Erlang was created by Ericsson in the 1980s for telecom switches that needed to run for years without stopping. It's battle-tested, incredibly reliable, and perfect for building concurrent, fault-tolerant systems.
Installing Erlang
# Tell asdf where to get Erlang
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
# Install Erlang 26.2.5
asdf install erlang 26.2.5
# Make it the default version
asdf global erlang 26.2.5
β° WARNING: This takes 10-20 minutes!
Erlang is a big piece of software. Go for a walk, watch a YouTube video, or start learning about pattern matching in Elixir. Your computer is working hard compiling everything.
Why version 26.2.5? It's the current stable release as of January 2025. It's been thoroughly tested, and most Elixir libraries work perfectly with it.
What's happening during compilation?
Your computer is:
- Downloading Erlang source code
- Configuring it for your specific system
- Compiling thousands of files
- Creating the Erlang VM and all its libraries
- Installing everything in the right place
When it's done, verify it worked:
erl -version
You should see something like Erlang/OTP 26. Success!
Part 5: Installing Elixir - Your New Best Friend
Why Elixir?
Now that we have Erlang, we can install Elixir. If you're new to Elixir, here's why it's amazing:
- Beautiful syntax - Feels like Ruby, but runs on the Erlang VM
- Functional programming - Immutable data, pure functions, easy to reason about
- Fault tolerance - Crashes are handled gracefully; your app keeps running
- Concurrency - Handle thousands of connections easily
- Pattern matching - Makes code incredibly expressive
- The Phoenix framework - Like Rails, but better for real-time apps
Installing Elixir
# Tell asdf where to get Elixir
asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
# Install Elixir 1.16.2, built for OTP 26
asdf install elixir 1.16.2-otp-26
# Make it the default
asdf global elixir 1.16.2-otp-26
Notice the -otp-26 part? This ensures Elixir is built specifically for Erlang/OTP 26. You need to match these versions.
This is much faster than Erlang - maybe 2-3 minutes.
Your First Elixir Experience
Let's verify it works and have some fun:
elixir --version
You should see your Elixir and Erlang versions.
Now start the interactive Elixir shell:
iex
Try this:
IO.puts "Hello, Phoenix developer!"
# Some cool Elixir features
1..10 |> Enum.map(&(&1 * 2)) |> Enum.sum()
# Pattern matching
{:ok, result} = {:ok, "It works!"}
IO.puts result
Type Ctrl+C twice to exit.
Wasn't that beautiful? The pipe operator (|>) chains functions together. That one line created a range from 1 to 10, doubled each number, and summed them up. This is the power of Elixir.
Part 6: PostgreSQL - Your Database
Why PostgreSQL?
Phoenix defaults to PostgreSQL, and for good reason:
- Rock solid - Used by massive companies (Apple, Instagram, Reddit)
- Feature-rich - JSON support, full-text search, geographic data
- Open source - Free and well-documented
- Great with Ecto - Elixir's database library works beautifully with Postgres
The Windows PostgreSQL Trap
Here's a mistake I made: I already had PostgreSQL installed on Windows. I thought "Great! I'll just connect to that from WSL."
Don't do this. Here's why:
- Networking complexity - WSL and Windows have different network stacks
- Path issues - Windows paths (C:) don't work in Linux
- Performance - Cross-system connections are slower
- Reliability - IP addresses can change; connections can break
Just install PostgreSQL in WSL. It's lightweight and will save you hours of frustration.
Installing PostgreSQL
sudo apt install postgresql postgresql-contrib -y
This installs PostgreSQL 16 (the latest stable version in Ubuntu).
The Installation Problem You'll Probably Hit
When I first did this, I tried to start PostgreSQL and got an error:
connection to server on socket failed: No such file or directory
This happens because the database cluster wasn't initialized. Let's fix it proactively:
# Create the data directory
sudo mkdir -p /var/lib/postgresql/16/main
# Give PostgreSQL ownership
sudo chown -R postgres:postgres /var/lib/postgresql/16
# Initialize the database cluster
sudo -u postgres /usr/lib/postgresql/16/bin/initdb -D /var/lib/postgresql/16/main
What's a database cluster? It's where PostgreSQL stores all your databases, users, and settings. Think of it as the filing cabinet for all your data.
Now start PostgreSQL:
sudo pg_ctlcluster 16 main start
Check if it's running:
sudo pg_ctlcluster 16 main status
You should see "online" or something similar. Success!
Setting Up Your Database Password
PostgreSQL comes with a default superuser called postgres. Let's give it a password:
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'your_password_here';"
β οΈ Special Character Warning:
If your password has a $ in it (like mine did - Shanipal$9984), you need to escape it:
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'Shanipal\$9984';"
The backslash tells bash "don't interpret the $ as a variable, it's literally a dollar sign."
My password advice: Use something secure but not annoying to type. You'll be using it in your Phoenix config files and typing it occasionally in the terminal.
Test your connection:
psql -U postgres -h localhost -d postgres
Type your password. If you see the postgres=# prompt, you're in! Type \q to exit.
Auto-Starting PostgreSQL
Here's annoying thing #47 about WSL: services don't auto-start.
Every time you reboot Windows or restart WSL, PostgreSQL won't be running. You'll start Phoenix, and it'll fail with a database connection error. You'll spend 10 minutes debugging before realizing "oh, I forgot to start PostgreSQL."
Let's fix this now:
echo "sudo service postgresql start" >> ~/.bashrc
source ~/.bashrc
Now PostgreSQL starts automatically every time you open a terminal.
Alternative approach - Create an alias:
echo "alias pgstart='sudo service postgresql start'" >> ~/.bashrc
echo "alias pgstatus='sudo pg_ctlcluster 16 main status'" >> ~/.bashrc
source ~/.bashrc
Now you can type:
pgstartto start PostgreSQLpgstatusto check if it's running
Part 7: Node.js - For Your Frontend Assets
Why Does Phoenix Need Node.js?
Phoenix handles your backend (database, API, business logic). But web apps also need frontend assets:
- JavaScript files
- CSS stylesheets
- Images
- Fonts
Phoenix uses esbuild (a JavaScript bundler) to compile and optimize these assets. Esbuild is written in Go but distributed via npm (Node's package manager).
Even if you're building an API-only app, it's good to have Node.js installed. Many Elixir tools depend on it.
Installing Node.js
# Tell asdf about Node.js
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
# Install Node.js 20 (LTS version)
asdf install nodejs 20.11.1
# Make it default
asdf global nodejs 20.11.1
Verify:
node --version
npm --version
Why version 20? It's the current Long-Term Support (LTS) release. It'll be supported with security updates for years.
Part 8: Phoenix - Finally, The Main Event!
What Is Phoenix?
Phoenix is a web framework built with Elixir. If you've used Rails, Django, or Express, Phoenix will feel familiar. But it has some superpowers:
- Real-time by default - LiveView makes interactive UIs without writing JavaScript
- Blazing fast - Can handle millions of connections
- Developer-friendly - Amazing error messages, hot code reloading
- Productivity - Generators create boilerplate code for you
- Ecto - The best database library I've ever used
Installing Phoenix
# Install Hex (Elixir's package manager)
mix local.hex --force
# Install the Phoenix application generator
mix archive.install hex phx_new --force
What's mix? It's Elixir's build tool. Think of it like npm for JavaScript or rake for Ruby. You'll use mix for everything: running tests, starting servers, generating code, managing dependencies.
Creating Your First Phoenix App
This is the moment. Let's build something:
cd ~
mix phx.new my_first_app
You'll see:
* creating my_first_app/config/config.exs
* creating my_first_app/config/dev.exs
* creating my_first_app/config/prod.exs
...
Fetch and install dependencies? [Yn]
Type Y and press Enter.
What's happening?
Phoenix is:
- Creating a complete project structure
- Setting up configuration files
- Creating example controllers, views, and templates
- Installing Elixir dependencies (like Ecto for database)
- Installing Node.js dependencies (for asset compilation)
This takes a few minutes. Watch the output - it's actually pretty interesting to see everything being set up.
The Moment of Truth
cd my_first_app
mix ecto.create
This creates your development database. You should see:
The database for MyFirstApp.Repo has been created
Now start Phoenix:
mix phx.server
You'll see:
[info] Running MyFirstAppWeb.Endpoint with cowboy 2.x.x at 127.0.0.1:4000 (http)
[info] Access MyFirstAppWeb.Endpoint at http://localhost:4000
Open your Windows web browser and go to http://localhost:4000
π YOU DID IT! You should see the Phoenix welcome page.
Why This Is Amazing
You just:
- Set up a complete Linux development environment
- Installed a programming language and its runtime
- Set up a production-grade database
- Created a web application
- Are running a server that can handle thousands of concurrent connections
And it all happened in under an hour (mostly waiting for Erlang to compile).
Part 9: Understanding Your Phoenix App
Let's explore what we just created. Open a new terminal (keep the server running) and look around:
cd ~/my_first_app
ls
Project structure:
my_first_app/
βββ assets/ β Your JavaScript, CSS, images
βββ config/ β Configuration files
β βββ dev.exs β Development settings
β βββ prod.exs β Production settings
β βββ test.exs β Test settings
βββ lib/
β βββ my_first_app/ β Business logic (contexts, schemas)
β βββ my_first_app_web/ β Web stuff (controllers, views, templates)
βββ priv/
β βββ repo/
β βββ migrations/ β Database migrations
βββ test/ β Your tests
βββ mix.exs β Dependencies and project config
The Two Main Directories
lib/my_first_app/ - Your business logic
This is where you put things like:
- User accounts
- Blog posts
- Shopping cart logic
- Email sending
- API integrations
Phoenix calls these "contexts" - groups of related functionality.
lib/my_first_app_web/ - Your web layer
This is where you put:
- Controllers (handle HTTP requests)
- Views (prepare data for templates)
- Templates (HTML)
- LiveViews (real-time interactive components)
- Routes (URL mapping)
Why separate them? It keeps your business logic independent of the web framework. You could potentially use the same business logic for a mobile app API, a chatbot, or a command-line tool.
Looking at the Code
Open lib/my_first_app_web/controllers/page_controller.ex:
defmodule MyFirstAppWeb.PageController do
use MyFirstAppWeb, :controller
def home(conn, _params) do
# The home page is rendered from lib/my_first_app_web/controllers/page_html/home.html.heex
render(conn, :home)
end
end
This is beautiful simplicity. When someone visits /, Phoenix:
- Routes to
PageController.home - Renders the template
- Sends back HTML
Part 10: Database Configuration
Let's configure Phoenix to use your PostgreSQL database.
Open config/dev.exs in a text editor. Look for this section:
# Configure your database
config :my_first_app, MyFirstApp.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "my_first_app_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
Change the password to match what you set earlier:
password: "your_actual_password",
What is this config doing?
username- Database user (we're using the defaultpostgres)password- Your database passwordhostname- Where's the database? (localhostmeans on this machine)database- Name of your app's databasepool_size- How many concurrent database connections (10 is good for development)
Pro tip: Never commit database passwords to git! For real projects, use environment variables:
password: System.get_env("DATABASE_PASSWORD") || "postgres",
Then create a .env file:
export DATABASE_PASSWORD="your_password"
And add .env to your .gitignore.
Part 11: VS Code Setup - Your Development Environment
Why VS Code?
You can use any editor (Vim, Emacs, Sublime), but VS Code with WSL integration is magical:
- Remote development - Code runs in Linux, but you edit in Windows
- IntelliSense - Auto-completion for Elixir
- Debugging - Set breakpoints, inspect variables
- Git integration - Stage, commit, push without leaving the editor
- Extensions - ElixirLS gives you superpowers
Setting Up VS Code
- Install VS Code on Windows (not in WSL)
- Install the WSL extension - Search for "WSL" in extensions marketplace
Now, from your WSL terminal:
cd ~/my_first_app
code .
The first time you do this, VS Code will install a server component in WSL. Wait for it to finish.
Then VS Code opens, and look at the bottom-left corner - you'll see something like "WSL: Ubuntu". This means you're developing inside WSL!
Essential Extensions
Install these in VS Code:
- ElixirLS: Elixir support and debugger
- Auto-completion
- Go to definition
- Find all references
- Inline documentation
- Error highlighting
- Phoenix Framework
- Snippets for common Phoenix patterns
- Template syntax highlighting
- GitLens
- See who wrote each line of code
- Navigate through git history
- Blame annotations
The VS Code Terminal
Press Ctrl+` (backtick) to open the integrated terminal. This terminal is actually running in WSL! You can run mix phx.server, iex, or any Linux command.
Accessing Files from Windows
Your WSL files are available in Windows at:
\\wsl$\Ubuntu\home\your-username\my_first_app
But honestly, just use VS Code. It's way better than trying to navigate WSL files in Windows Explorer.
Part 12: Your First Feature - Building Something Real
Let's build a simple blog to understand how Phoenix works.
Generating a Blog Post Resource
Phoenix has generators that create everything you need:
mix phx.gen.html Blog Post posts title:string body:text published_at:datetime
Let me break down this command:
mix phx.gen.html- Generate HTML resources (controllers, templates, context)Blog- The context name (groups related functionality)Post- The schema name (database table will beposts)posts- Plural form for routestitle:string body:text published_at:datetime- Database fields and types
Phoenix creates:
* creating lib/my_first_app_web/controllers/post_controller.ex
* creating lib/my_first_app_web/controllers/post_html.ex
* creating lib/my_first_app_web/controllers/post_html/...
* creating lib/my_first_app/blog/post.ex
* creating priv/repo/migrations/20260106000000_create_posts.exs
* creating test/my_first_app/blog_test.exs
* creating test/my_first_app_web/controllers/post_controller_test.exs
That's a lot of code we didn't have to write!
Adding the Routes
Phoenix tells you what to do next:
Add the resource to your browser scope in lib/my_first_app_web/router.ex:
resources "/posts", PostController
Open lib/my_first_app_web/router.ex and add that line:
scope "/", MyFirstAppWeb do
pipe_through :browser
get "/", PageController, :home
resources "/posts", PostController # β Add this line
end
What does resources do? It creates these routes:
GET /posts- List all postsGET /posts/new- Form to create a new postPOST /posts- Create a postGET /posts/:id- Show a specific postGET /posts/:id/edit- Form to edit a postPUT /posts/:id- Update a postDELETE /posts/:id- Delete a post
That's 7 routes with one line of code!
Running the Migration
We generated a migration file. Let's run it:
mix ecto.migrate
You'll see:
[info] == Running MyFirstApp.Repo.Migrations.CreatePosts.change/0 forward
[info] create table posts
[info] == Migrated in 0.0s
What just happened? Phoenix created a posts table in your database with columns for title, body, and published_at.
Testing It Out
Visit http://localhost:4000/posts
You should see an empty list with a "New Post" button. Click it, create a post, and boom - you have a working blog!
Look at what you got for free:
- List view with edit/delete buttons
- Create form with validation
- Edit form
- Show page
- Database queries
- Tests
- HTML templates
All from one command.
Part 13: Understanding the Code Phoenix Generated
The Schema (lib/my_first_app/blog/post.ex)
defmodule MyFirstApp.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :string
field :published_at, :naive_datetime
timestamps(type: :utc_datetime)
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :published_at])
|> validate_required([:title, :body])
end
end
The schema defines the structure of your data. Notice:
field :title, :string- Maps to database columntimestamps()- Automatically addsinserted_atandupdated_atchangeset- Validates and filters data
Changesets are brilliant. They're how Ecto handles validation, casting, and constraints. You pipe data through transformations:
post
|> cast(attrs, [:title, :body]) # Only allow these fields
|> validate_required([:title]) # Title is required
|> validate_length(:title, min: 3) # Add custom validation
The Context (lib/my_first_app/blog.ex)
defmodule MyFirstApp.Blog do
import Ecto.Query
alias MyFirstApp.Repo
alias MyFirstApp.Blog.Post
def list_posts do
Repo.all(Post)
end
def get_post!(id), do: Repo.get!(Post, id)
def create_post(attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
# ... more functions
end
The context is your public API. Controllers call these functions, but they never touch the database directly.
Why this architecture?
- Testability - You can test business logic without HTTP
- Reusability - Use the same logic in API, web, CLI
- Clarity - Easy to find where business logic lives
The Controller (lib/my_first_app_web/controllers/post_controller.ex)
defmodule MyFirstAppWeb.PostController do
use MyFirstAppWeb, :controller
alias MyFirstApp.Blog
def index(conn, _params) do
posts = Blog.list_posts()
render(conn, :index, posts: posts)
end
def create(conn, %{"post" => post_params}) do
case Blog.create_post(post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post created successfully.")
|> redirect(to: ~p"/posts/#{post}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
# ... more actions
end
Look at the create action. This is pattern matching magic:
- If
create_postreturns{:ok, post}β redirect with success message - If it returns
{:error, changeset}β re-render form with errors
No if statements needed. No checking for null. Just pattern matching.
Part 14: Common Issues and How to Fix Them
Issue 1: "Could not compile dependency"
What happened: You tried to start Phoenix, but got compilation errors.
Solution:
# Delete build artifacts and dependencies
mix deps.clean --all
rm -rf _build
# Re-download dependencies
mix deps.get
# Recompile
mix compile
Why this works: Sometimes dependencies get corrupted or cached in a weird state. This nukes everything and starts fresh.
Issue 2: "Database does not exist"
What happened: You see (Postgrex.Error) FATAL 3D000 (invalid_catalog_name) database "my_app_dev" does not exist
Solution:
mix ecto.create
Why this works: You forgot to create the database. This creates it.
Issue 3: "Port 4000 already in use"
What happened: You started Phoenix, but another process is using port 4000.
Solution:
# Find what's using port 4000
lsof -i :4000
# Kill it (replace PID with the actual number)
kill -9 PID
# Or start Phoenix on a different port
mix phx.server --port 4001
Why this happens: You probably stopped Phoenix with Ctrl+Z instead of Ctrl+C, leaving it running in the background.
Issue 4: PostgreSQL won't start
What happened: sudo service postgresql start does nothing or fails.
Solution:
# Check status
sudo pg_ctlcluster 16 main status
# Try starting manually
sudo pg_ctlcluster 16 main start
# Check logs
sudo tail -50 /var/log/postgresql/postgresql-16-main.log
Common cause: The data directory wasn't initialized. Go back to Part 6 and run the initdb command.
Issue 5: "Can't access localhost:4000 from Windows"
What happened: Server is running, but your Windows browser can't connect.
Solution:
In config/dev.exs:
config :my_first_app, MyFirstAppWeb.Endpoint,
http: [ip: {0, 0, 0, 0}, port: 4000] # Bind to all interfaces
Restart Phoenix.
Why this works: By default, Phoenix binds to 127.0.0.1 (localhost only). Changing to {0, 0, 0, 0} makes it accessible from Windows.
Part 15: Next Steps on Your Journey
You've come so far! You have:
- β A full Linux development environment
- β Erlang and Elixir installed
- β PostgreSQL configured
- β Phoenix working
- β Your first Phoenix app
- β Understanding of the Phoenix architecture
What to Learn Next
1. Phoenix LiveView
This is game-changing. Build real-time, interactive UIs without writing JavaScript. Start with the official guide:
mix phx.new my_app --live
2. Ecto Queries
Learn how to query your database like a pro:
# Simple query
Repo.all(Post)
# With conditions
from(p in Post, where: p.published == true)
# Joins
from(p in Post, join: c in assoc(p, :comments), where: c.author == "John")
# Aggregates
from(p in Post, select: count(p.id))
3. Testing
Phoenix generates tests for you. Run them:
mix test
Learn to write good tests. It'll make you a better developer.
4. Deployment
When you're ready, learn about:
- Releases (bundling your app for production)
- Fly.io (easy Phoenix hosting)
- Database migrations in production
- Environment variables
Learning Resources That Actually Helped Me
Books:
- Programming Phoenix 1.4 by Chris McCord - The definitive guide
- Elixir in Action by SaΕ‘a JuriΔ - Best Elixir book, period
Videos:
- ElixirConf talks on YouTube - Amazing community
- Pragmatic Studio courses - Worth every penny
Communities:
- Elixir Forum (https://elixirforum.com/) - Super helpful folks
- Elixir Slack - Real-time help
- Phoenix Discord - Friendly and active
My Final Advice
1. Don't rush. Elixir is different from OOP languages. Pattern matching, immutability, and the "let it crash" philosophy take time to internalize.
2. Embrace functional programming. Stop trying to write JavaScript/Python/Java in Elixir. Learn to think functionally.
3. Read other people's code. Open source Elixir projects on GitHub are incredibly readable.
4. Build something real. Don't just do tutorials. Build a project you care about.
5. Ask for help. The Elixir community is the friendliest I've encountered. Don't be shy.
Quick Reference - Commands You'll Use Daily
# PostgreSQL
pgstart # Start PostgreSQL
pgstatus # Check if it's running
psql -U postgres # Connect to database
# Phoenix Server
mix phx.server # Start server
iex -S mix phx.server # Start server with interactive shell
mix phx.server --port 4001 # Start on different port
# Database
mix ecto.create # Create database
mix ecto.migrate # Run migrations
mix ecto.rollback # Undo last migration
mix ecto.reset # Drop, create, migrate
# Generators
mix phx.gen.html Context Schema schemas field:type
mix phx.gen.live Context Schema schemas field:type
mix phx.gen.json Context Schema schemas field:type
mix phx.gen.context Context Schema schemas field:type
# Testing
mix test # Run all tests
mix test test/file_test.exs # Run specific file
mix test --trace # Run tests synchronously with detailed output
# Dependencies
mix deps.get # Fetch dependencies
mix deps.update --all # Update all dependencies
mix deps.clean --all # Remove all dependencies
# Code Quality
mix format # Format code
mix credo # Static code analysis (need to add to deps)
# Interactive Elixir
iex # Start IEx
iex -S mix # Start IEx with your project loaded
h Module # Show documentation
r Module # Recompile module
Troubleshooting Checklist
When things go wrong (and they will), work through this:
- [ ] Is PostgreSQL running? (
pgstatus) - [ ] Did you create the database? (
mix ecto.create) - [ ] Did you run migrations? (
mix ecto.migrate) - [ ] Are dependencies installed? (
mix deps.get) - [ ] Is port 4000 available? (
lsof -i :4000) - [ ] Did you restart the server after config changes?
- [ ] Are you in the project directory? (
pwd) - [ ] Check the error message carefully (Phoenix errors are helpful!)
Conclusion: You're Ready
I remember sitting where you are now - confused about WSL, intimidated by Elixir, wondering if I could actually learn this.
Here's what I learned: Phoenix makes hard things easy and easy things trivial.
- Want WebSockets? Built-in.
- Want real-time updates? LiveView.
- Want to handle 2 million connections? Phoenix does that.
- Want amazing development experience? Phoenix has your back.
You've done the hard part - setting up the environment. Now comes the fun part - building amazing things.
Welcome to the Phoenix community. We're glad you're here.
Now go build something awesome! π
This guide was written by someone who learned Phoenix on Windows, made all the mistakes, and wants to save you the frustration. If this helped you, pay it forward - help the next person starting their Phoenix journey.