Deployment to Bucknell Servers

February 06, 2026 (2 weeks and 3 days from now)
DANA 325 A B D E C

Resources

You must watch all videos before the lecture.

Deployment on Bucknell Server

Objectives

  • Environment Variables

    .bashenv shows correctly set environment variables.

    0 points
  • Running Remotely

    Have Phoenix running on eg.bucknell.edu.

    0 points
  • deploy.sh script working from linuxremote and/or mix task

    Script successfully updates and restarts server via tmux session.

    0 points
  • mix tasks

    mix tasks (deploy and reset_tables) created successfully and working.

    0 points

The objective of this lecture is to create a website/app using Phoenix and get it up and running on our eg.bucknell.edu server.

Important! Moving forward, all objectives will only be checked and graded on your live website. It is your responsibility to ALWAYS ensure your live website is in a working state and online.

Installation

You are required to deploy your production version to the bucknell servers so your web application is accessible in the internet.

Please read the following correct subsection for your environment.

Installing Postgres

We now need to install postgres as our backend SQL database service. This is a great guide you can follow. Make sure

  • when selecting the password for postgres user to set it to postgres as well.
  • when creating your first database create it with the same name as the postgres user instead of the mydb:
createdb postgres

If you get an error that the postgres database already exists then you can skip this step and you are done.

Test your installation with

psql --version
psql (PostgreSQL) 16.4 (Ubuntu 16.4-1.pgdg22.04+1)

MacOS Guide

The best way to install Elixir/Erlang/Postgres on MacOS is via . Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install elixir

Ultimately you know you have been successful if the following command works and returns the elixir version:

elixir --version
Erlang/OTP 27 [erts-15.1.2] [source] [64-bit] [smp:24:24] [ds:24:24:10] [async-threads:1] [jit:ns]
Elixir 1.17.3 (compiled with Erlang/OTP 27)

Then follow this guide to install postgres.

Test your installation with

psql --version
psql (PostgreSQL) 16.4 (Ubuntu 16.4-1.pgdg22.04+1)

If you selected version 15 and see 15.x as a result thats fine, both 15 and 16 work well.

Next set a password for the postgres. The following will switch you to the postgres user account:

sudo -i -u postgres

You can now open psql

psql

In psql (this is the direct interface to the database) you can set a password:

ALTER USER postgres WITH PASSWORD 'postgres';

Now you can run exit (twice, subsequently) to get you back to your workspace as the default user.

Installing Hex and Phoenix (Windows and Mac)

mix archive.install hex phx_new

(Windows only) also install inotify-tool for auto-page reloading:

sudo apt-get install inotify-tools

Cloning personal repository and creating Phoenix Project

Now head over to gitlab. You can find your personal link on the course website under Resources/Gitlab

