Skip to main content

Command Palette

Search for a command to run...

Effortless Deployment: Shipping Your Flutter Web App with Jenkins, SSH, and Docker

Published
7 min read
Effortless Deployment: Shipping Your Flutter Web App with Jenkins, SSH, and Docker

Moving your finished Flutter web application from your local development environment to a live server is an essential step. This guide explains how to set up an automated, secure, and robust deployment pipeline using Jenkins (for automation), SSH Keys (for security), and Docker (for containerization).

This process involves configuration on three systems: your Local Machine, the Remote Deployment Server, and Jenkins.

1️⃣ SSH Key Setup: The Secure Handshake

The first and most critical step is establishing a secure, password-less connection between your Jenkins Agent (or local machine) and the Remote Deployment Server. We do this using SSH keys.

Step 1.1: Generate the Key Pair (Local Machine/Jenkins Agent)

You need to create a pair of unique keys: a Private Key (kept secret) and a Public Key (shared widely).

Bash

ssh-keygen -t rsa -b 4096 -C "jenkins-deployment-key"
# Save to a dedicated file, e.g., ~/.ssh/id_rsa_jenkins_deploy
  • Result: You now have id_rsa_jenkins_deploy (Private) and id_rsa_jenkins_deploy.pub (Public).

Step 1.2: Add Public Key to Remote Server

The Public Key acts like a lock that only your Private Key can open. We need to install this "lock" on the server.

You can use the convenient ssh-copy-id command:

Bash

# Replace 'your-remote-user' and '192.168.1.100' with your actual server details
ssh-copy-id -i ~/.ssh/id_rsa_jenkins_deploy.pub your-remote-user@192.168.1.100
  • What this does: It appends the content of your public key (.pub file) to the ~/.ssh/authorized_keys file on the remote server.

Step 1.3: Verification

Always verify the connection works immediately:

Bash

# You should log in without being asked for a password.
ssh -i ~/.ssh/id_rsa_jenkins_deploy your-remote-user@192.168.1.100

2️⃣ Jenkins Configuration: Storing the Secret

Jenkins needs to hold onto the Private Key so it can securely connect to your server during the pipeline run.

Step 2.1: Install the SSH Agent Plugin

Make sure the SSH Agent Plugin is installed in Jenkins (Manage Jenkins $\rightarrow$ Manage Plugins). This plugin allows the pipeline to use the stored private key.

Step 2.2: Add Private Key to Jenkins Credentials

  1. Navigate to Manage Jenkins $\rightarrow$ Manage Credentials.

  2. Click Add Credentials.

  3. Kind: Select "SSH Username with Private Key".

  4. ID: This is CRITICAL. It must exactly match the SSH_CREDENTIAL_ID in your Jenkinsfile. In your configuration, this is remote-ssh-key-id.

  5. Username: Enter the remote user (e.g., ubuntu or your-remote-user).

  6. Private Key: Select "Enter directly" and paste the entire content of your Private Key file (id_rsa_jenkins_deploy).

Step 2.3: Jenkinsfile Variables Check

The Jenkins pipeline needs to know where to deploy. Ensure these variables in your Jenkinsfile are updated:

  • REMOTE_HOST: Should be in the format user@ip_or_hostname (e.g., 'root@46.62.255.210').

  • REMOTE_DEPLOY_DIR: The path on the server where the code will be copied (e.g., '/opt/shinee-trip-flutter-web').

  • SSH_CREDENTIAL_ID: Must match the credential ID you set (e.g., 'remote-ssh-key-id').


3️⃣ Remote Deployment Server Setup: Docker Environment

Your server must be ready to build and run your Flutter application using the Docker setup defined in your Dockerfile and docker-compose.yml.

Step 3.1: Install Docker and Docker Compose (V1)

The provided Jenkinsfile uses the command docker-compose (with a hyphen), which refers to the legacy Docker Compose V1 binary. You must install this specific version.

For Ubuntu:

Bash

# Install Docker Engine
sudo apt update
sudo apt install docker.io -y

# Install the legacy Docker Compose V1 binary (crucial for the Jenkinsfile)
sudo apt install docker-compose -y

# Verify the installation
docker-compose --version

Step 3.2: Configure Non-Root Docker Access

To allow Jenkins to run Docker commands without needing sudo (which is often safer and easier in pipelines), add the remote deployment user to the docker group.

Bash

# Replace 'your-remote-user' with the actual user
sudo usermod -aG docker your-remote-user

# IMPORTANT: You must log out and log back in to apply this group change!

4️⃣ The Deployment Flow (Jenkinsfile Explained)

Once the setup is complete, the Jenkinsfile orchestrates the entire deployment in two key stages:

A. Stage: Checkout Flutter Frontend Code

Jenkins first clones your source code from the Git repository (e.g., GitHub) into its workspace.

B. Stage: Deploy via SSH and Build Remotely

This is where the magic happens, all executed inside the sshagent block which injects your private key:

  1. Prepare Remote Directory:

    Bash

     ssh $REMOTE_HOST "mkdir -p $REMOTE_DEPLOY_DIR"
    

    Jenkins connects and creates the target folder on the remote server.

  2. Copy Files (rsync):

    Bash

     rsync -avz ... ./ $REMOTE_HOST:$REMOTE_DEPLOY_DIR
    

    The rsync command securely copies all the necessary files (including the Dockerfile and docker-compose.yml) from the Jenkins workspace to the remote server.

  3. Run Docker Compose Remotely:

    Bash

     ssh $REMOTE_HOST "cd $REMOTE_DEPLOY_DIR && docker-compose up -d --build"
    
    • Jenkins logs into the server again.

    • It navigates to the deployment directory (cd).

    • It uses docker-compose down (to stop any old running version) and then docker-compose up -d --build to:

      • Build the new image using your Dockerfile.

      • Start the container in detached mode (-d).

