loading...

From GitLab to Heroku with Docker

mattdark profile image Mario García Updated on ・5 min read

From Zero to Production with Rust, Python and GitLab (4 Part Series)

1) Development Environment for Rust and Python on Linux 2) A web app built with Rust and Python 3) A custom Docker image for Rust and Python 4) From GitLab to Heroku with Docker

After writing the code of the project and testing it in a local development environment, you must create a GitLab repository and upload the code and configure to deploy to Heroku.

.gitignore

Add a .gitignore file to your repository so the temporary files and directories generated during the building process don't be uploaded to GitLab. You can use the templates provided by GitLab or generate this file at gitignore.io.

gitignore.io

Just type in the search bar the technologies you're using for the project and it will generate the file for you.

The file will look as follows:

# Created by https://www.gitignore.io/api/rust,python
# Edit at https://www.gitignore.io/?templates=rust,python

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# Mr Developer
.mr.developer.cfg
.project
.pydevproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

### Rust ###
# Generated by Cargo
# will have compiled files and executables
/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# End of https://www.gitignore.io/api/rust,python

Heroku

Before configuring the repository, you should create a Heroku app.
A new Heroku app

Go to dashboard.heroku.com/account and copy the API Key.

Dockerfile

Create a new Dockerfile in your repository. it will have the following content:

FROM docker.io/username/rust-python:latest

WORKDIR /home/admin
USER admin
RUN mkdir app
WORKDIR app

RUN env PYTHON_CONFIGURE_OPTS='--enable-shared' pyenv install 3.7.7
ENV LD_LIBRARY_PATH $HOME/.pyenv/versions/3.7.7/lib/

RUN pyenv global 3.7.7
RUN pyenv rehash

RUN rustup override set nightly

COPY pyproject.toml ./

RUN poetry config virtualenvs.create false \
    && poetry lock \
    && poetry export --without-hashes -f requirements.txt --dev \
    |  poetry run pip install -r /dev/stdin \
    && poetry debug

COPY  . ./

RUN poetry install --no-interaction \
    && cargo build --release

CMD ["sh", "run"]

According to this issue, the container registry address (docker.io) must be specified before the name of the base image, for buildah to pull the image correctly from Docker Hub.

The base image used for the project will be the one created before and available from username/rust-python:latest.

Then set the WORKDIR to /home/admin and the USER to admin. Create the app directory and set it as WORKDIR.

Install Python 3.7.7 with shared libraries and set the LD_LIBRARY_PATH environment variable.

Specify the version of Python and Rust to be used.

Copy the pyproject.toml file. Poetry is being used to manage the dependencies of Python but no virtual environment will be created.

Then lock the dependencies and export to a requirements.txt file and install them using pip, this is done to generate a cache of them.

Then install Python and Rust dependencies of the project, and build the project for production.

When the Docker image being created is run, the bash script named run is executed.

The run bash script has the following content, so that Heroku knows how to run the app:

ROCKET_PORT=$PORT ROCKET_ENV=prod ./target/release/rust-python-demo

The variable $PORT is set by Heroku as it dynamically chooses a port for the app, this value is assigned to ROCKET_PORT. Then the environment is set to production with ROCKET_ENV=prod and the binary of the app is run.

GitLab CI

Add the value of the API Key of Heroku that you copied before to the settings of the repository by following the steps bellow:

  • Go to Settings > CI/CD.
  • In the Variables section click on Expand.
  • Add the variable HEROKU_API_KEY and assign the value copied before.

Heroku API Key

Then add the .gitlab-ci.yml file, this is the configuration file of the Continuous Integration system provided by GitLab.

For this I just follow Alessandro Diaferia's tutorial on Continuous Deployment With GitLab, Docker And Heroku.

The file will look as follows:

stages:
  - build
  - release

build_image:
  only:
    - master
  image: registry.gitlab.com/majorhayden/container-buildah
  stage: build
  variables:
    STORAGE_DRIVER: "vfs"
    BUILDAH_FORMAT: "docker"
  before_script:
    - dnf install -y nodejs
    - curl https://cli-assets.heroku.com/install.sh | sh
    - sed -i '/^mountopt =.*/d' /etc/containers/storage.conf
  script:
    - buildah bud --iidfile iidfile -t rust-python-demo:$CI_COMMIT_SHORT_SHA .
    - buildah push --creds=_:$(heroku auth:token) $(cat iidfile) registry.heroku.com/heroku-app-name /web

release:
  only:
    - master
  image: node:10.17-alpine
  stage: release
  before_script:
    - apk add curl bash
    - curl https://cli-assets.heroku.com/install.sh | sh
  script:
    - heroku container:release -a heroku-app-name web

It will only have two stages, build_image, where the Docker image is built using the Dockerfile created previously and added to the registry of Heroku, and release, where the image will be pushed to the Heroku app.

Just replace registry.heroku.com/heroku-app-name/web and heroku container:release -a heroku-app-name web with the name of the app you created on Heroku.

If everything goes well, the jobs of the pipeline will succeed and your app is deployed to Heroku.
GitLab Pipeline

You can go to rust-python-demo.herokuapp.com to see a live demo running.
Heroku App running on Firefox

From Zero to Production with Rust, Python and GitLab (4 Part Series)

1) Development Environment for Rust and Python on Linux 2) A web app built with Rust and Python 3) A custom Docker image for Rust and Python 4) From GitLab to Heroku with Docker

Posted on by:

mattdark profile

Mario García

@mattdark

Free Software Enthusiast | Speaker | Mozillian | Mozilla Reps | Mozilla Open Leaders | Python & Rust Developer

Discussion

markdown guide