diff --git a/complete/Dockerfile.dotnet b/complete/Dockerfile.dotnet index 460ced0..ecb5eba 100644 --- a/complete/Dockerfile.dotnet +++ b/complete/Dockerfile.dotnet @@ -25,9 +25,10 @@ EXPOSE 8080 # Set the environment variables ENV ASPNETCORE_URLS=http://+:8080 ENV DOTNET_RUNNING_IN_CONTAINER=true +ENV ApiSettings__BaseUrl="${ApiSettings__BaseUrl}" # Copy the published files from the build stage COPY --from=publish /app/publish . # Set the entry point for the container -ENTRYPOINT ["dotnet", "Contoso.BlazorApp.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "Contoso.BlazorApp.dll"] diff --git a/complete/Dockerfile.java b/complete/Dockerfile.java index 16d8f8b..06422d9 100644 --- a/complete/Dockerfile.java +++ b/complete/Dockerfile.java @@ -1,55 +1,90 @@ -# Stage 1: Build the application -FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu AS build +# Multi-stage build for Java Spring Boot application +# Stage 1: Build stage with Microsoft OpenJDK 21 +FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu AS builder -WORKDIR /app +# Set working directory for build +WORKDIR /workspace -# Copy gradle files for dependency resolution -COPY java/socialapp/gradle/ ./gradle/ -COPY java/socialapp/gradlew java/socialapp/build.gradle java/socialapp/settings.gradle ./ +# Copy Gradle configuration files +COPY java/socialapp/gradle ./gradle +COPY java/socialapp/gradlew . +COPY java/socialapp/gradlew.bat . +COPY java/socialapp/build.gradle . +COPY java/socialapp/settings.gradle . -# Give executable permissions to gradlew +# Make gradlew executable RUN chmod +x ./gradlew -# Download dependencies to cache this layer +# Download dependencies (for better Docker layer caching) RUN ./gradlew dependencies --no-daemon # Copy source code COPY java/socialapp/src ./src # Build the application -RUN ./gradlew bootJar --no-daemon +RUN ./gradlew build --no-daemon -x test # Stage 2: Extract JRE from JDK -FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu AS jre-build +FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu AS jre-builder -# Create a custom JRE using jlink that only includes modules needed for the application +# Create a custom JRE using jlink RUN jlink \ - --add-modules java.base,java.compiler,java.desktop,java.instrument,java.management,java.naming,java.prefs,java.rmi,java.security.jgss,java.security.sasl,java.sql,jdk.crypto.ec,jdk.unsupported,jdk.zipfs,jdk.management \ + --add-modules java.base,java.desktop,java.instrument,java.management,java.naming,java.net.http,java.security.jgss,java.sql,jdk.unsupported \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ - --output /jre-minimal + --output /custom-jre -# Stage 3: Create final image +# Stage 3: Runtime stage with custom JRE FROM ubuntu:22.04 -# Set environment variables -ENV JAVA_HOME=/opt/jre-minimal +# Install required packages and clean up +RUN apt-get update && \ + apt-get install -y \ + ca-certificates \ + sqlite3 \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Copy custom JRE from jre-builder stage +COPY --from=jre-builder /custom-jre /opt/java/openjdk + +# Set JAVA_HOME and update PATH +ENV JAVA_HOME=/opt/java/openjdk ENV PATH="${JAVA_HOME}/bin:${PATH}" -# Copy the extracted JRE from the jre-build stage -COPY --from=jre-build /jre-minimal $JAVA_HOME +# Create application user for security +RUN groupadd -r appuser && useradd -r -g appuser appuser -# Copy the built application from the build stage +# Set working directory WORKDIR /app -COPY --from=build /app/build/libs/*.jar app.jar -# Create a directory for persistent data -RUN mkdir -p /app/data +# Copy the built JAR from builder stage +COPY --from=builder /workspace/build/libs/*.jar app.jar + +# Create SQLite database file with proper permissions +RUN touch sns_api.db && \ + chown appuser:appuser sns_api.db && \ + chmod 664 sns_api.db -# Expose the application port +# Change ownership of the app directory +RUN chown -R appuser:appuser /app + +# Switch to non-root user +USER appuser + +# Set environment variables for GitHub Codespaces +ENV CODESPACE_NAME="${CODESPACE_NAME}" +ENV GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN="${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" + +# Expose port 8080 EXPOSE 8080 -# Set the entrypoint command to run the application -ENTRYPOINT ["java", "-jar", "/app/app.jar"] \ No newline at end of file +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/actuator/health || exit 1 + +# Run the application +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/complete/compose.yaml b/complete/compose.yaml index 93e8d26..c715390 100644 --- a/complete/compose.yaml +++ b/complete/compose.yaml @@ -1,3 +1,9 @@ +version: '3.8' + +networks: + contoso: + driver: bridge + services: contoso-backend: build: @@ -9,10 +15,14 @@ services: environment: - CODESPACE_NAME=${CODESPACE_NAME} - GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN} - volumes: - - ./java/socialapp/sns_api.db:/app/sns_api.db networks: - contoso + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] + interval: 30s + timeout: 3s + start_period: 60s + retries: 3 contoso-frontend: build: @@ -21,13 +31,9 @@ services: container_name: contoso-frontend ports: - "3030:8080" - depends_on: - - contoso-backend environment: - - ApiSettings__BaseUrl=http://contoso-backend:8080/api/ + - ApiSettings__BaseUrl=http://contoso-backend:8080/api networks: - contoso - -networks: - contoso: - name: contoso + depends_on: + - contoso-backend diff --git a/docs/05-containerization.md b/docs/05-containerization.md index a2125a3..83076a2 100644 --- a/docs/05-containerization.md +++ b/docs/05-containerization.md @@ -70,14 +70,16 @@ Refer to the [README](../README.md) doc for preparation. ```text I'd like to build a container image of a Java app. Follow the instructions below. - - The Java app is located at `java`. - - Your working directory is the repository root. - Identify all the steps first, which you're going to do. + - The Java app is located at `java/socialapp`. + - Your working directory is the repository root. - Create a Dockerfile, `Dockerfile.java`. - Use Microsoft OpenJDK 21. - Use multi-stage build approach. - Extract JRE from JDK. - Use the target port number of `8080` for the container image. + - Add both environment variables, `CODESPACE_NAME` and `GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN` from the host to the container image. + - Create an SQLite database file, `sns_api.db`, in the container image. DO NOT Copy the file from the host. ``` 1. Click the ![the keep button image](https://img.shields.io/badge/keep-blue) button of GitHub Copilot to take the changes. @@ -101,6 +103,7 @@ Refer to the [README](../README.md) doc for preparation. Use the container image just built, run a container and verify if the app is running properly. - Use the host port of `8080`. + - Both `CODESPACE_NAME` and `GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN` values should be the ones from GitHub Codespaces. ``` ### Containerize .NET Application @@ -111,13 +114,14 @@ Refer to the [README](../README.md) doc for preparation. ```text I'd like to build a container image of a .NET app. Follow the instructions below. + - Identify all the steps first, which you're going to do. - The .NET app is located at `dotnet`. - Your working directory is the repository root. - - Identify all the steps first, which you're going to do. - Create a Dockerfile, `Dockerfile.dotnet`. - Use .NET 9. - Use multi-stage build approach. - Use the target port number of `8080` for the container image. + - Add the environment variable, `ApiSettings__BaseUrl` to the container. It should point to the Java app, `http://localhost:8080/api`. ``` 1. Click the ![the keep button image](https://img.shields.io/badge/keep-blue) button of GitHub Copilot to take the changes. @@ -141,12 +145,13 @@ Refer to the [README](../README.md) doc for preparation. Use the container image just built, run a container and verify if the app is running properly. - Use the host port of `3030`. + - Pass the environment variable `ApiSettings__BaseUrl` the value of `http://localhost:8080/api`. ``` 1. Make sure that both frontend and backend apps are NOT communicating with each other because they don't know each other yet. Run the prompt like below. ```text - Regardless or not, remove both containers currently running. + Remove both Java and .NET containers and their respective container images. ``` ### Orchestrate Containers @@ -157,6 +162,7 @@ Refer to the [README](../README.md) doc for preparation. ```text I'd like to create a Docker Compose file. Follow the instructions below. + - Identify all the steps first, which you're going to do. - Your working directory is the repository root. - Use `Dockerfile.java` as a backend app. - Use `Dockerfile.dotnet` as a frontend app. @@ -164,7 +170,8 @@ Refer to the [README](../README.md) doc for preparation. - Use `contoso` as the network name. - Use `contoso-backend` as the container name of the Java app. Its target port is 8080, and host port is 8080. - Use `contoso-frontend` as the container name of the .NET app. Its target port is 8080, and host port is 3030. - - Mount the volume for the database that the Java app uses, `java/socialapp/sns_api.db`. + - Add both environment variables, `CODESPACE_NAME` and `GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN` from the host to the Java container. + - Add the environment variable, `ApiSettings__BaseUrl` to the .NET container. It should point to the Java app's `/api`. ``` 1. Click the ![the keep button image](https://img.shields.io/badge/keep-blue) button of GitHub Copilot to take the changes. @@ -172,7 +179,7 @@ Refer to the [README](../README.md) doc for preparation. 1. Once the `compose.yaml` file is created, run it and verify if both apps are running properly. ```text - Now, run the Docker compose file and verify if the apps are running properly. + Run the Docker compose file and verify if all the apps are running properly. ``` 1. Open a web browser and navigate to `http://localhost:3030`, and verify if the apps are up and running properly.