Visit https://forwardemail.net to get started!
See Requirements below.
Install brew dependencies:
brew tap mongodb/brew brew install [email protected] redis python3 ansible ansible-lint libtool automake autoconf nasm brew services start [email protected] brew services start redis pip3 install dkimpy pyspf dnspython
Install ansible-galaxy requirements (assumes current working directory is the root of this repository):
ansible-galaxy install -r ansible/requirements.yml
Install pm2 which is used for deployment:
npm i -g pm2
Install npm dependencies:
Our server alias naming convention consists of the following fields, joined together by a hyphen, and converted to lower case:
For example, one of our web servers is named
web-1-do-nyc3-us.forwardemail.net, and one of our API servers is named
All server aliases with the same hostname (with a minimum count of at least 2) are set in Cloudflare under a geo-located load balancer.
Unless otherwise noted, all of the servers should have dedicated CPU's and not be running in a shared CPU environment.
Also note that ansible-lint is a helpful linting tool you can use if you plan on making changes to playbooks. Note that our current playbooks have several existing lint errors.
Follow the Deployment guide below for automatic provisioning and deployment instructions.
Set up host configuration by copying the
hosts.yml file template:
cp ansible/playbooks/templates/hosts.yml hosts.yml
Edit this configuration and update the file with your newly created server aliases and IP addresses. You can add more than one host to each group if you are setting up load balancing. Refer to the Naming Convention documentation for our recommended approach to server alias naming. Note that this file is automatically ignored by git. If you have a private repository and would like to commit this, then remove
hosts.yml from the root
Set up environment configuration by copying the
env file template:
cp ansible/playbooks/templates/env .env.production
Edit this configuration and reference the official Lad documentation for a list of all available environment variables (or see .env.defaults). You will need to open this file in your preferred editor and set the values for any fields containing
TODO, whereby you replace
TODO with the appropriate value. Preserve double quotes where they are already defined.
Generate pm2 ecosystem files using our automatic template generator. We created an ansible-playbook.js which loads the
.env.production environment variables rendered with @ladjs/env into
process.env, which then gets used in the playbooks. This is a superior, simple, and the only known dotenv approach we know of in Ansible. Newly created
ecosystem-web.json files will now be created for you in the root of the repository. If you ever more add or change IP addresses, you can simply re-run this command.
node ansible-playbook ansible/playbooks/ecosystem.yml -l 'localhost'
Set up the web and API server(s) (see patterns and ansible-playbook flags docs if you need help). If you completely (or partially) run this playbook (or any others below), then the second time you try to run it may not succeed. This is because we prevent root user access through security hardening. To workaround this, run the same command but without
-e 'ansible_user=root' appended as it will default to the
devops user created.
node ansible-playbook ansible/playbooks/http.yml -e 'ansible_user=root' -l 'http'
Set up the Bree server(s):
node ansible-playbook ansible/playbooks/bree.yml -e 'ansible_user=root' -l 'bree'
Set up the Redis server:
node ansible-playbook ansible/playbooks/redis.yml -e 'ansible_user=root' -l 'redis'
Set up the Mongo server:
node ansible-playbook ansible/playbooks/mongo.yml -e 'ansible_user=root' -l 'mongo'
Set up GitHub deployment keys for all the servers. Note that the
deployment-keys directory is ignored from git, so if you have a private repository and wish to commit it, then remove
deployment-keys from the
node ansible-playbook ansible/playbooks/deployment-keys.yml -l 'http:bree'
Go to your repository "Settings" page on GitHub, click on "Deploy keys", and then add a deployment key for each servers' deployment key copied to the
deployment-keys directory. If you're on macOS, you can use the
pbcopy command to copy each file's contents to your clipboard. Use tab completion for speed, and replace the server names and paths with yours:
cat deployment-keys/api-1-li-dal.forwardemail.net.pub | pbcopy # # NOTE: repeat the above command for all servers # and after running the command, it will copy # the key to your clipboard for you to paste as # a new deploy key (make sure to use read-only access) #
Set up PM2 deployment directories on all the servers:
pm2 deploy ecosystem-web.json production setup
pm2 deploy ecosystem-api.json production setup
pm2 deploy ecosystem-bree.json production setup
Create a SSL certificate at Namecheap (we recommend a 5 year wildcard certificate), set up the certificate, and download and extract the ZIP file with the certificate (emailed to you) to your computer. We do not recommend using tools like LetsEncrypt and
certbot due to complexity when you have (or scale to) a cluster of servers set up behind load balancers. In other words, we've tried approaches like
lsyncd in combination with
certbot renewals and automatic checking. Furthermore, using this exposes the server(s) to downtime as ports
443 may need to be shut down so that
certbot can use them for certificate generation. This is not a reliable approach, and simply renewing certificates once a year is vastly simpler and also makes using load balancers trivial. Instead you can use a provider like Namecheap to get a cheap SSL certificate, then run a few commands as we've documented below. This command will prompt you for an absolute file path to the certificates you downloaded. Renewed your certificate after 1 year? Simply follow this step again. Do not set a password on the certificate files. When using the
openssl command (see Namecheap instructions), you need to use
*.example.com with an asterisk followed by a period if you are registering a wildcard certificate.
Important: If you renew or change certificates in the future, then after running the previous command, you will subsequently need to reload the processes as such:
# # NOTE: See the "Important" note above BEFORE running this command. # This command ONLY APPLIES for certificate renewals/changes. # pm2 deploy ecosystem-web.json production exec "pm2 reload web" pm2 deploy ecosystem-api.json production exec "pm2 reload api"
(Optional) Create a Google application credentials profile file and store it locally. You only need this if you want to support automatic translation. The following command will prompt you for the absolute file path (e.g.
/path/to/client-profile.json). See the mandarin docs for more information.
ansible-playbook ansible/playbooks/gapp-creds.yml -l 'http:bree'
.env.production file and create an AWS config file on the servers:
node ansible-playbook ansible/playbooks/env.yml -l 'http:bree'
Run an initial deploy to all the servers:
pm2 deploy ecosystem-web.json production
pm2 deploy ecosystem-api.json production
pm2 deploy ecosystem-bree.json production
Save the process list on the servers so when if the server were to reboot, it will automatically boot back up the processes:
pm2 deploy ecosystem-web.json production exec "pm2 save"
pm2 deploy ecosystem-api.json production exec "pm2 save"
pm2 deploy ecosystem-bree.json production exec "pm2 save"
Test by visiting your web and API server in your browser (click "proceed to unsafe" site and bypass certificate warning).
Configure your DNS records for the web and API server hostnames and respective IP addresses.
Test by visiting your web and API server in your browser (in an incognito window). There should not be any certificate warnings (similar to the one that occurred in step 15).
(Optional) Remove the local
.env.production file for security purposes. If you do this, then make sure you have a backup, or securely back up off the server in the future before destroying the server.
(Optional) Remove the local certificate files you downloaded locally and specified in step 11. If you do this, then make sure you have a backup, or securely back up off the server in the future before destroying the server.
Finished. If you need to deploy again, then push your changes to GitHub
master branch and then follow step 14 again. We recommend you to read the Ansible getting started guide, as it provides you with insight into commands like
ansible all -a "echo hello" which can be run across all or specific servers.