<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://surkoff.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://surkoff.com/" rel="alternate" type="text/html" /><updated>2025-08-25T09:18:18+00:00</updated><id>https://surkoff.com/feed.xml</id><title type="html">Roman Surkoff</title><subtitle>An experienced software engineer</subtitle><entry><title type="html">Deploying Keycloak on a VPS Using Docker-compose, Nginx, Certbot, and SSL</title><link href="https://surkoff.com/blog/deploying-keycloak-to-vps" rel="alternate" type="text/html" title="Deploying Keycloak on a VPS Using Docker-compose, Nginx, Certbot, and SSL" /><published>2024-10-17T00:00:00+00:00</published><updated>2024-10-17T00:00:00+00:00</updated><id>https://surkoff.com/blog/deploying-keycloak-to-vps</id><content type="html" xml:base="https://surkoff.com/blog/deploying-keycloak-to-vps"><![CDATA[<p>The article explains how to deploy Keycloak, an open-source identity management system, on a VPS using Docker-compose, 
Nginx, Certbot, and SSL for secure access. It covers essential setup steps, including creating a domain, configuring 
Nginx, setting up SSL certificates, and using Docker for deployment automation. The guide also details how to handle 
automatic certificate renewal and import Keycloak realms during startup.</p>

<hr />

<div class="Article-Text"><span><div>Hello everyone!</div>
<div><br /></div>
<div>In this article, I would like to share how to deploy Keycloak on a VPS using Docker-compose, Nginx, Certbot, and
    SSL.
</div>
<div><br /></div><h2>Key Points:</h2>
<div>- Keycloak v.25.0.1</div>
<div>- SSL protection for Keycloak</div>
<div>- Certbot v.2.11.0 for obtaining and renewing SSL certificates</div>
<div>- Nginx v.1.27.0 as a reverse proxy</div>
<div>- Postgres v.14 to replace the default internal H2 DB of Keycloak</div>
<div>- Automatic realm import during deployment</div>
<div>- Docker-compose for deployment automation</div>
<div>- .env file for managing environment variables</div>
<div><br /></div>
<div>For those who might not be familiar, Keycloak is a powerful access management system with SSO support that can
    significantly simplify user management and authentication.
</div>
<div><br /></div>
<div>The desire to deploy your own Keycloak can arise both for experimenting with your projects and for handling your
    usual backend tasks. This happened to me as well. I decided to kill two birds with one stone. However, I couldn't
    find a comprehensive guide. I don't need Keycloak locally, but setting it up on a separate, always-available server
    with backup and the ability to export/import realms, etc., is excellent. Plus, the deployment process is automated,
    making it easier to switch to another VPS provider.
</div>
<div><br /></div>
<div>Everyone has their own motivation, but let's get to the point.</div>
<div><br /></div>
<h2>Introduction</h2>
<br />
<h4>What is Keycloak?</h4>
<div>Keycloak is an open-source identity and access management solution. It provides features such as SSO (Single
    Sign-On), user management, authentication, and authorization.
</div>
<br />
<h4>Why Docker-compose?</h4>
<div>Docker-compose makes it easy to manage multi-component applications like Keycloak and simplifies the deployment and
    scaling process. This guide uses containers for Keycloak, Certbot, Nginx, and the Postgres database.
</div>
<br />
<h4>Why Nginx and Certbot?</h4>
<div>Nginx will act as a reverse proxy, ensuring security and performance, while Certbot will help obtain and
    automatically renew SSL certificates from Let's Encrypt, saving us a couple of thousand rubles on certificates for
    our domain, which is nice.
</div>
<div><br /></div>
<div>Let's get started!</div>
<div><br /></div><h2>Step 1: Preparing the Environment</h2><h4>Cloning the Repository</h4>
<div>First, clone the repository with ready-made configurations to our VPS, which I have carefully prepared for you. It
    contains docker-compose.yml for managing deployment, nginx configs, and an environment variables file needed for
    docker-compose.
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">    git clone git@github.com:s-rb/keycloak-dockerized-ssl-nginx.git
    cd keycloak-dockerized-ssl-nginx</pre>
<h4>Editing the .env File</h4>
<div>Open the .env file and edit the following variables:</div>
<div><br /></div>
<div>- <span style="font-weight: bold;">KEYCLOAK_ADMIN_PASSWORD</span> - Admin password for accessing Keycloak</div>
<div>- <span style="font-weight: bold;">KC_DB_PASSWORD</span> - Password for Keycloak service access to the Postgres DB
    (should match `POSTGRES_PASSWORD` if a
    separate user is not created)
</div>
<div>- <span style="font-weight: bold;">POSTGRES_PASSWORD</span> - Admin password for Postgres</div>
<div><br /></div>
<div>Replace `password` with your values unless you want anyone to connect to your services ;)</div>
<div><br /></div>
<div>Example of a complete environment variables file:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">    KEYCLOAK_ADMIN=admin
    KEYCLOAK_ADMIN_PASSWORD=password
    PROXY_ADDRESS_FORWARDING=true
    KC_PROXY=edge
    <br />
    KC_DB=postgres
    KC_DB_URL=jdbc:postgresql://keycloak-postgres:5432/keycloak
    KC_DB_USERNAME=keycloak
    KC_DB_PASSWORD=password
    POSTGRES_DB=keycloak
    POSTGRES_USER=keycloak
    POSTGRES_PASSWORD=password
</pre>
<div><br /></div><h2>Step 2: Domain Registration and DNS Setup</h2>
<div>This step can be done before the first step - it does not depend on it. In the following instructions, we assume
    you have registered your domain (e.g., surkoff.com) and we want Keycloak to be accessible at
    my-keycloak.surkoff.com.
</div>
<div><br /></div><h4>Domain Registration</h4>
<div>Register a domain with any registrar, for example, REG.RU.</div>
<div><br /></div><h4>Creating an A Record for the Subdomain</h4>
<div>Create an A record pointing to your server's IP. For example, for the subdomain my-keycloak.surkoff.com, specify
    your server's IP.
</div>
<div><br /></div>
<div><img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00011-1.jpg" /><br /></div>
<div><br /></div><h4>Checking the DNS Record</h4>
<div>Ensure the DNS record is correctly configured:</div>
<div><br /></div>
<div>
    <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">ping my-keycloak.surkoff.com</pre>
