Simple email form with Sinatra, nginx-passenger, and Docker Compose

DEPRECATED!

As of 2017-6-13, this process does not work. Instead of revisiting, I decided to create an app with similar functionality in Node and Express: email-form-promo. It’s fully tested and a lot punchier than the app produced as a result of the process described below.

Introduction

It seems there’s no nice way to tie Nginx and Phusion Passenger together with Docker Compose. There still isn’t, but I cooked up a solution that works for my purposes. Here I document that process by deploying a simple email signup form with Sinatra.

The landing page

Nothing fancy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Email signup</title>
</head>
<body>
<form action='/mail/signup' method='post'>
<label>Name</label>
<input id='name' type='text' placeholder='Larry Wall'>
<label>Email</label>
<input id='email' type='email' placeholder='larry@example.com'>
<label>Message</label>
<textarea id='message' rows='5' placeholder='Keep me posted...'></textarea>
<button id='send-email-button' type='submit'>Send</button>
</form>
</body>
</html>

Sinatra app

config.ru

1
2
3
# config.ru
require File.expand_path('app', File.dirname(__FILE__))
map('/mail') { run MailerController }

Gemfile

1
2
3
4
5
source "https://rubygems.org"
gem 'sinatra'
gem 'passenger'
gem 'pony'

Don’t forget to run bundle install.

app.rb

This is where all the mailer magic is set in motion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# app.rb
require 'sinatra'
require 'pony'
class MailerController < Sinatra::Base
post '/signup' do
configure_pony
name = params[:name]
sender_email = params[:email]
message = params[:message]
logger.error params.inspect
begin
Pony.mail(
:from => 'info@example.com',
:to => 'info@example.com',
:reply_to => "#{name}<#{sender_email}>",
:subject => "#{name} wants to be added to the list",
:body => "#{message}",
)
halt 200
rescue
@exception = $!
erb :boom
end
end
def configure_pony
Pony.options = {
:via => :smtp,
:via_options => {
:address => 'mail.server.com',
:port => '587',
:user_name => 'info@example.com',
:password => 'secretpassword',
:authentication => :plain,
:enable_starttls_auto => true,
:domain => 'example.com'
}
}
end
end

boom.rb

Just in case something goes wrong… this will display any exceptions.

1
2
3
4
# boom.rb
No good!
<%= @exception %>

Docker

I hope someone comes up with a better way than this. It seems like this should be something achievable with Docker Compose alone. I can get close to that goal, but I still need to create a Dockerfile to make it all work.

Dockerfile

Phusion Passenger needs the app’s Gemfile. This Dockerfile points the image at that Gemfile and installs all the dependencies. It uses raphaeldelaghetto/nginx-passenger as its base.

1
2
3
4
5
6
7
8
# Dockerfile
FROM raphaeldelaghetto/nginx-passenger
MAINTAINER Some Guy
ADD Gemfile /usr/share/nginx/html/Gemfile
WORKDIR /usr/share/nginx/html
RUN bundle install

docker-compose.yml

This builds the Dockerfile above, which downloads the nginx-passenger image from the Docker repository. Optional SSL settings are commented out.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# docker-compose.yml
email-app:
restart: always
build: ./
ports:
- "80:80"
#- "443:443"
volumes:
# Page content
- ./:/usr/share/nginx/html
# Certs
#- /home/app/certs:/etc/nginx/ssl
# default.conf
- ./config:/etc/nginx/sites-enabled/

Nginx configuration

As you can see above, Docker Compose is going to look in the ./config directory for default.conf. Make sure the path exists:

1
mkdir config

Copy and paste the following into ./config/default.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#default.conf
server {
listen 80;
#listen 443 ssl;
server_name example.com;
#ssl_certificate example.com.crt;
#ssl_certificate_key example.com.key;
root /usr/share/nginx/html;
location /mail/ {
passenger_enabled on;
root /usr/share/nginx/html/public;
}
}

Optional SSL settings are commented out once again.

Recap

These are the files described above:

1
2
3
4
5
6
7
8
9
10
/home/app/email-app/
▾ config/
default.conf
app.rb
boom.erb
config.ru
docker-compose.yml
Dockerfile
Gemfile
index.html

Once everything is in place, fire ‘er up!

1
docker-compose up