# Deploy Your Phoenix App on DigitalOcean in 30 Minutes

Hey there, we're going to walk through deploying a Phoenix app to a DigitalOcean droplet, manually - no tools no nothing. Just straight up BASH! But don't worry, I've done the hard work so you don't have to.

It occurred to me this week after seeing some devs on twitter arguing about vercel/self-hosted/serveless that I have ever deployed a Phoenix app myself, by hand, the good old fashioned way. I've always used a PaaS, be that Gigalixir, Heroku, Render, Railway, Fly. The last time I manually deployed something was with PHP in 2012.

Maybe the newer generation of devs know no other way, so I want you to take 30 minutes of your day today, give this a shot, and take a peek at how the sausage is made. It's not as scary as you think!

**This is not a production ready server. Missing: firewals, security, domains, ssl, rolling deploys, backups, etc.**

## The Setup: Creating Our Phoenix Project

First things first, let's whip up a new Phoenix project. We're going with LiveView because, well, it's awesome, and we're using binary IDs because we're cool like that. Fire up your terminal and let's get cracking:

```bash
mix phx.new pokemon --binary-id --live
cd pokemon
```

## Setting Up Our Digital Ocean Droplet

Alright, now we're going to set up our cozy little home in the cloud. Head over to DigitalOcean and create a new Droplet. Here's what you want:

* Region: New York
    
* Datacenter: 1
    
* OS: Ubuntu 24.04 LTS x264
    
* Plan: Regular SSD, $4/month
    

Set up root password authentication and make sure to copy that IPv4 address. You're gonna need it!

## Connecting to Our Droplet

Time to make friends with our new Droplet. In your terminal, type:

```bash
ssh root@your-ip
```

Accept that SSH key fingerprint (it's cool, trust me) and enter your root password. Boom! You're in.

## Updating Our System

Let's make sure our new cloud home is up to date:

```bash
sudo apt update && sudo apt upgrade -y
```

## Installing PostgreSQL 16

Now, we need a place to store all our Pokémon data. Enter PostgreSQL 16:

```bash
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt update
sudo apt install postgresql-16 -y
```

Start it up and make sure it runs on boot:

```bash
sudo systemctl start postgresql
sudo systemctl enable postgresql
```

## Configuring PostgreSQL

Time to set up our database. We're going with 'pokemon' as our password because, well, why not?

```bash
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'pokemon';"
```

Now, let's make PostgreSQL accessible from anywhere (don't worry, we'll secure it later):

```bash
sudo nano /etc/postgresql/16/main/postgresql.conf
# Change: listen_addresses = '*'

sudo nano /etc/postgresql/16/main/pg_hba.conf
# Add: host all all 0.0.0.0/0 md5
```

Restart PostgreSQL and open up the firewall:

```bash
sudo systemctl restart postgresql
sudo ufw allow 5432/tcp
```

Create our app's database:

```bash
sudo -u postgres psql
CREATE DATABASE pokemon;
CREATE USER pokemon WITH PASSWORD 'pokemon';
GRANT ALL PRIVILEGES ON DATABASE pokemon TO pokemon;
\q
```

## Installing Dependencies

Our Droplet needs some tools to run our Phoenix app:

```bash
sudo apt install -y build-essential inotify-tools
sudo apt install elixir
```

## Setting Up Caddy

We're using Caddy as our web server because it's simple and awesome. Let's install it:

```bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
```

Start it up:

```bash
sudo systemctl start caddy
sudo systemctl enable caddy
```

## Preparing Our App for Deployment

Back on your local machine, let's get our Phoenix app ready for the big leagues:

```bash
mix assets.deploy
MIX_ENV=prod mix compile
MIX_ENV=prod mix phx.digest
MIX_ENV=prod mix release
tar -czf pokemon.tar.gz -C _build/prod/rel/pokemon .
```

Now, let's send it to our Droplet:

```bash
scp pokemon.tar.gz root@your_droplet_ip:/opt/pokemon/
```

## Deploying Our App

On the Droplet, extract our app:

```bash
tar -xzf /opt/pokemon/pokemon.tar.gz -C /opt/pokemon
```

Configure Caddy to serve our app:

```bash
sudo nano /etc/caddy/Caddyfile
```

Add this to the Caddyfile:

```ruby
:80 {
    root * /opt/pokemon
    file_server
    try_files {path} {path}/ /index.html
    reverse_proxy localhost:4000
}
```

Restart Caddy:

```bash
sudo systemctl restart caddy
```

Finally, start our Phoenix app:

```bash
/opt/pokemon/bin/pokemon start
```

And there you have it, folks! Your Phoenix app should now be live on your Droplet's IP address. Go ahead, give it a visit in your browser. You should see your app running in all its glory!

So now you guys know how the sausage is made. You've peeked behind the curtain and perhaps gained new appreciation to what PaaS providers abstract away for you. Or maybe you decided this is totally easy and something you can handle yourself. Either way the choice is your now and you're informed.

Where to next?

The next step beyond this simple first implementation would be to use something like [Kamal](https://github.com/basecamp/kamal) or [Coolify](https://github.com/coollabsio/coolify). Beyond that use a PaaS like [Fly.io](http://Fly.io)