</div>
<div><br /></div>
<div>The response should show your server's IP address.</div>
<div><br /></div><h2>Step 3: Configuring Nginx</h2><h4>Nginx Configuration</h4>
<div>In the nginx configs - `<span style="font-weight: bold;">default.conf_with_ssl</span>`, `<span style="font-weight: bold;">default.conf_without_ssl</span>` specify your domain:
</div>
<div><br /></div>
<div>- In the `<span style="font-weight: bold;">server_name</span>` section</div>
<div>- In the path to the certificate `<span style="font-weight: bold;">ssl_certificate</span>`</div>
<div>- In the path to the key `<span style="font-weight: bold;">ssl_certificate_key</span>`</div>
<div><br /></div>
<div>Example configuration with SSL:</div>
<div>
    <div><pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">server {
    listen 443 ssl;
    server_name my-keycloak.surkoff.com;

    ssl_certificate /etc/letsencrypt/live/my-keycloak.surkoff.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/my-keycloak.surkoff.com/privkey.pem;

    location / {
        proxy_pass http://keycloak:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}</pre>
</div>
<div><br /></div>
</div>
<h2>Step 4: Obtaining an SSL Certificate</h2>
<h4>Obtaining a Test Certificate</h4>
<div>Use the configuration without SSL:</div>
<div>
    <div><pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">cp nginx/conf.d/default.conf_without_ssl nginx/conf.d/default.conf
docker-compose up -d</span></pre>
    </div>
    <div><br /></div>
    <div>Obtain a test certificate (replace the domain and email with your own):</div>
</div>
<div>
    <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">docker exec certbot certbot certonly --webroot --webroot-path=/data/letsencrypt -d my-keycloak.surkoff.com --email your_email@gmail.com --agree-tos --no-eff-email --staging</span></pre>
</div><h4><img style="height: auto; display: block; margin: auto; max-width: 750px;" src="/assets/images/posts/2024/00011-2.jpg" /><br /></h4>
<h4>Checking the Certificate</h4>
<div>
    <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">docker exec certbot certbot certificates</span></pre>
</div>
<br />
<h4>Deleting the Test Certificate (replace the domain with your own)</h4>
<div>
    <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">docker exec certbot certbot delete --cert-name my-keycloak.surkoff.com</span></pre>
</div>
<br />
<h4>Obtaining a Real Certificate (replace email and domain with your own)</h4>
<div>
    <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">docker exec certbot certbot certonly --webroot --webroot-path=/data/letsencrypt -d my-keycloak.surkoff.com --email your_email@gmail.com --agree-tos --no-eff-email</span></pre>
</div>
<br />
<h2>Step 5: Final Configuration and Launch</h2><h4>Updating Nginx Configuration to Use SSL</h4>
<div><pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">docker-compose down
cp nginx/conf.d/default.conf_with_ssl nginx/conf.d/default.conf
docker-compose up -d</span></pre>
</div>
<br />
<h4>Checking Access to Keycloak</h4>
<div>Open a browser and go to my-keycloak.surkoff.com (your domain).</div>
<div><br /></div>
<div>You should see the admin login page where you can log in using the username and password you specified in the .env
    file.
</div>
<div><br /></div>
<div><img style="height: auto; display: block; margin: auto; max-width: 750px;" src="/assets/images/posts/2024/00011-3.jpg" /><br /></div>
<div><br /></div>
<div>For configuring Keycloak, there are interesting articles on other resources, which we won't cover in this
    publication.
</div>
<div><br /></div><h4>Automatic Certificate Renewal</h4>
<div>To automatically renew certificates and restart Nginx, create the `renew_and_reload.sh` script (already available
    in the repository):
</div>
<div>
    <div><pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">#!/bin/bash
# Renew certificates
docker exec certbot certbot renew --webroot --webroot-path=/data/letsencrypt

# Restart Nginx
docker restart nginx</span></pre>
</div>
</div>
<div><br /></div>
<div>Make the script executable:</div>
<div>
    <div>
        <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">chmod +x renew_and_reload.sh</span></pre>
    </div>
</div>
<div><br /></div>
<div>Add it to crontab for regular execution:</div>
<div>
    <div>
        <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">crontab -e</span></pre>
    </div>
</div>
<div><br /></div>
<div>Add a line to crontab, remembering to specify the path to the script:</div>
<div>
    <pre style="margin-top: 5px; margin-bottom: 5px; padding: 5px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 18px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; max-width: 100%; overflow: auto;"><span style="color: rgb(64, 64, 64); font-family: Consolas, Monaco, Monospaced, monospace; font-size: 12px;">0 0 1 * * /path/to/renew_and_reload.sh</span></pre>
</div>
<br />
<h2>Importing Realms</h2>
<div>If you want to import a realm at startup, you can place it in the `<span style="font-weight: bold;">keycloak/config/</span>`
    folder, and it
    will be imported when the application starts.
</div>
<div><br /></div>
<h2>Conclusion</h2>
<div>That's it! Now you have a deployed latest&nbsp;<span style="font-weight: bold;">Keycloak</span> with <span style="font-weight: bold;">SSL</span> on your <span style="font-weight: bold;">VPS</span>. I hope this article
    was helpful! If you have any
    questions or suggestions, feel free to write to me in private messages.
</div>
<div><br /></div>
<div>The source code is available at the link - <a href="https://github.com/s-rb/keycloak-dockerized-ssl-nginx">https://github.com/s-rb/keycloak-dockerized-ssl-nginx</a>.
</div></span></div>]]></content><author><name></name></author><category term="keycloak" /><category term="docker" /><category term="ssl" /><category term="certbot" /><category term="vps" /><category term="nginx" /><summary type="html"><![CDATA[This article provides a step-by-step guide for deploying Keycloak on a VPS with Docker-compose, Nginx, Certbot, and SSL for secure access and automated deployment.]]></summary></entry><entry><title type="html">The optimal Docker image for Spring Boot. Part 3</title><link href="https://surkoff.com/blog/optimal-docker-image-for-springboot-3" rel="alternate" type="text/html" title="The optimal Docker image for Spring Boot. Part 3" /><published>2024-10-16T00:00:00+00:00</published><updated>2024-10-16T00:00:00+00:00</updated><id>https://surkoff.com/blog/optimal-docker-image-for-springboot-3</id><content type="html" xml:base="https://surkoff.com/blog/optimal-docker-image-for-springboot-3"><![CDATA[<p>The Jib plugin is an open-source tool developed by Google that allows developers to build Docker images without 
needing Docker installed or writing a Dockerfile. It integrates directly with Maven and Gradle, creating Docker 
images by layering dependencies and resources efficiently. Jib also supports both local and remote builds, sending 
images directly to registries like Docker Hub, while producing slightly larger images compared to traditional methods.</p>

<hr />

<div class="Article-Text"><span><h2>Jib plugin</h2>
<div>This is an open-source tool from Google that doesn't require Docker to work. You also don't need to write a
    Dockerfile.
</div>
<div><br /></div>
<div>To use Jib, add the following to your pom.xml:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">
&lt;build&gt;
&nbsp; &nbsp; &lt;plugins&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &lt;plugin&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;groupid&gt;org.springframework.boot&lt;/groupid&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;artifactid&gt;spring-boot-maven-plugin&lt;/artifactid&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &lt;/plugin&gt;
&nbsp; &nbsp; &lt;/plugins&gt;
&lt;/build&gt;
</pre>
<div><br /></div>
<h2>Building without Docker</h2>
<div>One of the advantages of this plugin is building without installing Docker:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">    mvn compile jib:build
</pre>
<div><br /></div>
<div>After the build, I searched for the image in my local storage. Then it hit me. In this mode, Docker is not used, so
    the image was sent to the registry on Docker Hub.
</div>
<div>Pull the image and analyze it using dive:</div>
<div><br /></div>
<div>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00009-1.jpg" />
    <br />
</div>
<div><br /></div>
<div>The final weight came out to be 321 MB, which is 12 megabytes more than the previous method. Among them:</div>
<div><br /></div>
<div>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00009-2.jpg" />
    <br />
</div>
<div><br /></div>
<div>
    <ul>
        <li>78 + 48 MB - Linux and various certificates.</li>
        <li>140 MB - JDK.</li>
        <li>55 MB - layer with release dependencies.</li>
        <li>14 KB - layer with snapshot dependencies.</li>
        <li>981 bytes - only resources. Resources folder.</li>
        <li>22 KB - our written code.</li>
    </ul>
</div>
<div>Jib runs your image on behalf of the root user.</div>
<div>Similar to the Spring plugin, Jib separates dependencies into separate layers, but it goes even further and creates
    a separate layer for the resources folder. After all, resources are also rarely changed but can weigh a lot. For
    example, if you have many Liquibase migration scripts.
