Update: This article is no longer relevant. The NOW API referred to below is depcrecated and I've since moved on to running Dokku on a VPS.
I recently had the opportunity to play around with zeit.co's deployment service (also where this blog is hosted). I used Gitlab's pipelines to set up a docker container deployment workflow that includes automated review and staging builds and a manual step for deploying master to production.
Update: I edited the gitlab pipeline to clean previous deployments and name deployments with prefixed $APPS_DOMAIN in order to have a less confusing list on zeit.co's deployment list. Also, all but production builds now clean previous deployments automatically on a new deployment. The $NOW_REGION variable is no longer necessary, instead all region specific settings are in now.json using "scale".
Update 2: now updated to use their serverless Docker infrastructure. Handle no existing deployments when trying to remove old deployments. Dockerfile: remove devDepencencies after build.
For this to work properly, you need to be able to run now from your local machine (e.g. now.json config or a now-key in package.json and a Dockerfile). If your deployment runs fine locally, it will - quite probably - do so in a Gitlab runner instance.
While testing zeit.co without applying any scaling I noticed that the TTFB is significantly higher than with my previous hosting environment. Somewhere in a constant 800ms range - from brussels. And I guess this is why: containers on now that do not have scaling configured will be frozen after a certain timeout. With scaling configured (e.g. in now.json), TTFB is almost halved. I guess that non-scaling deployments are behind a different load balancer setup than scaled deployments. Or something like that…
I switched off zeit.co's domain CDN to save a few bucks (nobody cares if this blog loads in 400ms or 1.5s…), turned on scaling and voila, the datacenter in Brussels seems to be doing just fine for European traffic. I ran a few tests on pingdom.com via San Jose and the TTFB from the eastern US is about 2.5s. I'm not concerned with that currently but might extend my config to spin up 2 instances in SFO and BRU, just to see what would be possible.
now.json: name the project the same as $APP_DOMAIN-production in order for it to pick up aliases. I set the region to "bru1" for europe and set "scale" to minimum 1 instance and maximum 3. Set this according to your requirements.
now.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
{ "name": "genox.ch-production", "alias": [ "genox.ch", "www.genox.ch" ], "type": "docker", "scale": { "bru1": { "min": 0, "max": "auto" }, "sfo1": { "min": 0, "max": "auto" } }, "features": { "cloud": "v2" } }
.gitlab-ci.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
image: node:8-alpine stages: - review - staging - production variables: NOW_TEAM: "" NOW_SECRET: "--token=${NOW_TOKEN}" NOW_BUILD_PARAMS: "--force" before_script: - npm install -g now --silent --unsafe-perm review: stage: review script: - echo "Deploying app to [review] environment as $APPS_DOMAIN-$CI_BUILD_REF_SLUG" - HAS_DEPLOYMENTS=$(now $NOW_SECRET $NOW_TEAM ls | grep -q ${APPS_DOMAIN}-${CI_BUILD_REF_SLUG} && echo 1 || echo 0) - NOW_DEPLOYMENT=$(now ${NOW_SECRET} ${NOW_TEAM} ${NOW_BUILD_PARAMS} -n ${APPS_DOMAIN}-${CI_BUILD_REF_SLUG}) - sleep 10 - now $NOW_SECRET $NOW_TEAM alias set $NOW_DEPLOYMENT $CI_BUILD_REF_SLUG.$APPS_DOMAIN - if [[ $HAS_DEPLOYMENTS -gt 0 ]]; then now $NOW_SECRET $NOW_TEAM rm $APPS_DOMAIN-$CI_BUILD_REF_SLUG --safe --yes; fi environment: name: review/$CI_BUILD_REF_NAME url: https://$CI_BUILD_REF_SLUG.$APPS_DOMAIN on_stop: stop_review only: - branches except: - master stop_review: stage: review script: - HAS_DEPLOYMENTS=$(now $NOW_SECRET $NOW_TEAM ls | grep -q ${CI_BUILD_REF_SLUG} && echo 1 || echo 0) - echo "Removing $APPS_DOMAIN-$CI_BUILD_REF_SLUG from [review] environment" - now $NOW_SECRET $NOW_TEAM alias rm $CI_BUILD_REF_SLUG.$APPS_DOMAIN - if [[ $HAS_DEPLOYMENTS -gt 0 ]]; then now $NOW_SECRET $NOW_TEAM rm $APPS_DOMAIN-$CI_BUILD_REF_SLUG --safe --yes; fi variables: GIT_STRATEGY: none when: manual environment: name: review/$CI_BUILD_REF_NAME action: stop staging: stage: staging script: - echo "Deploying app to [staging] environment as $APPS_DOMAIN-staging" - HAS_DEPLOYMENTS=$(now $NOW_SECRET $NOW_TEAM ls | grep -q staging && echo 1 || echo 0) - NOW_DEPLOYMENT=$(now ${NOW_SECRET} ${NOW_TEAM} ${NOW_BUILD_PARAMS} -n ${APPS_DOMAIN}-staging) - sleep 10 - now $NOW_SECRET $NOW_TEAM alias $NOW_DEPLOYMENT staging.$APPS_DOMAIN - if [[ $HAS_DEPLOYMENTS -gt 0 ]]; then now $NOW_SECRET $NOW_TEAM rm $APPS_DOMAIN-staging --safe --yes; fi environment: name: staging url: https://staging.$APPS_DOMAIN only: - master production: stage: production script: - echo "Deploying app to [production] environment as $APPS_DOMAIN-production" - HAS_DEPLOYMENTS=$(now $NOW_SECRET $NOW_TEAM ls | grep -q production && echo 1 || echo 0) - NOW_DEPLOYMENT=$(now ${NOW_SECRET} ${NOW_TEAM} ${NOW_BUILD_PARAMS} -n ${APPS_DOMAIN}-production) - sleep 10 - now $NOW_SECRET $NOW_TEAM alias environment: name: production url: https://$APPS_DOMAIN when: manual only: - master
It takes a while to build during rush hours (well, I'm using Gitlab's free runners, so I don't expect any miracles). But all in all, this is a very simple, very effective way to run some minor devops for a nextjs app.
For completeness, here's my Dockerfile:
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
FROM node:8-alpine ENV NPM_CONFIG_LOGLEVEL warn ARG NODE_ENV ENV NODE_ENV=${NODE_ENV} ARG HOSTNAME ENV HOSTNAME=${HOSTNAME} ARG NOW_URL ENV NOW_URL=${NOW_URL} ARG NOW_REGION ENV NOW_REGION=${NOW_REGION} ARG NOW_DC ENV NOW_DC=${NOW_DC} ARG NOW ENV NOW=${NOW} COPY . ./next/ WORKDIR /next RUN npm config set color false &&\ npm config set unsafe-perm true &&\ npm install -g npm &&\ npm run next:install &&\ npm run next:build &&\ rm -rf ~/.npm && npm prune --production CMD npm run next:start EXPOSE 80
Photo by Scott Webb on Unsplash