diff --git a/.gitignore b/.gitignore
index c41cc9e35e38efc7d080637859e6c72940b374a2..81462badc514f87a564a4ff3a5c0a3741490c63c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-/target
\ No newline at end of file
+/target
+.rocks
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c6d2d601da9cc1eba12ec5cf969cdda85bb92ace
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,115 @@
+stages:
+  - test
+  - lint
+  - utility
+
+variables:
+  GIT_STRATEGY: clone
+  CI_IMAGE_REPOSITORY: docker-picodata.binary.picodata.io
+  CI_IMAGE: "${CI_IMAGE_REPOSITORY}/${CI_PROJECT_NAME}:0.1.0"
+  DOCKER_AUTH_CONFIG: $DOCKER_AUTH_RO
+  CARGO_NET_GIT_FETCH_WITH_CLI: "true"
+
+default:
+  image: $CI_IMAGE
+
+.rust_cache: &rust_cache
+  - paths:
+      - $CI_PROJECT_DIR/.cargo/.crates.toml
+      - $CI_PROJECT_DIR/.cargo/.crates2.json
+      - $CI_PROJECT_DIR/.cargo/bin
+      - $CI_PROJECT_DIR/.cargo/registry/index
+      - $CI_PROJECT_DIR/.cargo/registry/cache
+      - $CI_PROJECT_DIR/.cargo/git/db
+      - $CI_PROJECT_DIR/.cache/sccache
+      - $CI_PROJECT_DIR/target/debug/libexample.so
+    key:
+      files:
+        - ./Cargo.lock
+    when: "always"
+
+.lua_cache: &lua_cache
+  - paths:
+      - $CI_PROJECT_DIR/.rocks
+    key:
+      files:
+        - ./deps.sh
+    when: "always"
+
+.base:
+  variables:
+    # variables for rust cache
+    CARGO_HOME: $CI_PROJECT_DIR/.cargo
+    SCCACHE_DIR: $CI_PROJECT_DIR/.cache/sccache
+    RUSTC_WRAPPER: sccache
+  image:
+    name: $CI_IMAGE
+    pull_policy: [always, if-not-present]
+  tags:
+    - docker
+  needs: []
+  interruptible: true
+  cache:
+    - *rust_cache
+    - *lua_cache
+
+int-test:
+  stage: "test"
+  extends: .base
+  script:
+    - make int-test
+
+lua-lint:
+  extends: .base
+  stage: lint
+  script:
+    - ./deps.sh
+    - make lua-lint
+
+rust-lint:
+  extends: .base
+  stage: lint
+  script:
+    - make rust-lint
+
+rust-fmt:
+  stage: lint
+  extends: .base
+  script:
+    - cargo fmt --check
+
+build_ci:
+  stage: utility
+  tags:
+    - shell
+  inherit:
+    default: false
+  variables:
+    DOCKER_AUTH_CONFIG: $DOCKER_AUTH_RW
+
+    # Image version MUST be passed to CI job, or job will be failed.
+    CI_IMAGE_VERSION: ""
+
+    # If true is passed here, job will overwrite existing image with the same tag.
+    # If it is false(default) and image with the same tag already resides in docker registry, job will be failed.
+    CI_IMAGE_OVERWRITE_TAG: "false"
+
+    # If true is passed here, resulting image would also be tagged as latest.
+    CI_IMAGE_IS_LATEST: "true"
+
+    BASE_IMAGE_NAME: "${CI_IMAGE_REPOSITORY}/${CI_PROJECT_NAME}"
+    IMAGE_TAG: "${BASE_IMAGE_NAME}:${CI_IMAGE_VERSION}"
+    LATEST_IMAGE_TAG: "${BASE_IMAGE_NAME}:latest"
+  before_script:
+    - if [ -z "$CI_IMAGE_VERSION" ]; then echo "Image version should be specified, got '$CI_IMAGE_VERSION'" 1>&2 && exit 1; fi
+    - IMAGE_EXIST=$(docker manifest inspect $IMAGE_TAG > /dev/null; echo $?)
+    - if [[ "$CI_IMAGE_OVERWRITE_TAG" == "false" && "$IMAGE_EXIST" == "0" ]]; then echo "Image tag already resides in registry and it must not be overwritten" 1>&2 && exit 1; fi
+  script:
+    - docker build --file Dockerfile.ci -t $IMAGE_TAG .
+    - if [ $CI_IMAGE_IS_LATEST = "true" ]; then docker tag $IMAGE_TAG $LATEST_IMAGE_TAG; fi
+    - docker push --all-tags $BASE_IMAGE_NAME
+  when: manual
+  only:
+    changes:
+      - Dockerfile.ci
+  needs: []
diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000000000000000000000000000000000000..f585a8b23f0726762385c32f7b6d48446e1faa2b
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,28 @@
+include_files = {'**/*.lua', '*.luacheckrc', '*.rockspec'}
+exclude_files = {'.rocks/', 'tmp/'}
+max_line_length = 120
+redefined = false
+
+read_globals = {
+    "box",
+    "table.copy",
+    "table.deepcopy",
+    "table.clear",
+    "table.foreach",
+    "package.reload.count",
+    "dostring",
+    "package.setsearchroot",
+    "string.split",
+    "mp_encode",
+    "mp_decode",
+    "repository_find",
+    "repository_get",
+    "repository_put",
+    "repository_update",
+    "repository_delete",
+    "repository_map_reduce",
+    "repository_prepare_filter"
+}
+globals = {
+    "repository",
+}
\ No newline at end of file
diff --git a/Dockerfile.ci b/Dockerfile.ci
new file mode 100644
index 0000000000000000000000000000000000000000..63ee2941f2996e31560a9c62f2d477f27dae5167
--- /dev/null
+++ b/Dockerfile.ci
@@ -0,0 +1,19 @@
+FROM docker.binary.picodata.io/rockylinux:8
+
+WORKDIR /app
+
+# install needed packages
+RUN dnf install -y epel-release && \
+    sed 's/enabled=.*/enabled=1/g' -i /etc/yum.repos.d/epel.repo && \
+    dnf install -y --allowerasing git gcc cmake3 libarchive gcc-c++ wget curl \
+    findutils unzip bash-completion openssl-devel libicu-devel && \
+    rpm --import https://download.picodata.io/tarantool-picodata/el/RPM-GPG-KEY-kdy && \
+    dnf install -y https://download.picodata.io/tarantool-picodata/el/8/x86_64/picodata-release-1.1.0.11-1.el8.x86_64.rpm && \
+    dnf install -y tarantool-picodata tarantool-picodata-devel && \
+    dnf clean all
+
+# install rust
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain 1.67 -y
+ENV PATH="/kafka/bin:/root/.cargo/bin:${PATH}"
+
+RUN cargo install sccache
diff --git a/Makefile b/Makefile
index b63c58d7b3435b1674e22d5aeb25d744dc94a810..d5520f21f69b38d0e173039b8369d4c68f423eb8 100644
--- a/Makefile
+++ b/Makefile
@@ -19,4 +19,12 @@ run-example: build-example
 	cargo run --features="bin" -p tarantool-test -- test -p ./target/debug/libexample.$(LIBS_EXT) -e alternative_test_entrypoint
 
 int-test: build build-example
-	cargo test -p tests
\ No newline at end of file
+	cargo test -p tests
+
+lua-lint:
+	.rocks/bin/luacheck .
+
+rust-lint:
+	cargo clippy --all-targets --all-features -- -D warnings
+
+lint: lua-lint rust-lint
\ No newline at end of file
diff --git a/deps.sh b/deps.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cf34be10aca55fd4d9f5e2d65140b7c20dceb17c
--- /dev/null
+++ b/deps.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Call this script to install test dependencies
+
+set -e
+
+# Test dependencies:
+tarantoolctl rocks install luacheck 0.26.0