</div>
<h2>Building using Docker</h2>
<div>For this, add the plugin as in the previous step but run a different command:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">    mvn compile jib:dockerBuild
</pre>
<div><br /></div>
<div>The size and structure of the layers are no different from the image we built without Docker.</div>
<h2>Conclusions on Jib</h2>
<div>If you have an ARM processor, the image will still be built for amd64.</div>
<div>I have never used this plugin in projects, only heard about it, so I can't make any deep conclusions. It surprised
    me that it can work without Docker; sometimes this can be useful. But I don't see the point in using it when there
    is a Spring plugin available.
</div></span></div>]]></content><author><name></name></author><category term="springboot" /><category term="docker" /><category term="optimization" /><category term="image" /><category term="post" /><summary type="html"><![CDATA[Jib is a Google tool that simplifies building Docker images without Docker, optimizing image layers and supporting direct registry deployment.]]></summary></entry><entry><title type="html">The optimal Docker image for Spring Boot. Part 4</title><link href="https://surkoff.com/blog/optimal-docker-image-for-springboot-4" rel="alternate" type="text/html" title="The optimal Docker image for Spring Boot. Part 4" /><published>2024-10-16T00:00:00+00:00</published><updated>2024-10-16T00:00:00+00:00</updated><id>https://surkoff.com/blog/optimal-docker-image-for-springboot-4</id><content type="html" xml:base="https://surkoff.com/blog/optimal-docker-image-for-springboot-4"><![CDATA[<p>The article explores the process of building optimized Dockerfiles by reducing the size of the JDK using Java modules 
and determining necessary dependencies with the jdeps tool. It also introduces multi-stage Docker builds, where the 
JDK is custom-built in the first stage and then added to a minimal Linux image in the second stage, keeping the final 
image as lightweight as possible. This approach not only reduces the image size but also enhances control over the 
build, making it a flexible and efficient solution for large systems.</p>

<hr />

<div class="Article-Text"><span><h2>Advanced build with Dockerfile</h2>
<div>If you want something done right, do it yourself. No plugin knows your application like you do. So the next level
    is writing optimized Dockerfiles.
</div>
<div><br /></div>
<div>The first thing we'll do is reduce the JDK size. This is possible thanks to modules added since Java 9. All classes
    in the JDK have been divided into these modules. We will use only those that are truly necessary.
</div>
<div><br /></div>
<div><img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00010-1.jpg" /><br /></div>
<div><br /></div>
<div>But how do you know which modules are needed for the application and which are not? Moreover, you must also
    consider all application dependencies. If they use a class from a module you don't include, the application will
    either fail to build or crash at runtime.
