Deploying Flutter Web with Docker
In March 2021, Flutter 2.0 included support for the web as a target platform. Still, there's no clear official instruction on how to set it up with a de-facto standard platform for web applications deployment - docker. In this article, we'll investigate how to make those two play nicely together. Without further ado, let's get to code!
Or jump straight to the TLDR to find an example setup.
Naive Implementation: Nginx Only Image
Let's build our app for the web release. Assuming, you are in the root folder of a Flutter app all you need to do is run the command below:
flutter build web
This will place our website files, like index.html
in the folder build/web
. All we need to do to get a fully functional web app is to host our files in a web server. Let's use nginx
. We'll need to put our built files in a dedicated Nginx folder. Here's the Dockerfile
we'll get:
FROM nginx
COPY /build/web /usr/share/nginx/html
Let's also create a compose.yml
file to simplify deploying our app
services:
example:
build: .
ports:
- "4444:80"
With the file in place, we can deploy our app by running the command below:
docker compose up -d --build
When it's finished, we should be able to see our app by navigating to localhost:4444
in a browser. Although the current version works there's a huge pitfall in the current implementation, so let's fix it now.
Full Dockerfile: Building Hosting Files
The problem with our first implementation is that we used pre-built files. A proper Dockerfile
should build the whole application from scratch, so that we don't have to rely on any other files for that. This is especially useful if you want to use your Dockerfile
in a CI pipeline. So let's create a proper image. Let's take ubuntu
, install the necessary dependencies, clone the flutter repo, and install the CLI command in the path:
FROM ubuntu AS build
RUN apt-get update
RUN apt-get install -y curl git unzip
RUN git clone https://github.com/flutter/flutter.git
ENV PATH="/flutter/bin:${PATH}"
With that, we'll be able to actually build our app from within the docker builder process. Let's copy our files, move to where we've copied them, clean
the folder just to make sure we won't be affected by any unexpected caching, and finally build our app:
COPY . /app
WORKDIR /app
RUN flutter clean
RUN flutter build web
With that in place, we'll just need to change our existing deployment via nginx to copy files not from our local machine, but from the previous stage we called build
:
FROM nginx
COPY --from=build /app/build/web /usr/share/nginx/html
Here's the complete file:
FROM ubuntu AS build
RUN apt-get update
RUN apt-get install -y curl git unzip
RUN git clone https://github.com/flutter/flutter.git
ENV PATH="/flutter/bin:${PATH}"
COPY . /app
WORKDIR /app
RUN flutter clean
RUN flutter build web
FROM nginx
COPY --from=build /app/build/web /usr/share/nginx/html
With the new configuration, running docker compose up -d --build
will result in the same app as before, deployed to localhost:5555
. The difference is that now the whole build process is happening inside the docker process. Now our Dockerfile
is CI-ready! However, we can do something slightly better.
Using Base Build Image
As you may notice, the first lines in our new Dockerfile
are not specific to our app, but just prepare an ubuntu instance with Flutter CLI. The setup is a good candidate for a dedicated docker image. However, for some reason Flutter team doesn't provide an official one. So I've built and published a custom one, called vosarat/flutter
. Let me show you what's inside!
There's also
vosarat/flutter-beta
image, in case you need one.
Well, essentially, the image is a copy of the first steps in the previous file with just two enhancements on top. The reason behind adding those two lines is that Flutter downloads certain SDKs, like the web on the first run of a matching CLI command. So we'll let Flutter decide what it needs regardless of the platform target by running doctor -v
RUN flutter doctor -v
Also since the image is for web apps we'll force downloading of web sdk by running flutter build web
. Of course, flutter won't be able to actually execute the build since we don't have a project there yet. That's fine, the web sdk will be downloaded anyway and we'll intercept the error by using the ||
directive:
RUN flutter build web || echo "expected fail (build triggered to preload web sdk)"
With that, we'll get the following dockerfile for our base image:
FROM ubuntu AS build
RUN apt-get update
RUN apt-get install -y curl git unzip
RUN git clone https://github.com/flutter/flutter.git
ENV PATH="/flutter/bin:${PATH}"
RUN flutter doctor -v
RUN flutter build web || echo "expected fail (build triggered to preload web sdk)"
Knowing how it all works, let's wrap it up with an example of a production-ready flutter web Dockerfile
utilizing vosarat/flutter
TLDR
This article sheds light on creating a dockerfile for your Flutter web app. Here's a short snippet of the final version:
FROM vosarat/flutter AS build
COPY . /app
WORKDIR /app
RUN flutter clean
RUN flutter build web
FROM nginx
COPY --from=build /app/build/web /usr/share/nginx/html
The version utilizes a base image from my docker registry. You can find the source code for it in the dedicated repository folder and, by the way ... claps are appreciated! 👏