Deploy feature branches
If you use pull requests or merge requests as part of your workflow; making and deploying feature branches can be helpful for sharing testable versions.
For this example lets take a look at deploying a web app to a web-server, with Lets Encrypt on a wildcard certificate.
A static typescript-based web app. Need to install dependencies on the build server, run tests, build app. Need to point the domain to the feature distribution on a web-server.
https://my-feature-branch.feature.example.com
To get a wildcard domain the post on using lego
and DNS to authenticate your
domain. If you use certbot
you could use the a python plugin for your DNS
service.
lego --email name@example.com --domains *.feature.example.com run
See --dns
options for your DNS service to automate this. Also --http
and
--tcp
which also might work well to validate your domain if you already have a
primary domain (for example https://example.com
, or
http://example.com/.well-known/acme-challenge
) setup on this domain.
lego
, by default, stores your certificates in ./.lego/certificates/
. You can
define where you’d like these to go.
Then add to your web-server. Nginx, for example, supports regex in the site name, which you can use as a capture to modify where to look for files.
Note: Consider this regex as a vector to access other files, but limited to the running Nginx user (usually www-data). Limit to certain characters in your regex.
server {
server_name ~^(.*)\.feature\.example\.com$;
listen 443 ssl http2;
listen [::]:443 ssl http2;
access_log /var/log/nginx/wildcard.access.log main_ext;
root /usr/lib/$1;
location / {
try_files $uri $uri/ =404;
}
ssl_certificate /home/letsencrypt/.lego/certificates/_.example.com.crt;
ssl_certificate_key /home/letsencrypt/.lego/certificates/_.example.com.key;
}
Now you need somewhere to point to in the deployment.
This regex gives you a feature match (generous matching) with $1
. You can use
this in your root
path to point to where you deployed you feature branch.
Showcasing Gitlab runner CI but most build systems working with git should allow
you to do jobs based on branch name or type of branch (merge_request
,
pull_request
, feature/.*
).
For example, gitlab-ci.yml
feature-build:
stage: build
script:
- make
- tar -czf $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz release/target
artifacts:
paths:
- $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz
only:
- merge_requests
We create a build using make
and then tarball it up into an artifact using
$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
.
deploy_feature_site:
stage: deploy
image: kroniak/ssh-client
before_script:
- mkdir -p ~/.ssh
- echo -e "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- chmod 600 ~/.ssh/*
script:
- echo "Deploy $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz"
- scp $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz
user@example.com:/www/builds/
- ssh user@example.com "mkdir -p
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME && tar -xf
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz --directory
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
- ssh user@example.com "mkdir -p
/www/release/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME/ && ln -sfn
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME/release/target/$CI_COMMIT_SHORT_SHA/
/www/release/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME/www"
environment:
name: feature/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
url: https://$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME.feature.example.com
only:
- merge_requests
Here we deploy using ssh
and scp
on a small ssh-client container. You may
find deploying into a container and then to a private registry might be easier.
image: kroniak/ssh-client
Needs a SSH_PRIVATE_KEY
of your deploy key on the server to be set in the
Gitlab Project Settings, Variables.
before_script
- mkdir -p ~/.ssh
- echo -e "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- chmod 600 ~/.ssh/*
Then deploy artifact using scp
.
- scp $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz \
user@example.com:/www/builds/
Deploying artifact into /www/builds
but I would suggest making a pattern that
works for you.
Deploy to a new directory with the commit so each commit has a fresh release.
- ssh user@example.com "mkdir -p
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME && tar -xf
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-build.tar.gz --directory
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
/www/builds/my-feature-branch/release/target/29e09d023
Then link to the release to the newly deployed artifact.
- ssh user@example.com "mkdir -p
/www/release/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME/ && ln -sfn
/www/builds/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME/release/target/$CI_COMMIT_SHORT_SHA/
/www/release/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME/www"
/www/release/my-feature-branch/www -> /www/builds/my-feature-branch/release/target/29e09d923
Downside of this process is there is no cleanup of deployed artifacts and needs cleaning up of older artifacts. Having a history of artifacts allows for rapid downgrading for failure cases. Another alternative is using containers which handle this invalidation a little better.
Last bit is updating your Nginx config to the place you chose.
root /www/release/$1/www;
Then reload Nginx.
sudo nginx -s reload
And then you should be able to see your released static web app.
https://my-feature-branch.feature.example.com
Note: Your feature branches are open to the public. Consider how you might want to handle this.