Your Dockerfile uses a two-stage build: a builder stage (to compile the Flutter web app) and a lightweight runner stage (using Nginx) to serve the static output files. This keeps the final deployed image small and secure.


With these steps complete, your next run of the Jenkins pipeline should successfully connect, transfer the code, and launch your containerized Flutter web application on the remote server!

// Dockerfile

# --- STAGE 1: Build the Flutter Web Application ---
# Using a slim Debian base is highly compatible across architectures (AMD64, ARM64)
FROM debian:bookworm-slim AS builder

# Install necessary build tools and dependencies
RUN apt-get update && \
   apt-get install -y --no-install-recommends \
   ca-certificates \
   curl \
   git \
   unzip \
   libglu1 \
   libgl1 \
   && rm -rf /var/lib/apt/lists/*

# Set environment variables for Flutter
# IMPORTANT: Update FLUTTER_VERSION to the exact version you need (e.g., 3.3.5)
ENV FLUTTER_VERSION 3.35.7
ENV FLUTTER_HOME /usr/local/flutter
ENV PATH "$PATH:$FLUTTER_HOME/bin"

# Download and install Flutter SDK from the official repository
# This guarantees you are using the official source code for the SDK.
RUN echo "Installing Flutter v$FLUTTER_VERSION from official source..." && \
   git clone https://github.com/flutter/flutter.git $FLUTTER_HOME \
   && cd $FLUTTER_HOME \
   && git checkout tags/$FLUTTER_VERSION -b v$FLUTTER_VERSION \
   && flutter precache --web

# Stage 1: The Builder

# Uses a Flutter SDK image to clean and build the web application assets.
# FROM coacfd/flutter:3.27.0 AS builder

# Set the working directory inside the container
WORKDIR /app

# Copy the pubspec.yaml and pubspec.lock files first to leverage caching
COPY pubspec.* ./

# Install dependencies
RUN flutter pub get

# Copy the rest of the application source code
COPY . .

# 1. Clean previous Flutter builds
RUN flutter clean

# 2. Execute the Flutter web build
# We use the '--release' flag and explicitly set the web-renderer for consistency.
RUN flutter build web --release

# Stage 2: The Runner (Serving the Static Assets)
# Uses a lightweight Nginx image to serve the compiled static files.
FROM nginx:stable-alpine AS runner

# Copy the compiled web assets from the builder stage into the Nginx serving directory
COPY --from=builder /app/build/web /usr/share/nginx/html

# Expose the default Nginx HTTP port
EXPOSE 80

# Nginx starts automatically
CMD ["nginx", "-g", "daemon off;"]
// docker-compose.yml

version: '3.8'

services:
 flutter-web:
   # Build the image using the Dockerfile in the current directory
   build:
     context: .
     dockerfile: Dockerfile

   # Optional: Assign a recognizable name to the running container
   container_name: shinee-web

   # Map the container port (80, where Nginx is running)
   # to a host port (8080), so you can access it via http://localhost:8080
   ports:
     - "8080:80"

   # Ensure the container restarts if it fails or is stopped
   restart: always

# How to Run:
# 1. Place both Dockerfile and docker-compose.yml in the root of your Flutter project.
# 2. Run the following command in your terminal:
#    docker compose up --build -d
# 3. Access the application in your browser: http://localhost:8080
// Jenkinsfile

pipeline {
   agent any

   environment {
       // IMPORTANT: Update this to your remote server details
       REMOTE_HOST = 'root@192.168.1.100'
       REMOTE_DEPLOY_DIR = '/opt/shinee-trip-flutter-web'
       // Jenkins credential ID configured with the SSH Private Key for the remote host
       SSH_CREDENTIAL_ID = 'remote-ssh-key-id' // *** Update this ID in Jenkins Credentials ***

       // Define SSH options once for clean usage
       SSH_OPTS = '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
   }

   stages {
       stage('Checkout Flutter Frontend Code') {
           steps {
               // !!! IMPORTANT: Replace this URL with your actual Flutter frontend repository !!!
               git branch: 'main', url: 'https://github.com/username/projectname.git', credentialsId: 'github_cred'
           }
       }

       stage('Deploy via SSH and Build Remotely') {
           agent any

           steps {
               sshagent(credentials: [env.SSH_CREDENTIAL_ID]) {
                   sh """

                   echo '1. Preparing remote deployment directory...'
                   # The SSH options must follow the 'ssh' command directly.
                   ssh \$SSH_OPTS \$REMOTE_HOST "mkdir -p \$REMOTE_DEPLOY_DIR"

                   echo '2. Copying files to remote server via rsync...'
                   # For rsync, the SSH options must be passed using the '-e' or '--rsh' flag.
                   rsync -avz --exclude='.git' --exclude='node_modules' --delete \\
                       -e "ssh \$SSH_OPTS" \\
                       ./ \$REMOTE_HOST:\$REMOTE_DEPLOY_DIR

                   echo '3. Running Docker Compose remotely...'
                   # The options go on the main 'ssh' command. Remote commands are chained with '&&'.
                   ssh \$SSH_OPTS \$REMOTE_HOST "\
                       cd \$REMOTE_DEPLOY_DIR && \
                       echo 'Starting remote build/deploy...' && \
                       docker-compose down || true && \
                       docker-compose up -d --build && \
                       echo 'Remote deployment complete!' \
                   "

                   echo 'Deployment successful. Flutter web application is now running on the remote host.'
                   """
               }
           }
       }
   }
}