Error when deploy with Gitlab and Docker

Hi :slight_smile:

I’m a beginner with Docker and Gitlab CI/CD and i’m trying to deploy an Elixir app with Docker and Gitlab ci/cd but i’m facing some issues.
I tried several solutions and read lot of doc and threads but i’m stucked :pensive:

It’s probably a problem with my Dockerfile, i tested one Dockerfile that works in Gitlab for build stage and not for deploy stage, and another Dockerfile that works for deploy stage but not for build one.

Below the Dockerfile which works for build stage :

FROM debian:buster

RUN apt-get update && apt-get install -y \
     locales \
     curl \
     make \
     p7zip \
     wget \
     musl \
     build-essential \
     ca-certificates \
     gcc \
     libtool-bin \
     libsm6 \
     inotify-tools \
     zip \
     && apt-get purge -y --auto-remove $buildDeps \
     && rm -rf /var/lib/apt/lists/*

RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
     dpkg-reconfigure --frontend=noninteractive locales && \
     update-locale LANG=en_US.UTF-8

ENV LANG en_US.UTF-8

RUN wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb \
     && dpkg -i erlang-solutions_2.0_all.deb \
     && apt-get update && apt-get install -y \
       esl-erlang \
       elixir \
     && apt-get purge -y --auto-remove $buildDeps \
     && rm -rf /var/lib/apt/lists/* \
     && elixir -v

RUN mix local.hex --force \
     && mix local.rebar --force

WORKDIR /app

ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"

and then the one that works for deploy but build failed (the error is that mix is command not found :

ARG ELIXIR_VERSION=1.13.2
ARG OTP_VERSION=24.3.2
ARG DEBIAN_VERSION=bullseye-20210902-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${BUILDER_IMAGE} as builder

RUN apt-get update && apt-get install -y \
    locales \
    curl \
    make \
    p7zip \
    wget \
    musl \
    build-essential \
    ca-certificates \
    gcc \
    libtool-bin \
    libsm6 \
    inotify-tools \
    zip \
    && apt-get purge -y --auto-remove $buildDeps \
    && rm -rf /var/lib/apt/lists/*

RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
    dpkg-reconfigure --frontend=noninteractive locales && \
    update-locale LANG=en_US.UTF-8

ENV LANG en_US.UTF-8

RUN wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb \
    && dpkg -i erlang-solutions_2.0_all.deb \
    && apt-get update && apt-get install -y \
      esl-erlang \
      elixir \
    && apt-get purge -y --auto-remove $buildDeps \
    && rm -rf /var/lib/apt/lists/* \
    && elixir -v

RUN mix local.hex --force \
    && mix local.rebar --force

WORKDIR /app

ENV MIX_ENV="prod"

COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

COPY lib lib

COPY assets assets

RUN mix assets.deploy

RUN mix compile

COPY config/runtime.exs config/

COPY rel rel
RUN mix release

FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
   && apt-get purge -y --auto-remove $buildDeps \
   && rm -f /var/lib/apt/lists/*_*

RUN apt-get update && apt-get install -y \
    locales \
    curl \
    make \
    p7zip \
    wget \
    musl \
    build-essential \
    ca-certificates \
    gcc \
    libtool-bin \
    libsm6 \
    inotify-tools \
    zip \
    && apt-get purge -y --auto-remove $buildDeps \
    && rm -rf /var/lib/apt/lists/*

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR "/app"
RUN chown nobody /app

# set runner ENV
ENV MIX_ENV="prod"

COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/ze_cats_dev ./

USER nobody

CMD ["/app/bin/server"]
# # Appended by flyctl
ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"

My gitlab-ci.yml

stages:
  - build
  - test
  - deploy

test:
  stage: test
  before_script:
    - apt-get update -qq && apt-get install -y curl
    - mix local.rebar --force
    - mix deps.get --only test
  script:
    - mix test

app_image:
  image: docker
  services:
    - docker:dind
  only:
    - not_run 
  variables:
    MIX_ENV: "prod"
  stage: build
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
    - docker build --no-cache -f Dockerfile -t <my-private-gitlab-registry-container>:latest .
    - docker push <my-private-gitlab-registry-container>:latest

elixir:
  stage: build
  image:<my-private-gitlab-registry-container>:latest
  cache:
    key: ${CI_COMMIT_REF_SLUG}__app
    paths:
      - _build/
      - deps/
  artifacts:
    paths:
      - _build/prod/rel
  script:
     - make qa
    - make build-prod

deploy application:
  image: docker:latest
  stage: deploy
  services:
    - docker:dind
  variables:
    DOCKER_DRIVER: overlay
  before_script:
    - apk add curl
    - curl -L https://fly.io/install.sh | sh
  script:
    - /root/.fly/bin/fly auth login
    - /root/.fly/bin/flyctl deploy --local-only

I use a Makefile

qa:
	MIX_ENV=test mix local.rebar --force
	MIX_ENV=test mix deps.get

build-prod:
	rm -Rf _build/prod/rel
	MIX_ENV=prod mix local.rebar --force
	MIX_ENV=prod mix deps.get --only prod
	MIX_ENV=prod mix compile
	MIX_ENV=prod mix assets.deploy
	MIX_ENV=prod mix release --force

Anyone has an idea of how i can solve my problem ?

There’s sort of a lot going on here, and I don’t do Erlang development myself.

Two thoughts:

  1. You may want to try to have one Dockerfile for builds in Gitlab (to run tests, etc), and a separate one for builds for production that deploy to Fly.
  2. You may want to swap to --remote-only in the Fly Deploy stage, which will run Docker on Fly’s side when deploying. This might make the mental-model of what’s going on a bit simpler. This way Gitlab is running builds/tests, but then Fly is doing the building for production.

In the 2nd Dockerfile where the build failed, you seem to be using an elixir/erlang base image but then re-installing erlang into it?

Thank you for your answer !
I’m really beginner to devops as you can see :sweat_smile:

But how can i have 2 different Dockerfile ?

For the second Dockerfile, it’s this one, proposed by Fly.io for Elixir apps https://github.com/fly-apps/hello_elixir/blob/main/Dockerfile

Hi!

The fly deploy command has a --dockerfile flag you can use to define which Dockerfile is used. So you could name it something like Dockerfile.fly and use:

# Perhaps use --remote-only instead of --local-only
fly deploy --dockerfile Dockerfile.fly --local-only

It looks like the Fly-provided Dockerfile was modified a bunch based on the Dockerfile pasted above, but perhaps that’s an accidental copy and paste?

If you have Docker installed locally you can test that out as well to see if it builds and runs.
If you don’t have docker installed locally, you can still deploy to fly directory to play with that, and use --remote-only so Fly uses its Docker to build it.

Hi @sevb!

When I was using Gitlab CI/CD, the way you typically do that is using the CI config file to build the app and run the tests. The tests must be run on a TEST build the project… which isn’t what you deploy. This means it needs to be MIX_ENV=test and what you deploy is built for production, MIX_ENV=prod and will also include asset optimizations, etc.

After doing a quick search, this looks like a reasonable place to start: Elixir Gitlab CI Example

So you don’t actually need a Dockerfile for running tests. The basic setup is part of the .gitlab-ci.yml file you add to your project.

That is the direction you need for running tests.

Deploying from there is a whole different thing which I never did.

Thank you @fideloper-fly and @Mark for your help !

I separate in 2 different Dockerfile as you suggested @fideloper-fly, one for build/lint and tests and the other for deploy (and I use some parts of https://www.agundy.com/2021/03/10/elixir-gitlab-ci.html, and :

image

It works !

I know that it’s not perfect, so i have to continue learning about Docker, continuous integration and deployment :muscle:t2:

1 Like

Awesome, I’m glad you got it working! :tada:

@fideloper-fly Thank you again for your help :blush:

I think i have just one last problem, the flyctl auth login in the deploy stage wants to open a browser as below
image

so i have to connect manually to Fly.io, I suppose i miss something :sweat_smile:

Hi!
You can get an auth token from Fly and set that as a secret in Gitlab (rather than using the fly auth login command.

There’s some details on that here (this article may be useful in general): Continuous Deployment with GitLab · Fly

Yes, I’ve already done that and set a FLY_API_TOKEN in Gitlab variables but it’s still asking to connect to browser when the job is running :thinking:

Edit : I’ve just deleted the command fly auth login, sorry for this dumb question :roll_eyes:

If the token to env var FLY_AUTH_TOKEN, you don’t need to call the fly auth command at all.