In your VSCode terminal navigate to (create if path doesn't exist)

~/workspace

Clone your repository

git clone <yourlink.git>

Careful! This will only work if you have set up ssh publickey authentication correctly. If not watch this video around the 3:15min mark on how to do this.

Do not go into your cloned folder yet. We are going to create the phoenix repository from ~/workspace so we don't have to move our files afterwards.

Start by renaming your folder to csci379, for example:

mv z csci379
mix phx.new csci379 --app app

Do not change the app name from app and make sure you are not altering any optional arguments that you may explore via mix help phx.new.

When prompted whether you would like to install dependencies, confirm yes.

Creating a VSCode workspace

Let's now create a VSCode workspace from our cloned folder. In VSCode go to File > Open Folder and navigate to ~/workspace/csci379.

You should see your folder being popolated with a bunch of files and folders!

You will now want to go again to File > Save Workspace As. and name it csci379.

This should create yet another file in your folder and the overall folder structure should look like this: Folder Structure

Fantastic! All that remain is to create the database:

mix ecto.create

And run the server:

mix phx.server

A testwebsite should now be accessible on http://localhost:4000 and you are done, huray!

Phoenix Startpage

Create a file deploy.sh on linuxremote under ~/workspace with the following content.

module load elixir erlang || exit 1
cd ~/workspace/csci379 || exit 1
echo "Pulling the latest code..."
git pull || { echo "git pull failed"; exit 1; }
echo "Loading dependencies..."
mix deps.get --only prod || { echo "mix deps.get failed"; exit 1; }
echo "Compiling Code"
MIX_ENV=prod mix compile || { echo "mix compile failed"; exit 1; }
echo "Migrating Database"
MIX_ENV=prod mix ecto.migrate || { echo "mix ecto.migrate failed"; exit 1; }
echo "Deploying assets..."
MIX_ENV=prod mix assets.deploy || { echo "mix assets.deploy failed"; exit 1; }
echo "(Re)starting server in tmux..."
tmux kill-session -t LETTER 2>/dev/null || true
tmux new -d -s LETTER "MIX_ENV=prod mix phx.server" || { echo "tmux failed"; exit 1; }
echo "Deployment complete."

Don't forget to change letter in the script and make this file executable:

chmod 700 ~/workspace/deploy.sh

While we are on bucknell's linux system we should set up our secrets. Modify ~/.bashrc to include the production databases's credentials and a secret:

export DATABASE_URL_LETTER="ecto://csci379_25s_LETTER:PASSWORD@eg-postgresql.bucknell.edu/csci379_25s_LETTER"
export SECRET_KEY_BASE="Iee/+2HRTmYA03aa1xvY0umwT1GBM2VPrdMpTpay0NPwacef4L+Wa1bntBQiU6Nk"

Obviously replace LETTER with your course letter (twice, see above) and PASSWORD with your database password that you can find on the course website in your user-menu (when logged in). Also fill in your unique secret key base which you can generate with:

mix phx.gen.secret

Back in our local project workspace.

Update config/prod.exs:

config :app, AppWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json",
check_origin: ["https://eg.bucknell.edu"] # <-- update to this

Also update config/runtime.exs:

# ...
System.get_env("DATABASE_URL_LETTER") || # <-- replace letter with your letter
raise """
environment variable DATABASE_URL_LETTER is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
# ...
config :app, AppWeb.Endpoint,
url: [
host: "www.eg.bucknell.edu", # <-- update to this domain
path: "/csci379-25s-LETTER", # <-- update path with your correct letter
port: 443,
scheme: "https"
],
http: [
# ...
port: 4000 # <-- this needs to match your port number!
],
secret_key_base: secret_key_base
# ...

Since we serve our website locally directly on localhost but our production environment is setup under a subdomain eg.bucknell.edu/csci379-s25-LETTER we also need to tweak our websocket on the frontend to prepend the folder if we are in production enviroment:

assets/js/app.js:

let liveSocketPath = process.env.NODE_ENV === "production" ? "/csci379-s25-LETTER/live" : "/live";
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket(liveSocketPath, Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken }
})

Don't forget to replace LETTER with your letter again, this time, lower-case.

Now your production environment is set up and should be able to connect to the database later.

mix ecto.create and mix ecto.drop will not work as commands as we don't have postgres superadmin rights. Thus we will need to add a custom script that simulates the same behavior.

Add lib/mix/tasks/reset_tables.ex (create folder path as it doesn't exist yet):

In your local computer's project workspace you will need to add two misk tasks: lib/mix/tasks/deploy.ex:

defmodule Mix.Tasks.Deploy do
use Mix.Task
@shortdoc "SSH into the server, update code, run tests, and restart the Phoenix server"
def run(_) do
ssh_command = """
ssh linuxremote3 << 'EOF'
~/workspace/deploy.sh
EOF
"""
{result, _exit_code} = System.cmd("bash", ["-c", ssh_command], stderr_to_stdout: true)
IO.puts(result)
end
end

This will allow you to simply run mix deploy from your local computer and automatically ssh into Bucknell's linuxremote3 server in order to update the live website.

lib/mix/tasks/deploy.ex:

defmodule Mix.Tasks.ResetTables do
use Mix.Task
@shortdoc "Drops, recreates and migrates database."
def run(_) do
# Start the application to ensure Repo is loaded
Mix.Task.run("app.start")
alias App.Repo
alias Ecto.Adapters.SQL
Mix.shell().info("Resetting tables...")
# Drop all tables
drop_all_tables()
# Run migrations
Mix.Task.run("ecto.migrate")
# Seed the database
# Mix.Task.run("run", ["priv/repo/seeds.exs"])
end
defp drop_all_tables do
alias Ecto.Adapters.SQL
alias App.Repo
query = """
DO $$ DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END $$;
"""
SQL.query!(Repo, query, [])
Mix.shell().info("Dropped all tables.")
end
end

Since we can't delete and recreate the database directly (on bucknell's server) this workaround will essential do the same steps as mix reset. (Delete, Recreate, Migrate)

If we ever mess up our migrations/database we can now run MIX_ENV=prod mix reset_tables (in linuxremote) to reset the live database.