Atlassian JIRA is the #1 software development tool used by agile teams, but its cost could be prohibitive for small business and for professionist. Fortunately there is a server version of JIRA that could be installed in your own EC2 instance and, with Hakuna Cloud, you will save most of its cost!

We are going to provide you configuration files and a list of commands that will let you run a self-hosted JIRA Server in 10 minutes!

Pre-requirements

In this guide we will use Amazon Web Services to create a cloud server and we will assume that you already have an AWS Account. If you have not, you can simply register to it and follow the rest of the guide.

You will also need the AWS CLI.

EC2 Configuration and installation

We believe in infrastructure as code, which means that any piece of our infrastructure is described in a configuration file and is created using that file. Any characteristic, option or setting is wrote in a YAML file. In that way we can reproduce our infrastructure simply running a few commands. There are different tools to code cloud infrastructure in a configuration file, like Terraform or CloudFormation.

This guide will use CloudFormation, the AWS Service to configure and manage AWS Resources. We will provide you a CloudFormation template to configure an EC2 instance, install Jira and PostgreSQL and to configure the DNS record.

CloudFormation calls “stack” a set of resources created from the same file, and we’ll use this definition from now on.

Stack parameters

The first piece of the template is the declaration of the parameters.

Parameters:
  ServiceName: 
    Type: String
    Default: "jira"
  DnsRootName:
    Type: String
    Default: "hakuna.dev"
  DnsZoneId:
    Type: String
    Default: "Z1NMATJ1U7G30Q"
  AvailabilityZone:
    Type: String
    Default: "eu-west-1c"
  PgPass:
    Type: String
    Default: "a long, long and secure password for the db"

In this part of the template we are defining that, in order to create all resources in the stack, some parameters are needed and we are defining some information for each parameter like type and a default value.

You can replace default value or pass it at creation time using the appropriate option --parameters [list] for the aws cli, as we will see later, and as stated in the create-stack documentation.

EC2 instance

This is the most interesting part of this guide. This piece will configure the EC2 instance and install PostgreSQL and JIRA on it. It’s pretty long piece, if you want more information about it, just scroll down!

 InstanceServer:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            DeleteOnTermination: false
            VolumeSize: 50
            VolumeType: standard

      IamInstanceProfile: !Ref InstanceProfile
      ImageId: ami-00035f41c82244dab
      InstanceInitiatedShutdownBehavior: stop
      InstanceType: t3.xlarge
      KeyName: devtools-dub
      Monitoring: false
      SubnetId: !ImportValue hakuna-devtools-vpc:subnet-eu-west-1c
      SecurityGroupIds:
        - !ImportValue hakuna-devtools-vpc:sgroup-l0-http
        - !ImportValue hakuna-devtools-vpc:sgroup-management
      Tags:
        - Key: Name
          Value: !Sub "${ServiceName}"
        - Key: stage
          Value: dev
        - Key: project
          Value: hakuna

      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash -x

            ##########################
            # Install postgresql 9.6 #
            ##########################
            echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" > /etc/apt/sources.list.d/pgdg.list
            wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
            apt-get update
            apt-get install -y postgresql-9.6
            sudo -u postgres psql -c "create database ${ServiceName};"
            sudo -u postgres psql -c "create user ${ServiceName} with encrypted password '${PgPass}';"
            sudo -u postgres psql -c "grant all privileges on database ${ServiceName} to ${ServiceName};"

            ##########################
            # nginx as reverse proxy #
            ##########################
            apt-get install -y vim wget nginx xmlstarlet

            # Wite nginx configuration
            cat << 'EOF' > /etc/nginx/sites-available/${ServiceName}.${DnsRootName}.conf
              server {
                  listen 80;
                  server_name ${ServiceName}.${DnsRootName};
                  location / {
                      proxy_set_header X-Forwarded-Host $host;
                      proxy_set_header X-Forwarded-Server $host;
                      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                      proxy_pass http://127.0.0.1:8080;
                      client_max_body_size 10M;
                  }
              }
            EOF

            ln -s /etc/nginx/sites-available/${ServiceName}.${DnsRootName}.conf /etc/nginx/sites-enabled/
            systemctl restart nginx

            # Install certbot
            add-apt-repository ppa:certbot/certbot
            apt-get update
            apt install -y python-certbot-nginx

            certbot --nginx --non-interactive --keep-until-expiring --agree-tos --email it@hakuna.cloud --redirect -d ${ServiceName}.${DnsRootName}

            ################
            # Install jira #
            ################
            cat << 'EOF' > /tmp/response.varfile
            #install4j response file for JIRA Software 7.13.0
            #Tue Dec 11 08:55:45 UTC 2018
            launch.application$Boolean=true
            rmiPort$Long=8005
            app.jiraHome=/var/atlassian/application-data/jira
            app.install.service$Boolean=true
            existingInstallationDir=/usr/local/JIRA Software
            sys.confirmedUpdateInstallationString=false
            sys.languageId=en
            sys.installationDir=/opt/atlassian/jira
            executeLauncherAction$Boolean=true
            httpPort$Long=8080
            portChoice=default
            EOF

            wget https://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-software-7.13.0-x64.bin
            chmod +x atlassian-jira-software-7.13.0-x64.bin
            ./atlassian-jira-software-7.13.0-x64.bin -q -varfile /tmp/response.varfile

            # Enable proxy
            xmlstarlet ed --inplace  --insert '/Server/Service/Connector' -t attr -n 'proxyName' -v "${ServiceName}.${DnsRootName}" /opt/atlassian/jira/conf/server.xml
            xmlstarlet ed --inplace  --insert '/Server/Service/Connector' -t attr -n 'proxyPort' -v "443" /opt/atlassian/jira/conf/server.xml
            xmlstarlet ed --inplace  --insert '/Server/Service/Connector' -t attr -n 'scheme' -v "https" /opt/atlassian/jira/conf/server.xml

            # Create systemd service file (jira internal scripts need init.d)
            /etc/init.d/jira stop
            cat << 'EOF' > /etc/systemd/system/jira.service
            [Unit]
            Description=Atlassian JIRA Software Server
            After=network.target postgresql.service

            [Service]
            Type=forking
            User=jira
            ExecStart=/opt/atlassian/jira/bin/start-jira.sh
            ExecStop=/opt/atlassian/jira/bin/stop-jira.sh
            ExecReload=/opt/atlassian/jira/bin/start-jira.sh | sleep 60 | /opt/atlassian/jira/bin/stop-jira.sh

            [Install]
            WantedBy=multi-user.target
            EOF
            rm /etc/init.d/jira
            systemctl enable jira
            systemctl start jira

            

