From 046bd495efeb796c3261674562872f65d1650bbf Mon Sep 17 00:00:00 2001 From: Renato Cordeiro Ferreira Date: Mon, 11 Apr 2022 10:36:14 -0300 Subject: [PATCH] Dockerize project to build, test and run the game Create a [Dockerfile][1] with [multi-stage building][2] to create a build environment based on the [Gradle Docker image][3] and a lightweight runner environment based on the [Eclipse Temurin Docker image][4] (both based on OpenJDK 17). Create a .dockerignore file based on .gitignore to skip files that [should not be uploaded in the docker image][5]. Create a [docker-compose.yml][6] to setup four services: - `game` to run the app for end users. - `linter` to run all linters once. - `tester` to rerun all tests with live reload. - `demo` to rerun the app with live reload. Add the same image name to [build the docker image only once][7] for multiple services sharing with the same codebase. Create a .env file to [configure environment variables][8] used inside the compose file. Add the [`COMPOSE_PROJECT_NAME` variable][9] to assign a prefix for all resources managed by compose. Update the pre-commit config to use `docker compose` with the `linter` image instead of raw `docker` commands to speed up the execution. Update the README.md to use `docker compose` instead of raw `docker` commands to execute tasks related to the project. [1]: https://docs.docker.com/engine/reference/builder/ [2]: https://docs.docker.com/develop/develop-images/multistage-build/ [3]: https://hub.docker.com/_/gradle [4]: https://hub.docker.com/_/eclipse-temurin [5]: https://codefresh.io/docker-tutorial/not-ignore-dockerignore-2/ [6]: https://docs.docker.com/compose/compose-file/ [7]: https://stackoverflow.com/questions/40899236/how-to-prevent-docker-compose-building-the-same-image-multiple-times [8]: https://docs.docker.com/compose/environment-variables/ [9]: https://docs.docker.com/compose/reference/envvars/#compose_project_name --- .dockerignore | 10 +++++++ .env | 4 +++ .pre-commit-config.yaml | 2 +- Dockerfile | 62 +++++++++++++++++++++++++++++++++++++++++ README.md | 26 +++++++++-------- docker-compose.yml | 48 +++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 .env create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8774928e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +# Ignore Git files +.git +.gitignore +.gitattributes + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/.env b/.env new file mode 100644 index 00000000..192dee3b --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +COMPOSE_PROJECT_NAME=galaxy-raiders + +GALAXY_RAIDERS_IMAGE=galaxy-raiders +GALAXY_RAIDERS_VERSION=dev diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d04cfe42..0ac87e67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,6 +15,6 @@ repos: name: Run Kotlin linters description: This hook runs Gradle to execute linters language: system - entry: bash -c "docker container run --rm --user gradle --volume $PWD:/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle --no-daemon formatKotlin detekt" + entry: docker compose --profile dev run -T --rm linter types: [kotlin] pass_filenames: false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7df712d2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,62 @@ +################################################################################ +# BASE IMAGE # +################################################################################ + +# Use Gradle image to run Gradle commands +FROM gradle:7.4.2-jdk17 AS base + +# Set default docker build args +ARG APP_DIR=/home/gradle/galaxy-raiders + +# Change default workdir +WORKDIR ${APP_DIR} + +# Set environment variable to disable Gradle Daemon +ENV GRADLE_OPTS=-Dorg.gradle.daemon=false + +# Give the ownership of workdir to the Gradle user +RUN chown -R gradle:gradle . + +# Change to non-priviled user (PID 1000) +USER gradle + +# NOTE: This repo has gradle wrappers to ensure developers use the same version +# of gradle in every environment. However, using gradle's docker image already +# guarantees that. Therefore, commands below can use the `gradle` CLI directly. + +# Copy source code to workdir +COPY --chown=gradle:gradle . . + +# NOTE: Gradle download dependencies only when they are necessary, which slows +# tasks the first time they execute. Running them here will allow docker to +# cache these dependencies to speed up containers that execute gradle tasks. + +# Force gradle to download dependencies to build, lint and test code +RUN gradle --no-build-cache clean + +################################################################################ +# BUILDER IMAGE # +################################################################################ + +# Use base image to build the project +FROM base AS builder + +# Use gradle wrapper to generate an uber JAR +RUN gradle --no-daemon shadowJar + +################################################################################ +# PRODUCTION IMAGE # +################################################################################ + +# Use Eclipse Temurin image to run the project +FROM eclipse-temurin:17-jdk AS runner + +# Reuse default docker build args +ARG APP_DIR=/home/gradle/galaxy-raiders + +# Copy runtime from image +COPY --from=builder ${APP_DIR}/app/build/libs/galaxy-raiders.jar \ + /bin/runner/galaxy-raiders.jar + +# Run uber JAR to start application +CMD ["java", "-jar", "galaxy-raiders.jar"] diff --git a/README.md b/README.md index dc4048f4..8a07f0fc 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,22 @@ pre-commit run --all-files Alternatively, to execute the linters manually, run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle --no-daemon formatKotlin detekt +docker compose --profile dev run --rm linter ``` ## Compilation -To compile all classes in the project, run: +The compilation of all classes and the generation of a runnable self-contained +JAR is made "behind the scenes" by [docker][4]. + +To build the development images, run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle --no-daemon clean assemble +docker compose --profile dev build ``` -To generate a runnable self-contained JAR of the project, run: +To build the production images, run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle --no-daemon clean shadowJar +docker compose --profile prod build ``` ## Tests @@ -51,32 +54,31 @@ All tests in the project are developed using [JUnit 5][8]. To execute all tests (with live reload), run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle --continuous test +docker compose --profile dev up tester ``` ## Execution To execute the project in development mode (with live reload), run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle --continuous run +docker compose --profile dev up demo --build ``` -To execute the project in production mode, generate a runnable self-contained -JAR of the project, then run: +To execute the project in production mode, run: ```bash -docker container run --rm --volume "$PWD"/app/build/libs/galaxy-raiders.jar:/bin/runner/galaxy-raiders.jar --workdir /bin/runner eclipse-temurin:17-jdk java -jar galaxy-raiders.jar +docker compose --profile prod up game --build ``` ## Other tasks To find available gradle tasks, run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle tasks +docker compose --profile dev run --rm demo gradle --no-daemon tasks ``` To execute any task, run: ```bash -docker container run --rm --user gradle --volume "$PWD":/home/gradle/app --workdir /home/gradle/app gradle:7.4.2-jdk17 gradle {task} +docker compose --profile dev run --rm demo gradle --no-daemon {task} ``` [1]: https://uspdigital.usp.br/jupiterweb/obterDisciplina?sgldis=MAC0218 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..d97c2353 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +services: + # The `game` service is the production app for end users. + game: + image: "${GALAXY_RAIDERS_IMAGE}:${GALAXY_RAIDERS_VERSION}" + build: + context: . + target: "runner" + profiles: + - "prod" + + # The `linter` service allows running all linters. + # Whenever a file changes, Gradle will rerun detekt and ktlint. + linter: + image: "${GALAXY_RAIDERS_IMAGE}:${GALAXY_RAIDERS_VERSION}" + build: + context: . + target: "base" + command: "gradle formatKotlin detekt" + volumes: + - "./:/home/gradle/galaxy-raiders/" + profiles: + - "dev" + + # The `tester` service allows running all tests. + # Whenever a file changes, JUnit will rerun all tests related to it. + tester: + image: "${GALAXY_RAIDERS_IMAGE}:${GALAXY_RAIDERS_VERSION}" + build: + context: . + target: "base" + command: "gradle --continuous test" + volumes: + - "./:/home/gradle/galaxy-raiders/" + profiles: + - "dev" + + # The `demo` service allows running the application. + # Whenever a file changes, the app will be restarted automatically. + demo: + image: "${GALAXY_RAIDERS_IMAGE}:${GALAXY_RAIDERS_VERSION}" + build: + context: . + target: "base" + command: "gradle --continuous runShadow" + volumes: + - "./:/home/gradle/galaxy-raiders/" + profiles: + - "dev"