If you like long lists of real world learnings without all the flowery words, this is for you.
I’ve been pottering about with Elm for a few years and Go for just over a year but hadn’t had the chance to play with GitLab. I had the opportunity recently to build out an Elm SPA with a Go backend so I thought I’d give GitLab a crack to learn from their approach and baked in CI/CD.
This post is more or less a chronological list (split into Elm and Go) of all the things I got wrong (largely by skim reading documentation 😳) or things I learnt along the way. The stack is basically an Elm SPA hosted on GitLab pages with a Go web app using gin deployed on EKS.
- First off I wasted a lot of time trying to understand GitLab runners which the docs seemed to insist on but it turns out it just sets up a shared runner for you when you push your first GitLab yaml file 😖
- Generally speaking Elm and it’s libraries suggest installing globally so I had to go back and add anything Elm related as npm dev deps
- I typically use
npxto use npm modules from a
Makefile, things break when you forget to add it
- So I had Cypress working locally and it also worked in my
nodedocker image but on GitLab it couldn’t find the Cypress binary no matter which path I tried to cache…some time passed before I used the Cypress docker image instead which comes with all it’s dependencies
cypress/base:latestisn’t a thing….don’t just make up docker images 😂
- This still didn’t solve my issues with caching the Cypress binary though, it needs some magic variables to be set, so follow the kitchen sink example for caching
- I used elm-live for testing locally so also repurposed it for running Cypress in CI which worked locally…I didn’t look too deeply into it but the way it was running in the docker container seemed to block Cypress from running so I used local-web-server instead (which turned out to be faster)
- GitLab pages uses the public folder for it’s assets, so I just pushed everything there for local dev too to make it easier to test the actual location when developing
- When I made silly mistakes it was often hard to see why the GitLab job failed, so split out your scripts in your GitLab yaml to make it clearer which one failed
- When passing
JSONas a CLI flag use single quotes for
JSONand surround with double quotes (this was to configure pug with my env vars)
- GitLab pages needs a job called
pagesto work, that’s it
- If you want a SPA on GitLab pages without a custom domain it will need to support the project name as a prefix to all your SPA paths
- For any SPA on GitLab pages you’ll need a 404.html which is the same as your index.html to capture visits directly to a URL that is not your project root
- Assets with a
gzipsuffix will be served by GitLab where possible
- Caching in GitLab can be a small footgun, sometimes you don’t need it but it will waste time in your CI anyway at the end of a job so double check what your jobs are actually doing
- Cypress on CI was a little flaky and sometimes timed out (even with parallel jobs) — needed to use
cy.wait()before testing a
POSTrequest body for example
- Parallel Cypress jobs need
— recordflags which also need a linked account (reading the docs rather than adding each bit individually and finding the next failure is probably quicker…)
- Parallel Cypress needs specs in multiple files (it doesn’t magically split by test suite…)
- Elm needs
Http.riskyRequestto pass a cookie to another domain (allows withCredentials)
- When creating an EKS cluster with GitLab’s out of the box k8s setup you need to use the role profile they give you in the docs for authentication which is different to the service role profile further down.
It’s super clear in a blog but is a footnote in the docs which I missed (I didn’t discover the blog post until much later). I wasted an unfortunate amount of time on this due to GitLab just saying “ROLLBACK failed” and Cloudformation saying “profile not trusted”. GitLab was just surfacing the fact the security group couldn’t be deleted due to lack of permissions and Cloudformation was alluding to the fact the role didn’t have the right EKS perms.
The service role needs the out of the box EKS policies (
- The created profile for authentication by GitLab also “secretly” has assume role limited to the root user. It took (an embarrassing amount of) time to realise why I couldn’t assume the role for kubectl on my machine. If you click “Edit trust relationship” on the “Trust relationships” tab of the GitLab authentication role you can add another user to assume the role
- Choosing 3 x
t3.nanoto save money was…silly…I couldn’t then install the out-of-the-box ingress on it…GitLab doesn’t seem to create a Node Group for your cluster and manually swapping out the worker nodes caused a bit of an issue because tiller was then not aware of them. I wasn’t sure the best way to fix this so I just redeployed the whole thing again
- Using Auto DevOps was largely painless but with a couple of gotchas. First off I didn’t expose my app to port 5000 so it fell over
- Then I didn’t have a root path for health checks…
- While GitLab does do Autodevops with Heroku builds pretty well, it obviously doesn’t make the smallest image it can which you can get from a custom Dockerfile
golang.org/x/oauth2you need to use a
JSONobject for the
statefield to pass back and forth info like the redirect URL you’d like to use
- I accidentally set
GIN_MODE=releaseat the build step rather than as an envar, check your logs…
While I think there are some nice ideas such as Auto DevOps and it gives you very powerful CI/CD controls I did find the docs incredibly fragmented. Either differing information exists on their own site in different places or blogs were better than the docs. Sometimes I found the getting started guides lacking and the docs themselves too over the top without anything end-to-end. In general there’s a huge dearth of info so you have to rely on the docs meaning Googling errors is a non-starter which perhaps isn’t an issue with GitHub.
If you have a small project you want to setup quickly with free private repos I think it’s a great option.
Happy to keep saying it, I think it’s hands down the best way to write a front-end. I came across this tweet while writing the SPA and can only say Elm abstracts everything away for you. Creating routes and handling states with pattern matching almost gets boring. When I hadn’t looked at the codebase for a while I could still return to it and be productive which I can’t say for a TypeScript app. The type system and standard patterns mean you don’t have to re-invent the wheel and can easily refactor led by the compiler.
Not much to say really other than it never gets in the way and it’s easy to use. Using gin sessions was very straightforward, perhaps the boiler plate needed to marshall and unmarshall
JSON can be a little laborious.