Skip to content

Deploy your own code

You can also deploy your own code on Lunni. In this tutorial, we provide instructions for GitLab CI and GitLab Container Registry, or GitHub Actions and GHCR, but you can adapt it to pretty much any CI and Docker registry.

Clone our example project to get started, or make your own (it should have Dockerfile in repo root):

git clone https://gitlab.com/lunni/examples/python.git -b tutorial-start lunni-example-python
cd lunni-example-python
git remote set-url origin git@gitlab.com:YOURUSERNAME/lunni-example-python
git clone https://gitlab.com/lunni/examples/python.git -b tutorial-start lunni-example-python
cd lunni-example-python
git remote set-url origin git@github.com:YOURUSERNAME/lunni-example-python

Step 1. Set up CI

Our example project already has a Dockerfile, which means we can build an image. You could use docker build locally, but in the long run it's better to have a CI do it for you.

Create a file with the following contents:

# .gitlab-ci.yml

docker-build:
  # Use the official docker image.
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  # Default branch leaves tag empty (= latest tag)
  # All other branches are tagged with the escaped branch name (commit ref slug)
  script:
    - |
      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
        tag=""
        echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
      else
        tag=":$CI_COMMIT_REF_SLUG"
        echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
      fi
    - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
    - docker push "$CI_REGISTRY_IMAGE${tag}"
  # Run this job in a branch where a Dockerfile exists
  rules:
    - if: $CI_COMMIT_BRANCH
      exists:
        - Dockerfile

Commit and push, then open your repository. In the left sidebar, hover CI/CD and go to Pipelines. You should see your pipeline running.

Once it finishes, in the left sidebar, hover Packages and registries and select Container Registry. If you see your image there, everything worked correctly.

# .github/workflows/build-docker.yml

name: Create and publish a Docker image

on:
  push:
    branches: ['release']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Log in to the Container registry
        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Commit and push, then open your repository. Under your repository name, click Actions. In the left sidebar, click Create and publish a Docker image. You should see your workflow running.

Once it finishes, navigate to the main page of the repository again, then to the right of the list of files, click Packages. If you see your image there, everything worked correctly.

Step 2. Login to container registry in Lunni

In order for Lunni to see your image, you'll have to authorize it to access the registry. The easiest way is to create a personal access token. Don't worry, this only needs to be done once for a given Lunni instance.

On your GitLab instance:

  1. In the top-right corner, select your avatar.
  2. Select Edit profile.
  3. On the left sidebar, select Access Tokens.

  4. Enter a name and optional expiry date for the token.

    Make sure name is descriptive enough, so that you can remember what this token is for later. For example, Lunni on acme-apps.cloud.

  5. Select the read_registry scope.

  6. Select Create personal access token.

On GitHub:

  1. In the upper-right corner of any page, click your profile photo, then click Settings.
  2. In the left sidebar, click Developer settings.
  3. In the left sidebar, under Personal access tokens, click Tokens (classic).
  4. Click Generate new token.

  5. Under Token name, enter a name for the token.

    Make sure name is descriptive enough, so that you can remember what this token is for later. For example, Lunni on acme-apps.cloud.

  6. To give your token an expiration, select the Expiration drop-down menu, then click a default or use the calendar picker.

  7. Select the read:packages scope.
  8. Select Generate token.

Copy the resulting token. Then, on Lunni:

  1. In the upper-right corner of any page, click your profile photo, then click Settings.
  2. In the left sidebar select Registries, then click Add registry.
  3. Authenticate using your token as a password.

Treat your access tokens like passwords

Personal access tokens are intended to access resources on behalf of yourself. While the scope limits the access granted by the token to just reading the packages, we recommend you to paste it right into Lunni and not store it anywhere.

Step 3. Deploy your app

You can now deploy your app as usual! Let's write a Compose file for it:

version: "3"

networks:
  traefik-public: { external: true }

services:
  server:
    image: registry.gitlab.com/YOURUSERNAME/lunni-example-python:latest
    networks: [traefik-public]
    deploy:
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.constraint-label=traefik-public
        - traefik.http.routers.${PROJECT_NAME?}-http.rule=Host(`${DOMAIN?}`)
        - traefik.http.routers.${PROJECT_NAME?}-http.entrypoints=http
        - traefik.http.routers.${PROJECT_NAME?}-http.middlewares=https-redirect
        - traefik.http.routers.${PROJECT_NAME?}-https.rule=Host(`${DOMAIN?}`)
        - traefik.http.routers.${PROJECT_NAME?}-https.entrypoints=https
        - traefik.http.routers.${PROJECT_NAME?}-https.tls=true
        - traefik.http.routers.${PROJECT_NAME?}-https.tls.certresolver=le
        - traefik.http.services.${PROJECT_NAME?}.loadbalancer.server.port=80
version: "3"

networks:
  traefik-public: { external: true }

services:
  server:
    image: ghcr.io/YOURUSERNAME/lunni-example-python:latest
    networks: [traefik-public]
    deploy:
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.constraint-label=traefik-public
        - traefik.http.routers.${PROJECT_NAME?}-http.rule=Host(`${DOMAIN?}`)
        - traefik.http.routers.${PROJECT_NAME?}-http.entrypoints=http
        - traefik.http.routers.${PROJECT_NAME?}-http.middlewares=https-redirect
        - traefik.http.routers.${PROJECT_NAME?}-https.rule=Host(`${DOMAIN?}`)
        - traefik.http.routers.${PROJECT_NAME?}-https.entrypoints=https
        - traefik.http.routers.${PROJECT_NAME?}-https.tls=true
        - traefik.http.routers.${PROJECT_NAME?}-https.tls.certresolver=le
        - traefik.http.services.${PROJECT_NAME?}.loadbalancer.server.port=80

Paste it on the Create project page, specify DOMAIN and click Deploy. Give it a minute or two to fetch the image and obtain a TLS certificate. After that, let's verify that it works:

$ http get https://lunni-example-python.demo.lunni.cloud/
HTTP/1.1 200 OK
Content-Length: 25
Content-Type: application/json
Date: Thu, 19 Jan 2023 03:05:56 GMT
Server: uvicorn

{
    "message": "Hello World"
}