The stack is long, but is divided in steps:

  • EC2 instance definition;
  • Installation of PostgreSQL, because JIRA need a DB;
  • JIRA listen on port 8080 by default. To add support for HTTPS, we install NGINX and configure it as a reverse proxy;
  • With xmlstarlet we update the configuraiton of JIRA, setting the values for the base url and port numbers;
  • Add an init file to start and stop jira as a daemon;

If you noticied the syntax ${ServiceName} maybe you are wondering about what it does mean. It is just an access to the value you have passed to the parameter called ServiceName in the first piece of the stack.

Record Set

This piece is going to route a FQDN hostname (eg. jira.hakuna.dev) to our instance IP, so we can reach the server using the hostname.

  InstanceDns:
    Type: AWS::Route53::RecordSetGroup
    DependsOn: InstanceServer
    Properties:
      Comment: !Sub "${ServiceName}"
      HostedZoneId: !Ref DnsZoneId
      RecordSets:
        - Name: !Sub "${ServiceName}.${DnsRootName}."
          ResourceRecords:
            - !GetAtt InstanceServer.PublicIp
          TTL: 300
          Type: A

This piece use some of previous defined parameters like ServiceName, DnsZoneId and DnsRootName.

ServiceName and DnsRootName are just used to compose the FQDN you want to use to reach JIRA.

DnsZoneId is the id of an already configured Dns Zone in AWS.

We are going to edit this piece of template at the end of this guide. Just keep it in mind!

Create the stack and make it real!

  • Download the entire Jira ClodudFormation Template
  • Open a terminal and place yourself on file path.
  • Run the following command in the terminal:
    aws cloudformation create-stack --stack-name jira-ec2 --template-body file://jira.yaml --parameters ParameterKey=ServiceName,ParameterValue=<ServiceName> ParameterKey=DnsRootName,ParameterValue=<DnsRootName> ParameterKey=DnsZoneId,ParameterValue=<DnsZoneId> ParameterKey=AvailabilityZone,ParameterValue=<AvailabilityZone> ParameterKey=PgPass,ParameterValue=<PgPass>
    
  • Wait until the stack is created. You can verify its status at the CloudFormation AWS Console
  • Go to http://<ServiceName>.<DnsRootName> to see it working!

Reduce the cost for the EC2 instance

JIRA is being used by our team during the standup in the morning, and maybe a couple of times in the day. It is very unlikely that someone accesses JIRA at nighttime, or during weekends. But we are developers, we do not have “business hours” and we have engineers that work during unusual periods.

It is reasonable to assume that our JIRA instance won’t be accessed more than 12 hours per day, which is roughly the 30% of the time in a month.

Hakuna Cloud is a tool that can help you, it automatically starts cloud servers, as our EC2 instance, when an HTTP request arrives, and stops them when they do not receive requests for 30 minutes (or any other configurable timeout). By stopping the instance when it is not required, the costs will just drop.

The configuration is easy.

  • Install the cli and signup/login;
  • Create an IAM user and register a new provider with hakuna provider create;
  • Configure the EC2 instance in Hakuna: hakuna vhost create.

The last step is to point our DNS record to our load balancer. Just update our dns like this:

jira.hakuna.dev CNAME eu1.lb.hakuna.cloud

(US users can use the load balancer in the US, na1.lb.hakuna.cloud)

That’s it!

Might also be interested in

If you want more details you can visit Hakuna Cloud web site or go deeply in our documentation reading following articles: