Indie DevOps - Deploying Your Applications (Part 3)
Welcome to the next part of the "Indie DevOps" series. This post will show you how to set up and deploy your applications with a simple
We will be using Dokku (a self-hosted heroku), which supports deploying with a Dockerfile or any of the following Heroku buildpacks.
- Grails 3.x
- Play 2.x
- Go and more...
Provisioning The Server
We are going to use DigitalOcean for all of our server needs. You can go with another VPS provider, however, keep in mind that DigitalOcean comes with a One-Click Dokku installer to make setup easy.
First, create a new Dokku instance on DigitalOcean. Here is a supplementary guide on how to set that up. For this guide, we will call the droplet "web" as this is where all of our web applications will be deployed (if we choose not to use Kubernetes).
Deploying an App with Dockerfiles
We almost always use Dockerfiles as opposed to buildpacks because they tend to deploy faster (due to layer caching) and are more portable (in case we need to host somewhere else or debug). If you want to use buildpacks, you can still follow the guide; just skip anything related to Docker and make sure to set up any custom buildpacks if you need. The rule of thumb is if you can push to Heroku, you can push to Dokku!
Before we do anything else, we need to create our application on the server so it is ready to accept a
git push. For this tutorial, our application will be called
myapp. To start, first
ssh into your server:
Once you're in, create your application with the following command:
dokku apps:create myapp
You can then do the following additional tasks if you need:
1. Add custom environment variables
Adding environment variables to your Dokku app can be done with the following command:
dokku config:set myapp DOPPLER_TOKEN="secret" ANOTHER_SECRET="shhh"
In our case, we use Doppler to manage our secrets, so we only need to set
To see all of the secrets for a certain app, we can run:
dokku config:show myapp
2. Add a custom domain
To add a custom domain, we can run the following command:
dokku domains:add myapp www.myapp.com
Then, you can check to see if it was added by running:
dokku domains:report myapp
If you enabled "virtual hosts" when you first set up Dokku, you may see additional domains listed here. If you don't need any of them, you can remove them with:
dokku domains:remove myapp unwanted.domain.com
3. Set up SSL certificates with LetsEncrypt
One of the best features of Dokku is that it has a Letsencrypt plugin to automatically provision and maintain SSL certificates for your domains. To get started, first run:
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
This will install the Letsencrypt plugin to your Dokku instance. Next, we need to provide an email address to Letsencrypt for the SSL certificate. We can do that through the following command:
dokku config:set --no-restart myapp DOKKU_LETSENCRYPT_EMAILfirstname.lastname@example.org
After that, we can provision the certificate:
dokku letsencrypt myapp
If it fails to generate, you may need to take a look at the logs to see what went wrong. If you are using Cloudflare, follow this guide.
Finally, we should set up Dokku to automatically renew the certificates with a CRON job:
dokku letsencrypt:cron-job --add
4. Set up Docker-based deployments
Dokku was initially created for Heroku-like deployments, however, we find that the Docker based deployment is faster, more portable, and easier to set up. If you intend to use Dokku exactly like Heroku, then please skip this step.
To set up Dockerfile based deployments, we just have to run one command to tell Dokku which port to map the webserver to:
dokku proxy:ports-add myapp http:80:5000
Dokku should automatically map port 443 for you, but it if it doesn't, you can do so manually with the same command. Be sure to replace 5000 with the port your app is going to run on.
If you intend to use Heroku-like deployments, please skip this section!
At ludicrous we mostly use Node.js so our Dockerfiles will be node-related. If you use another language, please refer to the best practices for creating Dockerfiles. There are plenty of online sources which you can copy-paste from.
To get started, create a
FROM node:alpine WORKDIR /app RUN apk add --no-cache build-base python curl RUN npm config set python /usr/bin/python RUN (curl -Ls https://cli.doppler.com/install.sh || wget -qO- https://cli.doppler.com/install.sh) | sh COPY package*.json ./ RUN npm ci COPY . . EXPOSE 5000 ENV PORT=5000 ENV NODE_ENV=production ENTRYPOINT ["doppler", "run", "--"] CMD ["npm", "start"]
There is a lot going on here so let's dissect the important parts:
The first few lines just pull the latest alpine image for node and install python which we some of our dependencies need.
Next, we install Doppler:
RUN (curl -Ls https://cli.doppler.com/install.sh || wget -qO- https://cli.doppler.com/install.sh) | sh
This will inject our environment variables when we start our application. If you haven't tried Doppler, we highly recommend checking it out; it has saved us a lot of time. We wrote another blog post on how we set up and use Doppler at ludicrous.
After installing Doppler, we copy our
package-lock.json (if you are using yarn, you will need to change this to
yarn.lock) and install the packages.
After installing dependencies, we move on to
COPY . ., which copies our entire source code into the container. Finally, we end by setting up our environment and providing the start command for our server.
After everything is configured correctly, the last thing we need to do is push to the Dokku repository. First, make sure to commit all your changes including your Dockerfile. Then, add the Dokku server as a remote with the following command:
git remote add dokku dokku@YOUR_SERVER_IP:myapp
And that's it! Run
git push dokku master
to deploy your application! You will need to make sure that your domain's DNS is configured properly to point to the DigitalOcean server, but once that is done you should be able to visit the URL and see your newly deployed application.
Dokku supports zero-downtime deployments so the next time you deploy, it will first set up everything in the background, ensure it's working, and then replace the live version of the site with the new version without losing any requests.