</div>
<div><br /></div>
<div>To determine the necessary modules, use the jdeps utility located in the bin folder of your JDK. But first, we need
    to build our JAR and get all dependencies. To do this, run the command:
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>mvn clean install dependency:copy-dependencies</div></pre>
<div><br /></div>
<div>Scan all the obtained JARs:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>jdeps --ignore-missing-deps -q -recursive --multi-release 9 --print-module-deps --class-path 'target/dependency/*' target/*.jar</div></pre>
<div><br /></div>
<div>The --multi-release 9 flag may not be suitable for you; try changing it.</div>
<div><br /></div>
<div>This command will output the module names used by the application and all dependencies. In my case, they are:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>java.base, java.compiler, java.desktop, java.instrument, java.management, java.prefs, java.rmi, java.scripting, java.security.jgss, java.sql.rowset, jdk.httpserver, jdk.jfr, jdk.unsupported</div></pre>
<div><br /></div>
<div>Now that we know which JDK modules are involved in the application, let's build our custom JDK from these modules
    using the jlink utility.
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>jlink --add-modules [your_modules_here] --strip-debug --no-man-pages --no-header-files --compress=2 --output javaruntime</div></pre>
<div><br /></div>
<div>Replace the value for the --add-modules flag with the package names found by jdeps. After running this command, you
    will get a javaruntime folder in the project root. This is a trimmed-down version of the JDK specifically for your
    JAR. In my case, the trimmed version weighs 50 MB, while the full JDK weighs 315 MB.
</div>
<div><br /></div>
<div>Now let's optimize the spring-boot-jar. I think it's no secret that you can simply unpack the spring-boot-jar using
    an archiver:
</div>
<div><br /></div>
<div><img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00010-2.jpg" /><br /></div>
<div><br /></div>
<div>Here you can find all dependencies, including Tomcat, code, and application resources. It makes sense to organize
    everything into layers as spring-plugin and Jib do.
</div>
<div><br /></div>
<div>But before unpacking our JAR with an archiver, let's use the layertools utility. It allows us to unpack our JAR a
    bit smarter.
</div>
<div><br /></div>
<div>Create a separate folder and navigate to it:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>mkdir build-app &amp;&amp; cd build-app</div></pre>
<div><br /></div>
<div>Then run the unpacking command:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>java -Djarmode=layertools -jar ../target/*.jar extract</div></pre>
<div><br /></div>
<div>You should see four folders:</div>
<div>
    <ul>
        <li>application: Your code is here.</li>
        <li>snapshot-dependencies: Snapshot dependencies are here.</li>
        <li>spring-boot-loader: Spring boot loaders are here.</li>
        <li>dependencies: Release dependencies are here.</li>
    </ul>
</div>


<div>Now it's time to combine all this knowledge into a single Dockerfile.</div>
<div><br /></div>
<div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">    <div>FROM eclipse-temurin:17 as app-build</div>    <div>ENV RELEASE=17</div><br /><div>WORKDIR /opt/build</div>    <div>COPY ./target/spring-boot-*.jar ./application.jar</div><br /><div>RUN java -Djarmode=layertools -jar application.jar extract</div>    <div>RUN $JAVA_HOME/bin/jlink \</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--add-modules `jdeps --ignore-missing-deps -q -recursive --multi-release
        ${RELEASE} --print-module-deps -cp 'dependencies/BOOT-INF/lib/*':'snapshot-dependencies/BOOT-INF/lib/*'
        application.jar` \<br /></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--strip-debug \</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--no-man-pages \</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--no-header-files \</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--compress=2 \</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--output jdk</div>    <div>FROM debian:buster-slim</div>    <div>ARG BUILD_PATH=/opt/build</div>    <div>ENV JAVA_HOME=/opt/jdk</div>    <div>ENV PATH "${JAVA_HOME}/bin:${PATH}"</div>    <div>RUN groupadd --gid 1000 spring-app \</div><div>&nbsp; &amp;&amp; useradd --uid 1000 --gid spring-app --shell /bin/bash --create-home spring-app</div>    <div>USER spring-app:spring-app</div>    <div>WORKDIR /opt/workspace</div>    <div>COPY --from=app-build $BUILD_PATH/jdk $JAVA_HOME</div>    <div>COPY --from=app-build $BUILD_PATH/spring-boot-loader/ ./</div><br /><div>COPY --from=app-build $BUILD_PATH/dependencies/ ./</div>    <div>COPY --from=app-build $BUILD_PATH/snapshot-dependencies/ ./</div>    <div>COPY --from=app-build $BUILD_PATH/application/ ./</div>    <div>ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]</div>
</pre>
    <div><br /></div>
    <div>
        <div>If the project does not use snapshot dependencies, remove 'snapshot-dependencies/BOOT-INF/lib/' from the
            jlink command. Otherwise, the build will fail because the /BOOT-INF/lib/ folders will not exist.
        </div>
        <div><br /></div>
        <div>To build this Dockerfile on Windows, replace
            'dependencies/BOOT-INF/lib/':'snapshot-dependencies/BOOT-INF/lib/' with
            'dependencies/BOOT-INF/lib/';'snapshot-dependencies/BOOT-INF/lib/'. The difference lies in the delimiter:
            Linux uses ':', while Windows uses ';'.
        </div>
        <div>It may look intimidating, but by now, you are familiar with most of it. Don't forget to run the command mvn
            clean package to get the target folder with the JAR file.
        </div>
        <div><br /></div>
        <div>The Dockerfile contains 2 FROM commands. This is called a multi-stage build. In the first stage, we build
            the necessary JDK for our application, and in the second stage, we build the image. The subtlety here is
            that in the second build stage, we only take the necessary files, namely the JDK files.
        </div>
        <div><br /></div>
        <div>Let's break down the first stage. It doesn't matter which base image you specify; the important thing is
            that JDK is installed there. We transfer our JAR there, then unpack it, determine the dependencies used, and
            build the JDK. With that, the first build stage is complete. Let's move on to the second stage.
        </div>
        <div><br /></div>
        <div>Here, as the base image, we use an image on which the application will run at runtime. Usually, this is the
            thinnest possible Linux. Next, we specify the JAVA_HOME environment variable and add it to PATH.
        </div>
        <div><br /></div>
        <div>After that, we copy our JDK and JAR layers from the previous build stage. To do this, along with COPY, we
            use the --from=app-build flag, which indicates that we are copying files not from our machine but from the
            build stage under the alias app-build. Don't forget about the sequence; the less chance of a layer changing,
            the earlier it is specified. The last line specifies the Spring loader that will launch our application.
        </div>
    </div>
    <div><br /></div>
    <div>If desired, you can also separate the resources folder into a separate layer.</div>
    <div><br /></div>
    <div>Let's explore the image layers. Our image weighs only 173 MB:</div>
    <div><br /></div>
    <div>
        <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00010-3.jpg" /><br /></div>
    <div>
        <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00010-4.jpg" /></div>
    <div>
        <ul>
            <li>63 MB — Linux;</li>
            <li>338 KB — added due to user creation;</li>
            <li>56 MB — our custom JDK;</li>
            <li>53 MB — release dependencies;</li>
            <li>252 KB — Spring loaders;</li>
            <li>14 KB — snapshot dependencies;</li>
            <li>34 KB — our code and resources.</li>
        </ul>
    </div>

    <h2>Conclusions on Dockerfile-pro:</h2>
    <div>We've eliminated almost all the downsides of Dockerfiles, turning them into advantages. With this build, you
        have the opportunity to customize everything to your liking. It's worth noting that this is the smallest image
        of all.
    </div>
    <div><br /></div>
    <div>You can also use build capabilities for different platforms, a feature that plugins lack.</div>
    <h2>In summary:</h2>
    <div>In this article, we've explored various ways to create images. Each method has its pros and cons.</div>
    <div><br /></div>
    <div>If you need quick results, use the spring-boot-maven-plugin or Jib. Remember that they currently cannot build
        images for ARM processors, and Jib runs the application on behalf of the root user.
    </div>
    <div><br /></div>
    <div>A properly written Dockerfile will be the best option for a large system. Pay special attention to writing your
        Dockerfile.
    </div>

</div></span></div>]]></content><author><name></name></author><category term="springboot" /><category term="docker" /><category term="optimization" /><category term="image" /><category term="post" /><summary type="html"><![CDATA[The article demonstrates how to write optimized Dockerfiles using multi-stage builds, minimizing the image size by customizing the JDK and organizing dependencies into layers.]]></summary></entry><entry><title type="html">The optimal Docker image for Spring Boot. Part 2</title><link href="https://surkoff.com/blog/optimal-docker-image-for-springboot-2" rel="alternate" type="text/html" title="The optimal Docker image for Spring Boot. Part 2" /><published>2024-10-15T00:00:00+00:00</published><updated>2024-10-15T00:00:00+00:00</updated><id>https://surkoff.com/blog/optimal-docker-image-for-springboot-2</id><content type="html" xml:base="https://surkoff.com/blog/optimal-docker-image-for-springboot-2"><![CDATA[<p>This article discusses the use of the spring-boot-maven-plugin to simplify the process of packaging Spring Boot 
applications into Docker images. The plugin allows developers to easily create a Docker image without manually writing 
a Dockerfile, while also optimizing the image structure by layering dependencies for better caching. The article 
explains how to configure the image name, build the Docker image using Maven, and analyze the resulting layers for more 
efficient deployment.</p>

<hr />

<div class="Article-Text"><span><h2>Spring Boot Plugin</h2>
<p>Spring Boot simplifies development to the maximum. Added a couple of starters, filled in
    the application.properties, and voilà, the microservice is ready. Seriously, take a look at the Spring Data REST
    project, which generates controllers based on JpaRepository.</p>
<p>Obviously, something was also invented
    for containerization. And it's the good old spring-boot-maven-plugin. It can not only transform a regular JAR into a
    JAR file with an embedded Tomcat but also build a full-fledged Docker image.</p>
<p>First, in the plugin
    configuration, let's specify the name of the future image. If you don't specify a tag, it will automatically be set
    to the latest.</p>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">
&lt;build&gt;
    &nbsp;&nbsp;&lt;plugins&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&lt;plugin&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupid&gt;org.springframework.boot&lt;/groupid&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactid&gt;spring-boot-maven-plugin&lt;/artifactid&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;configuration&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;imagename&gt;upagge/spring-boot-docker:spring-plugin&lt;/imagename&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/configuration&gt;
    &nbsp;&nbsp;&nbsp;&nbsp;&lt;/plugin&gt;
    &nbsp;&nbsp;&lt;/plugins&gt;
&lt;/build&gt;
</pre>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"># Image version
# If you want to specify the image version, you can do so using Maven project variables:
<imagename>upagge/spring-boot-docker:${project.version}</imagename>
</pre>
<p>To build, run the command:</p>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">mvn spring-boot:build-image</pre>
<p>And voilà, we have a working image. Let's explore the layers created by Spring:</p>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">dive upagge/spring-boot-docker:spring-plugin</pre>
<p>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00008-1.jpg" />
    <br />
</p>
<p>The created image weighs 309 megabytes, which is 139 megabytes less than the one we built ourselves. But even more
    notable is the structure of the image.</p><p></p>
<img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00008-2.jpg" />
<p></p>
<ul>
    <li>63 mb - still Linux;</li>
    <li>24 mb - various certificates;</li>
    <li>1.4 kb - clearly adds a user, on behalf of which the application will run;</li>
    <li>157 mb - layer with JDK;</li>
    <li>53 mb - separate layer of release dependencies;</li>
    <li>252 kb - layer with all Spring Boot loaders;</li>
    <li>14 kb – layer of snapshot dependencies;</li>
    <li>34 kb - actual code we wrote and resources for it;</li>
</ul><p></p><p>An important optimization has been made here - application dependencies are placed in separate layers.
    Thanks to this, they will also be cached and reused by Docker. Therefore, when making changes to the code, you will
    be sending not 53 megabytes (jar weight), but only 3-5 mb.</p><p>Moreover, an important feature is that snapshot
    dependencies are placed in a separate layer from the release dependencies. After all, the likelihood of their
    changes is much higher.</p><p>Let's launch our container.</p>
<p>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00008-3.jpg" />
    <br />
</p>
<p>Notice how much information was provided to us before starting. Spring has made some optimizations for us, as
    reported in this log:</p><p></p>
<ol>
    <li>It clearly still doesn't know how to build images for M1.</li>
    <li>Set up 5 active processors.</li>
    <li>Calculated available memory for JVM. And then distributed it.</li>
    <li>Displayed all additional keys applied at startup.</li>
</ol><p></p><h2>Conclusions on the spring-plugin</h2><p>An even simpler packaging method that uses optimizations because
    it understands the peculiarities of Spring Boot Java applications. The main thing is to monitor what it does under
    the hood so that these advantages do not turn into problems.</p>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><p>If you have an arm processor, the image will still be built for amd64.</p></pre>
<p>A separate user is created to run the application. This is considered a more secure method.</p><p>Also, we do not
    need to create a Dockerfile, which does not give us the flexibility in image settings needed for complex
    projects.</p><p>A good packaging method for simple pet projects.</p>
</span></div>]]></content><author><name></name></author><category term="springboot" /><category term="docker" /><category term="optimization" /><category term="image" /><category term="post" /><summary type="html"><![CDATA[The article covers using the spring-boot-maven-plugin to package Spring Boot applications into optimized Docker images with improved caching and no need for a Dockerfile.]]></summary></entry><entry><title type="html">The optimal Docker image for Spring Boot. Part 1</title><link href="https://surkoff.com/blog/optimal-docker-image-for-springboot-1" rel="alternate" type="text/html" title="The optimal Docker image for Spring Boot. Part 1" /><published>2024-10-14T00:00:00+00:00</published><updated>2024-10-14T00:00:00+00:00</updated><id>https://surkoff.com/blog/optimal-docker-image-for-springboot-1</id><content type="html" xml:base="https://surkoff.com/blog/optimal-docker-image-for-springboot-1"><![CDATA[<p>This article discusses popular methods for packaging a Spring Boot application into a Docker container, 
emphasizing the importance of optimal packaging to avoid security vulnerabilities. It explores four specific 
approaches: a simple Dockerfile, building with the spring-boot-maven-plugin, using Google’s Jib plugin, and writing 
an optimized Dockerfile. The article also provides practical steps for building and running the application, along 
with an analysis of the resulting Docker image.</p>

<hr />

<div class="Article-Text"><span><div>Let's consider popular ways to package an application into a container. We'll write our optimal Dockerfile for
    Spring Boot.
</div>
<div><br /></div>
<div>We live in the era of microservices architecture, and containers have become the primary means of packaging and
    delivering applications to various environments. However, many developers do not pay enough attention to how to
    properly package a service, how to do it optimally, and most importantly, how not to leave security holes. In this
    article, we will explore 4 packaging methods:
</div>
<div><br /></div>
<div>- Simple Dockerfile - build and get the image.</div>
<div>- Building with the spring-boot-maven-plugin.</div>
<div>- Using the special Jib plugin from Google.</div>
<div>- Writing an optimized Dockerfile.</div>
<br />
<div>For experiments, we will use the following project: github.com/Example-uPagge/spring_boot_docker. This is a Spring
    Boot application that contains a couple of simple controllers and repositories with an H2 database.
</div>
<h2>Simple Dockerfile</h2>
<div>A typical Dockerfile that developers write looks like this:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"><div>
FROM openjdk:17.0.2-jdk-slim-buster
</div>
<div>ARG JAR_FILE=target/*.jar</div>
<div>COPY ${JAR_FILE} app.jar</div>
<div>ENTRYPOINT ["java","-jar","/app.jar"]</div>
</pre>

<div><br /></div>
<div>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00007-1.jpg" />
    <br /></div>
<div><br /></div>
<div>Like a cake, a Docker image consists of a stack of layers. Each represents a change from the previous layer. When
    we pull a Docker image from the registry, it is pulled layer by layer and cached on the host.
</div>
<div><br /></div>
<div>Our typical Docker image consists of a base layer with Linux - the thinner, the better. Then comes the JDK layer.
    And on top is the application layer, which is effectively just a jar file.
</div>
<div><br /></div>
<div>Spring Boot uses the "Fat JAR" packaging format by default. This means that all dependencies required for execution
    are added to a single JAR file.
</div>
<div><br /></div>
<div>But enough theory, let's move on to practice. To obtain the image, first build the application using Maven:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">mvn clean package</pre>
<div>Then build the image:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">docker build -t upagge/spring-boot-docker:dockerfile .</pre>
<div>In this case, 'upagge' is your DockerHub login, 'spring-boot-docker' is the image name, and 'dockerfile' is the
    tag. If you do not plan to push the image to DockerHub, you can specify the name without using the login. For
    example:
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">docker build -t spring-boot-docker:0.0.1 .</pre>
<div>To analyze the image, use the dive utility. Dive allows you to show differences between layers: which files were
    added, changed, or removed.
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">dive upagge/spring-boot-docker:dockerfile</pre>
<div><br /></div>
<div>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00007-2.jpg" /><br /></div>
<div><br /></div>
<div>The total size of the image is 448 MB, of which:</div>
<div><br /></div>
<div>
    <img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00007-3.jpg" /><br /></div>
<div>
    <ul>
        <li>63+8.4 MB is the Linux layer.</li>
        <li>324 MB is the JDK size.</li>
        <li>53 MB is our spring-boot-jar.</li>
    </ul>
</div>
<div>Pay attention to the application layer size: 53 megabytes, even though the project contains almost no code. With
    any code change, we would have to send 53 megabytes over the network and the server would have to download 53
    megabytes. The other layers are unlikely to change, so Docker will not transmit them over the network and will use
    the cache. We'll figure out how to fix this shortly.
</div>
<div><br /></div>
<div>To ensure everything works, run the image with the command:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">docker run -p 8080:8080 upagge/spring-boot-docker:0.0.1</pre>
<div>Open a browser and go to: http://localhost:8080/api/person/1. If the image was started successfully, you will see
    the word 'valid' in response.
</div>
<div><br /></div><h3>Conclusions on Dockerfile</h3>
<div>This is how easily and simply we can package an application. However, this approach has several drawbacks that we
    will address shortly.
</div>
<div><br /></div>
<div>Pros of this approach:</div>
<div>
    <ul>
        <li>Full control. You can build your image as you like.</li>
        <li>Fairly simple method.</li>
    </ul>
</div>
<div>Cons:</div>
<div>
    <ul>
        <li>Full control. You can break something or create a security hole.</li>
        <li>We haven't even started development, and the image is already heavy.</li>
        <li>A large volume of changing layers.</li>
        <li>Runs as root user.</li>
    </ul>
</div></span></div>]]></content><author><name></name></author><category term="springboot" /><category term="docker" /><category term="optimization" /><category term="image" /><category term="post" /><summary type="html"><![CDATA[The article outlines various methods for packaging a Spring Boot application into a Docker container, highlighting best practices and practical implementation steps.]]></summary></entry><entry><title type="html">Jenkins: executing script over ssh in a pipeline</title><link href="https://surkoff.com/blog/jenkins-executing-script-pipeline" rel="alternate" type="text/html" title="Jenkins: executing script over ssh in a pipeline" /><published>2024-10-13T00:00:00+00:00</published><updated>2024-10-13T00:00:00+00:00</updated><id>https://surkoff.com/blog/jenkins-executing-script-pipeline</id><content type="html" xml:base="https://surkoff.com/blog/jenkins-executing-script-pipeline"><![CDATA[<p>This article explains how to use the Jenkins Pipeline with the sshPublisher plugin to run a bash script on a remote 
server. It covers key steps, such as setting up server authentication by generating and copying SSH keys, configuring 
Jenkins with the necessary plugin, and running the pipeline to transfer and execute files remotely. The final step is 
testing and verifying the pipeline output on the remote server.</p>

<hr />

<div class="Article-Text"><span><div>In this post, I'll demonstrate the process of executing a bash script on a remote server using a Jenkins Pipeline
    Step with the sshPublisher plugin.
</div>
<div><br /></div>
<h2>Server authentication setup</h2>
<div><br /></div>
<div>Generate a new key pair by logging in with the Jenkins service user and creating a key pair without a passphrase
    (leave it blank or set it for private key security):<br /><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"># using the service user
sudo su -s /bin/bash jenkins
# generate key pair
ssh-keygen -f ~/.ssh/id_pipessh -t rsa
</pre>
<div><br /></div>
<div>The private key ('id_pipessh') and the public key ('id_pipessh.pub') will be created in ~/.ssh</div>
<div><br /></div>
<div>Copy the public key to the remote server:</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">ssh-copy-id -i ~/.ssh/id_pipessh.pub user@host</pre>
<div><br /></div>
<div># if the ssh-copy-id command is not available, do it manually</div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">cat ~/.ssh/id_pipessh.pub | ssh user@host "mkdir -p ~/.ssh &amp;&amp; touch ~/.ssh/authorized_keys $$ chmod -R go= ~/.ssh &amp;&amp; cat &gt;&gt; /.ssh/authorized_keys"</pre>
<div><span style="font-weight: bold;"><br /></span></div>
<div>Note: If the password prompt is disabled on the remote server, ask the administrator to add your public key to the
    file '/.ssh/authorized_keys' (create it if it doesn't exist) and ensure proper permissions:
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh/</pre>
<div>2. Enable key authentication on the remote server by editing the /etc/ssh/sshd_config file and ensuring that
    'PubkeyAuthentication yes' is set. Save it and restart the sshd service. In my case, I'm using an RHEL server:
</div>
<div><br /></div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">sudo systemctl restart sshd</pre>
<div><br /></div>
<div>Test the connection:</div>
<pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);"># using verbose mode -v
ssh -i ~/.ssh/id_pipessh user@host -v</pre>
<div><br /></div>
<h2>Jenkins setup</h2>
<div><br /></div>
<div>Install the plugin by navigating to Manage Jenkins &gt; Manage Plugins &gt; Available, check 'Publish Over SSH',
    and select 'install without restart'.<br /><br /><img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00006-2.jpg" /><br /></div>
<div><br /></div>
<div>Configure the ssh key in Jenkins by going to Manage Jenkins &gt; Configure System &gt; Publish over SSH. Select the
    Add button &gt; Advanced to set configuration. Complete the fields Name, Hostname, Username, Remote Directory, check
    the option 'Use password authentication, or use a different key', set the Passphrase (if applicable), and Path to
    key to the private key.
</div>
<div><img style="height: auto; display: block; margin: auto;" src="/assets/images/posts/2024/00006-3.jpg" /><br /></div>
<div><br /></div>
<div>Finally, click 'Test Configuration' to validate if everything is set up correctly.</div>
<div><br /></div>
<div>Create a new Pipeline and add the script content for creating two text files in a zip to be transferred to the
    remote server for unzipping.
</div>
<div><br /></div>
<div><pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">    pipeline {
  agent any
  stages {
    stage('ssh') {
      steps {
        script{
          cleanWs()
          sh "echo 'hello' &gt;&gt; file1.txt"
          sh "echo 'hello' &gt;&gt; file2.txt"
          sh "zip -r oneFile.zip file1.txt file2.txt"
          echo 'Local files.....'
          sh 'ls -l'
          command='''
          unzip -o -d ./ oneFile.zip
          ls -l
          date
          cat /etc/os-release
          '''
        }

        // Copy file to remote server
        sshPublisher(publishers: [sshPublisherDesc(configName: 'dummy-server',
        transfers: [ sshTransfer(flatten: false,
        remoteDirectory: './',
        sourceFiles: 'oneFile.zip'
        )])
        ])
      }
    }
}
}

</pre>
</div>
<div><br /></div>
<div>Save and execute the pipeline.</div>
<div><br /></div>
<div>Jenkins output log:<br />
    <pre style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 12px; line-height: 18px; font-family: Consolas, Monaco, Monospaced, monospace; margin-top: 5px; margin-bottom: 5px; padding: 5px; vertical-align: baseline; border: 1px solid rgb(154, 154, 154); outline: 0px; background-image: none; background-position: 0px 0px; background-repeat: repeat; background-attachment: scroll; background-color: rgb(241, 241, 241); max-width: 100%; overflow: auto; color: rgb(64, 64, 64);">[Pipeline] {
[Pipeline] stage
[Pipeline] { (ssh)
[Pipeline] script
[Pipeline] {
[Pipeline] cleanWs
[WS-CLEANUP] Deleting project workspace...
[WS-CLEANUP] Deferred wipeout is used...
[WS-CLEANUP] done
[Pipeline] sh
+ echo hello
[Pipeline] sh
+ echo hello
[Pipeline] sh
+ zip -r oneFile.zip file1.txt file2.txt
  adding: file1.txt (stored 0%)
  adding: file2.txt (stored 0%)
[Pipeline] echo
Local files.....
[Pipeline] sh
+ ls -l
total 12
-rw-r--r--. 1 jenkins jenkins   6 jun 22 19:36 file1.txt
-rw-r--r--. 1 jenkins jenkins   6 jun 22 19:36 file2.txt
-rw-r--r--. 1 jenkins jenkins 326 jun 22 19:36 oneFile.zip
[Pipeline] sshPublisher
SSH: Connecting from host [dummy]
SSH: Connecting with configuration [dummy-server] ...
SSH: Disconnecting configuration [dummy-server] ...
SSH: Transferred 1 file(s)
[Pipeline] sshPublisher
SSH: Connecting from host [dummy]
SSH: Connecting with configuration [dummy-server] ...
SSH: EXEC: STDOUT/STDERR from command [
                        unzip -o -d ./ oneFile.zip
                        ls -l
                        date
                        cat /etc/os-release
                    ] ...
Archive:  oneFile.zip
 extracting: ./file1.txt
 extracting: ./file2.txt
total 16
-rw-r--r--. 1 asanchez asanchez    6 jun 22 19:36 file1.txt
-rw-r--r--. 1 asanchez asanchez    6 jun 22 19:36 file2.txt
-rw-rw-r--. 1 asanchez asanchez  326 jun 22 19:36 oneFile.zip
drwxrwxr-x. 2 asanchez asanchez 4096 jun 20 23:31 out
mié jun 22 19:36:22 CDT 2022
NAME="Red Hat Enterprise Linux Server"
VERSION="7.4 (Maipo)"
ID="rhel"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="7.4"
PRETTY_NAME="Red Hat Enterprise Linux"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:7.4:GA:server"
HOME_URL="https://www.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"

REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
REDHAT_BUGZILLA_PRODUCT_VERSION=7.4
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="7.4"
SSH: EXEC: completed after 200 ms
SSH: Disconnecting configuration [dummy-server] ...
SSH: Transferred 0 file(s)
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS</pre>
<br />And that is all!
</div></span></div>]]></content><author><name></name></author><category term="ssh" /><category term="post" /><category term="jenkins" /><category term="publish" /><category term="pipeline" /><summary type="html"><![CDATA[This guide demonstrates executing remote scripts through Jenkins Pipelines using SSH authentication and the sshPublisher plugin, detailing configuration and execution steps.]]></summary></entry><entry><title type="html">GitHub Profile Trophy: enhance your profile</title><link href="https://surkoff.com/blog/GitHub-Profile-Trophy" rel="alternate" type="text/html" title="GitHub Profile Trophy: enhance your profile" /><published>2024-10-12T00:00:00+00:00</published><updated>2024-10-12T00:00:00+00:00</updated><id>https://surkoff.com/blog/GitHub-Profile-Trophy</id><content type="html" xml:base="https://surkoff.com/blog/GitHub-Profile-Trophy"><![CDATA[<p>GitHub Profile Trophy is a service that enhances your GitHub profile with visual trophies showcasing your achievements, 
such as the number of repositories and open-source contributions. To use it, visit the GitHub Profile Trophy website, 
enter your account name, and get the code to insert into your README. You can customize the trophies’ theme, 
number of columns, and categories to make your profile more engaging and visually appealing.</p>

<hr />

<p><b>GitHub Profile Trophy: enhance your profile</b></p>

<p>GitHub Profile Trophy is a service to enhance your GitHub profile with visual trophies that showcase your achievements: number of repositories, open-source contributions, and much more.</p>

<p><b>How to use?</b></p>

<p>Go to <a href="https://github-profile-trophy.vercel.app/">GitHub Profile Trophy</a>, enter your account name, and get the code to insert into your README:</p>

<p><code>[![trophy](https://github-profile-trophy.vercel.app/?username=your_username)](https://github.com/ryo-ma/github-profile-trophy)</code></p>

<p><b>Settings</b></p>

<p>You can customize the theme, number of columns, and categories:</p>

<p><code>[![trophy](https://github-profile-trophy.vercel.app/?username=your_username&amp;theme=onedark&amp;row=2&amp;column=3)]</code></p>

<p><img src="/assets/images/posts/2024/00002-2.jpg" alt="Github profile trophy" /></p>

<p><b>Advantages</b></p>

<p><i>Visual appeal</i>: makes your profile more engaging. <i>Flexibility</i>: customize the trophies to your preferences. GitHub is your portfolio, and this service helps make it stand out and look better.</p>

<p><a href="https://github-profile-trophy.vercel.app/">Try it now</a>.</p>

<p>You can check out my trophies on <a href="https://github.com/s-rb">my profile</a>.</p>]]></content><author><name></name></author><category term="icons" /><category term="post" /><category term="github" /><category term="profile" /><summary type="html"><![CDATA[GitHub Profile Trophy enhances your GitHub profile with customizable visual trophies that showcase your achievements, making your portfolio more engaging]]></summary></entry><entry><title type="html">Deploying Multiple Apps on a Single VPS</title><link href="https://surkoff.com/blog/deploying-multiple-apps-docker" rel="alternate" type="text/html" title="Deploying Multiple Apps on a Single VPS" /><published>2024-10-12T00:00:00+00:00</published><updated>2024-10-12T00:00:00+00:00</updated><id>https://surkoff.com/blog/deploying-multiple-apps-docker</id><content type="html" xml:base="https://surkoff.com/blog/deploying-multiple-apps-docker"><![CDATA[<p>Deploying multiple applications on a single VPS using Docker allows for efficient isolation and management of each app 
in containers. By installing Docker and creating a docker-compose.yml file, you can easily launch and manage services 
like Node.js and PHP. Additionally, tools like Portainer provide a user-friendly web interface for streamlined 
container management, making updates and oversight simpler.</p>

<hr />

<p><b>Deploying Multiple Apps on a Single VPS with Docker</b></p>

<p>Docker enables efficient deployment of multiple applications on a single VPS by isolating them in containers. This simplifies management and updates.</p>

<p><b>Step 1: Install Docker</b></p>

<p>Install Docker on your server. For Ubuntu, use the following commands:</p>

<pre><code>sudo apt update
sudo apt install docker.io</code></pre>

<p>Start Docker:</p>

<pre><code>sudo systemctl start docker
sudo systemctl enable docker</code></pre>

<p><b>Step 2: Using Docker Compose</b></p>

<p>Create a <code>docker-compose.yml</code> file to manage your containers. Here’s an example with two apps (Node.js and PHP):</p>

<pre><code>version: '3'
services:
  node-app:
    image: node:14
    ports:
      - "3000:3000"
  php-app:
    image: php:7.4-apache
    ports:
      - "8080:80"</code></pre>

<p>Launch the services:</p>

<pre><code>docker-compose up -d</code></pre>

<p><b>Step 3: Manage via Portainer</b></p>

<p>To simplify container management, use <a href="https://www.portainer.io/">Portainer</a>, a web-based UI for managing Docker containers. To install it:</p>

<pre><code>docker run -d -p 9000:9000 --name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data portainer/portainer-ce</code></pre>

<p>Once installed, Portainer will be available on port <code>9000</code>.</p>

<p><b>Conclusion</b></p>

<p>Using Docker and tools like Portainer allows you to deploy and manage multiple applications on a single VPS easily, simplifying service management and updates.</p>]]></content><author><name></name></author><category term="docker" /><category term="post" /><category term="vps" /><category term="portainer" /><summary type="html"><![CDATA[Docker enables the efficient deployment and management of multiple applications on a single VPS through containerization, simplifying updates and service management with tools like Portainer.]]></summary></entry><entry><title type="html">Commit Message Style in Git</title><link href="https://surkoff.com/blog/commit-messages" rel="alternate" type="text/html" title="Commit Message Style in Git" /><published>2024-10-11T00:00:00+00:00</published><updated>2024-10-11T00:00:00+00:00</updated><id>https://surkoff.com/blog/commit-messages</id><content type="html" xml:base="https://surkoff.com/blog/commit-messages"><![CDATA[<p>Maintaining a clear and structured change history in Git is essential for effective version control, and following best 
practices for commit messages can greatly enhance this process. Key guidelines include writing concise messages in the 
imperative mood, explaining the reasons for changes, and separating messages into a clear header and body. By grouping 
related changes and regularly reviewing the commit history, you can ensure better organization and collaboration 
within your projects.</p>

<hr />

<p><b>Commit Message Style in Git</b></p>

<p>Working with Git is not only about version control, but also about maintaining a change history that can be clear and structured. Below are best practices for writing commit messages:</p>

<p><b>1. Write a clear and concise message</b>
The commit message should be clear and explain the essence of the change. For example:</p>
<pre><code>git commit -m "Fixed button display on the homepage"</code></pre>

<p><b>2. Use the imperative mood</b>
Phrase commit messages in the imperative mood. This helps create consistency in the history. Example:</p>
<pre><code>git commit -m "Add date filtering capability"</code></pre>

<p><b>3. Indicate the reason for changes</b>
A good practice is to briefly explain why you made the change. For example:</p>
<pre><code>git commit -m "Optimize image loading for better performance"</code></pre>

<p><b>4. Separate messages into a header and body</b>
Use a header of up to 50 characters, and then add a more detailed description after a blank line. Example:</p>
<pre><code>
git commit -m "Update library dependencies"

Updated libraries to the latest versions for improved security and performance.
</code></pre>

<p><b>5. Group related changes</b>
Avoid creating too many small commits. Try to combine logically related changes into one commit:</p>
<pre><code>git commit -m "Add new functionality: upload and delete files"</code></pre>

<p><b>6. Avoid using timestamps</b>
Do not include time or date in messages, as this is already recorded in the commit itself. Instead, it is better to specify what was changed:</p>
<pre><code>git commit -m "Fix bug in message sending function"</code></pre>

<p><b>7. Check the commit history</b>
Regularly review the commit history using the command:</p>
<pre><code>git log --oneline</code></pre>
<p>This will help you maintain order and structure in your project.</p>

<p><b>Conclusion</b>
By following these simple rules, you will be able to create a clear and accessible change history in your project, making work easier for both you and your colleagues. <tg-emoji emoji-id="5368324170671202286">👍</tg-emoji></p>]]></content><author><name></name></author><category term="git" /><category term="post" /><category term="commit" /><category term="codestyle" /><category term="best-practice" /><summary type="html"><![CDATA[Following best practices for commit messages in Git, such as writing concise and imperative statements while explaining changes, helps maintain a clear and structured change history for better collaboration and project management]]></summary></entry><entry><title type="html">25 Powerful and Free APIs Every Developer Should Know in 2024</title><link href="https://surkoff.com/blog/25-powerful-free-api" rel="alternate" type="text/html" title="25 Powerful and Free APIs Every Developer Should Know in 2024" /><published>2024-10-10T00:00:00+00:00</published><updated>2024-10-10T00:00:00+00:00</updated><id>https://surkoff.com/blog/25-powerful-free-api</id><content type="html" xml:base="https://surkoff.com/blog/25-powerful-free-api"><![CDATA[<p>Developers in 2024 have access to a variety of free APIs that can significantly enhance their projects. 
These 25 APIs range from weather data and news aggregation to AI-powered text generation and real-time exchange rates, 
allowing for diverse functionality across different application types. Whether you’re building entertainment apps with 
The Movie Database API or integrating powerful geolocation with Google Maps, these tools are invaluable for modern 
software development.</p>

<hr />

<p><b>25 Powerful and Free APIs Every Developer Should Know in 2024</b></p>

<p>In the world of development, access to powerful APIs can significantly simplify the task of creating applications. Here’s a list of 25 essential free APIs that will be useful for developers in 2024:</p>

<p><b>1. <a href="https://openweathermap.org/api">OpenWeatherMap</a></b>
Provides current weather data and forecasts. Ideal for applications related to weather.
<br /><strong>Usage:</strong> 60 calls per minute for free.</p>

<p><b>2. <a href="https://newsapi.org/">News API</a></b>
Collects the latest news from various sources to create your own news applications.
<br /><strong>Usage:</strong> 500 requests per day for free.</p>

<p><b>3. <a href="https://developers.google.com/maps/documentation">Google Maps API</a></b>
Integrates maps and geolocation features into your applications.
<br /><strong>Usage:</strong> Free tier includes $200 worth of credits monthly.</p>

<p><b>4. <a href="https://www.themoviedb.org/documentation/api">The Movie Database (TMDB) API</a></b>
Accesses information about movies, TV shows, and actors. Great for entertainment-related applications.
<br /><strong>Usage:</strong> No rate limits.</p>

<p><b>5. <a href="https://jsonplaceholder.typicode.com/">JSONPlaceholder</a></b>
Provides fake data for testing and prototyping your applications.
<br /><strong>Usage:</strong> Unlimited requests.</p>

<p><b>6. <a href="https://developer.spotify.com/documentation/web-api/">Spotify API</a></b>
Gives access to music data, playlists, and Spotify user information.
<br /><strong>Usage:</strong> Rate limits are based on user authentication; generally, around 10 requests per second.</p>

<p><b>7. <a href="https://api.github.com/">GitHub API</a></b>
Interact with GitHub: access repositories, commits, and user data.
<br /><strong>Usage:</strong> 5000 requests per hour for authenticated users.</p>

<p><b>8. <a href="https://developers.facebook.com/docs/graph-api">Facebook Graph API</a></b>
Integrates with Facebook to access user and page data.
<br /><strong>Usage:</strong> Rate limits depend on the access token type.</p>

<p><b>9. <a href="https://twitter.com/en/docs/twitter-api">Twitter API</a></b>
Access tweets and user data from Twitter.
<br /><strong>Usage:</strong> 900 requests per 15 minutes for user timeline.</p>

<p><b>10. <a href="https://www.boredapi.com/">Bored API</a></b>
Provides ideas for leisure activities if you’re unsure what to do.
<br /><strong>Usage:</strong> Unlimited requests.</p>

<p><b>11. <a href="https://api.exchangerate-api.com/">ExchangeRate API</a></b>
Offers real-time exchange rates for financial applications.
<br /><strong>Usage:</strong> 250 requests per month for free.</p>

<p><b>12. <a href="https://api.openai.com/">OpenAI API</a></b>
Leverage the power of AI for text generation and analysis.
<br /><strong>Usage:</strong> Pricing based on usage; free tier available.</p>

<p><b>13. <a href="https://ipgeolocation.io/">IP Geolocation API</a></b>
Determine user location based on their IP address.
<br /><strong>Usage:</strong> 1,000 requests per month for free.</p>

<p><b>14. <a href="https://www.feralhosting.com/api">Feral Hosting API</a></b>
Manage hosting and services through an API.
<br /><strong>Usage:</strong> Depends on the hosting plan.</p>

<p><b>15. <a href="https://developers.pocketbase.io/">PocketBase API</a></b>
A real-time database for mobile and web applications.
<br /><strong>Usage:</strong> Unlimited requests.</p>

<p><b>16. <a href="https://api.nasa.gov/">NASA API</a></b>
Access data about space, images, and NASA missions.
<br /><strong>Usage:</strong> 1,000 requests per hour for free.</p>

<p><b>17. <a href="https://docs.github.com/en/rest">GitHub REST API</a></b>
Additional access to GitHub resources and data.
<br /><strong>Usage:</strong> 5000 requests per hour for authenticated users.</p>

<p><b>18. <a href="https://developer.woocommerce.com/">WooCommerce API</a></b>
Integrates with the WooCommerce platform for online store management.
<br /><strong>Usage:</strong> Rate limits depend on server settings.</p>

<p><b>19. <a href="https://www.virustotal.com/gui/home/upload">VirusTotal API</a></b>
Checks files and URLs for viruses and threats.
<br /><strong>Usage:</strong> 4 requests per minute for free.</p>

<p><b>20. <a href="https://dog.ceo/dog-api/">Dog API</a></b>
Provides information and images of dogs for animal lovers.
<br /><strong>Usage:</strong> Unlimited requests.</p>

<p><b>21. <a href="https://catfact.ninja/">Cat Facts API</a></b>
Offers interesting facts about cats to entertain users.
<br /><strong>Usage:</strong> Unlimited requests.</p>

<p><b>22. <a href="https://rapidapi.com/">RapidAPI</a></b>
A platform for finding and integrating various APIs.
<br /><strong>Usage:</strong> Varies by individual API.</p>

<p><b>23. <a href="https://openai.com/api/">OpenAI GPT-3 API</a></b>
Creates powerful text generators and chatbots.
<br /><strong>Usage:</strong> Pricing based on usage; free tier available.</p>

<p><b>24. <a href="https://developer.yahoo.com/weather/">Yahoo Weather API</a></b>
Provides weather data from Yahoo.
<br /><strong>Usage:</strong> 2,000 requests per day for free.</p>

<p><b>25. <a href="https://www.openstreetmap.org/">OpenStreetMap API</a></b>
Access community-generated mapping data.
<br /><strong>Usage:</strong> Unlimited requests.</p>

<p><b>Conclusion</b>
These 25 APIs offer a wide range of capabilities for developers, enabling the creation of innovative applications that integrate diverse data and functionality. Use them to enhance your projects and make them more interactive. <tg-emoji emoji-id="5368324170671202286">👍</tg-emoji></p>]]></content><author><name></name></author><category term="api" /><category term="post" /><category term="developer" /><category term="free" /><summary type="html"><![CDATA[Here is a collection of 25 powerful free APIs that developers can leverage in 2024 to simplify app development, offering everything from weather data and news to AI capabilities and geolocation.]]></summary></entry></feed>