It’s been a while since I’ve added any articles to this blog. This has been due to working flat out on MultiBit HD - a Bitcoin startup project.
As part of that work I needed to add a Dropwizard project to an EC2 box fronted by nginx. As usual I wanted full automation so the whole build and deploy process was to take place on the server (it’s only a small build) in response to a git push. I reviewed my earlier work but found that it didn’t give a complete set of instructions for setting up on a bare bones server, so I thought I’d add some.
I’ll assume that you have an AWS account in place so this is just a case of commissioning a box. Naturally this process could be scripted but sometimes just having the manual steps available can be useful.
Use AWS to set up your bare bones EC2 server
Create a bare bones Ubuntu instance on the Free tier (micro should be fine for development projects).
Do the following with the examplekey.pem
file for the EC2 instance from Security Key Pair:
$ mv examplekey.pem ~/.ssh
$ chmod 400 ~/.ssh/example.pem
Ensure the SSH agent is running on your local machine, and add the key
$ exec ssh-agent
$ ssh-add ~/.ssh/examplekey.pem
Configure for HTTP, SSH, HTTPS as usual
Do the usual AWS security settings to enable HTTP, SSH and HTTPS
Login with the ubuntu
user
$ ssh -i .ssh/examplekey.pem ubuntu@1.2.3.4
Install the infrastructure
$ sudo -i
$ apt-get upgrade
$ apt-get update
Install JDK7, SSL, curl and nginx in one hit
$ apt-get install openjdk-7-jdk openssl curl nginx
Key files for later:
/usr/share/nginx/www
/etc/nginx/nginx.conf
/etc/nginx/sites-available/<host name>
Verify nginx
Visit http://1.2.3.4
You should see the standard nginx message
Install Maven
$ apt-get install maven
Verify with:
$ mvn --version
Key files for later:
/usr/bin/mvn
/usr/share/maven
/etc/maven
Edit Maven global settings to use a shared repository location for all users
$ vi /etc/maven/settings.xml
And then paste in the following:
<settings>
...
<!-- The local repository -->
<localRepository>/var/maven/repository</localRepository>
...
</settings>
Set up the repository directory structure
$ mkdir /var/maven/repository
$ chown -R ubuntu:ubuntu /var/maven/repository
Install git
$ apt-get install git
Verify that the project can be checked out and built by a local user
$ exit
$ cd ~
$ git clone https://github.com/example/example-dropwizard.git
$ cd example-dropwizard
$ git checkout example-branch
$ mvn clean install
Verify project startup
$ java -jar target/example-dropwizard-version.jar server example.yml
Expect to see a clean startup with banner
CTRL+C to interrupt
Prepare your server rollbacks
As root, set up the following directory structure
$ sudo -i
$ cd /var
$ mkdir git
$ mkdir www
$ mkdir /var/www/backups
$ mkdir /var/www/html
$ chown -R ubuntu:ubuntu /var/git
$ chown -R ubuntu:ubuntu /var/www
$ exit
Prepare a bare git repo
As ubuntu, create a bare repo that will receive client pushes. It must be bare otherwise the remote refs will get conflicted and git will complain.
$ cd /var/git
$ git clone --bare https://github.com/example/example-dropwizard.git
Prepare the post-receive git hook
The deployment magic happens using git hooks. In this case you need the post-receive hook, so do the following
$ cd /var/git/example-dropwizard/.git/hooks
$ vi post-receive
Populate it as follows:
#!/bin/bash
@echo off
# This script will live in git repo as .git/hooks/post-receive
targetdir=/var/git/example-dropwizard
# Target/working directory
cd $targetdir
# Stop the existing service (this may take some time so we do it before the Maven build)
echo Performing a soft kill of the existing process
pkill -f example-dropwizard-develop-SNAPSHOT
# Check out the local copy of the git repo
echo "Check out local copy"
export GIT_WORK_TREE=/var/git/example-dropwizard
export GIT_DIR=/var/git/example-dropwizard
cd $GIT_WORK_TREE
# The push will go from client 'develop' branch to server 'master' for convenience
git checkout -f
# Build with Maven
echo Maven build the new code
mvn clean package
# Start the new background process
echo Starting the new process under nohup
nohup java -jar target/example-dropwizard-develop-SNAPSHOT.jar server example.yml > /var/log/example/example-log.log 2>&1 &
echo Complete. Verify operation by visiting http://example.org/
Make sure that the script is executable with the proper ownership for the SSH login (usually ubuntu or ec2-user)
$ chmod +x post-receive
Prepare your local SSH setup
This will ensure that git can find it for authentication later on.
Git may get confused when deciding which key to use, so use this “belt and braces” approach by creating (or updating) an SSH config file
You can infer the public HostName from the AWS console IP address even without an Elastic IP.
$ vi ~/.ssh/config
The contents should look like this:
Host ExampleHost
HostName ec2-1-2-3-4.eu-west-1.compute.amazonaws.com
User ubuntu
IdentityFile ~/.ssh/examplekey.pem
IdentitiesOnly yes
Add the remote repo to your local repo
Add a new remote repo called “production” to your local git repo based on the SSH path to the remote repo on the EC2 instance.
$ cd <path to local repo>/example-dropwizard/.git
$ git remote add production ExampleHost:/var/git/example-dropwizard
Manually review what git has done to be sure that there is no confusion with keys
$ cd .git
$ vi config
Add the following:
[remote "production"]
url = ExampleHost:/var/git/example-dropwizard
fetch = +refs/heads/*:refs/remotes/production/*
To get the above to work, you’ll need to have your examplekey.pem registered as ExampleHost with SSH (see earlier) or you’ll get weird authentication errors. If you’re really struggling you can attempt the SSH authentication manually with OpenSSL
$ ssh -v -i ~/.ssh/examplekey.pem ubuntu@1.2.3.4
This will shine a light on any problems that you’ll otherwise be scratching your head over.
Do the first push
If no-one has ever done this do an initial push of all data in the master branch (assuming that’s where your production site content lives)
$ git push production +example-branch:refs/heads/master
For large repos over thin pipes this may take some time. You must have a bare remote repo (see earlier) otherwise git will starting whining at you. If you need to convert a repo back to a bare one then do the following on the server repo, but you’ll lose any files checked out from the remote repo.
$ cd /var/git/example-dropwizard
$ mv .git .. && rm -rf *
$ mv ../.git .
$ mv .git/* .
$ rmdir .git
$ git config --bool core.bare true
Link Dropwizard to nginx with additional static files
As root, do the following:
$ sudo -i
$ vi /etc/nginx/conf.d/example-dropwizard.conf
Add the following:
# Site (port 80 -> 8080)
server {
listen 80; # Listen on port 80 for IPv4 requests
server_name localhost;
access_log /var/log/nginx/site_access.log;
error_log /var/log/nginx/site_error.log;
# Set the root of the static content
root /var/www/html;
# Redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/html;
}
# Filter static content types and serve from the root
location ~*\.(jpg|jpeg|gif|css|png|js|ico|html)$ {
access_log off;
expires max;
}
# Serve the dynamic content
location / {
# The application provides its own detailed logs
access_log off;
# Hand over to the application
proxy_pass http://localhost:8080/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
By placing the file in /etc/nginx/conf.d
with a .conf
extension, nginx will automatically link to it. However you will probably have to disable the sites-enabled include in
/etc/nginx/nginx.conf
to override the default server.
Restart nginx (as root) with
$ /etc/init.d/nginx restart
to get the following behaviour:
- / will serve the root of
example-dropwizard
- Any static files are filtered on common extensions (
.jpg
,.png
etc) - Any other content is directed to the app serving on port 8080
- Any 50x error generated by the app will cause a redirect to the
/50x.html
static file
The usual workflow
Make your changes locally, stage and commit as usual then use
$ git push production example-branch:master
This will cause the following behaviour
- The local
example-branch
branch will be pushed to the remote master branch - The site will go offline almost immediately (not so good for high availability sites)
- A Maven build will take place and be seen in the console
- The site will be restarted as a
nohup
process
Conclusion
So there you have it. A simple set of instructions to get your Dropwizard project fronted by nginx from scratch in just a few minutes.