generateDockerfile method
Generate production Dockerfile
Implementation
Future<void> generateDockerfile() async {
if (!config.createServer) return;
info('Generating Dockerfile...');
final String content =
'''
# Production Dockerfile for ${config.serverPackageName}
# Multi-stage build for minimal image size
#
# Build context: the server package directory (e.g. ${config.serverPackageName}/).
# Oracular copies the sibling models package into this directory before
# `docker build` so `${config.modelsPackageName}/` is available at the
# root of the context. The pubspec.yaml inside /app references models
# via `path: ../${config.modelsPackageName}`, which resolves to the
# /${config.modelsPackageName}/ directory placed alongside /app below.
# Build stage
FROM ubuntu:22.04 AS build
# Install dependencies
# `flutter build linux --release` requires the full Flutter Linux desktop
# toolchain — clang, cmake, ninja-build, pkg-config, libgtk-3-dev,
# liblzma-dev, libstdc++-12-dev — in addition to the base curl/git/zip
# utilities. Without these the build fails with `CMake is required for
# Linux development.` See:
# https://docs.flutter.dev/get-started/install/linux/desktop#additional-linux-requirements
RUN apt-get update && apt-get install -y \\
curl \\
git \\
unzip \\
xz-utils \\
zip \\
libglu1-mesa \\
clang \\
cmake \\
ninja-build \\
pkg-config \\
libgtk-3-dev \\
liblzma-dev \\
libstdc++-12-dev \\
&& rm -rf /var/lib/apt/lists/*
# Install Flutter
RUN git clone https://github.com/flutter/flutter.git /flutter
ENV PATH="/flutter/bin:\$PATH"
RUN git config --global --add safe.directory /flutter
RUN flutter doctor
RUN flutter config --enable-linux-desktop
# Copy the models package alongside /app so the relative path
# `../${config.modelsPackageName}` in pubspec.yaml resolves correctly.
COPY ${config.modelsPackageName}/ /${config.modelsPackageName}/
# Copy server source (everything in the build context) into /app.
WORKDIR /app
COPY . /app/
# Get dependencies and build
RUN flutter pub get
RUN flutter build linux --release
# Runtime stage
FROM ubuntu:22.04
# `flutter build linux --release` produces a Flutter *desktop* binary that
# links against GTK and tries to open an X11 display on startup
# (`Gtk-WARNING **: cannot open display:`). On Cloud Run there is no display,
# so we start Xvfb manually and point DISPLAY at it before launching the
# binary.
#
# We do this with a small shell wrapper instead of the canonical `xvfb-run`
# helper because `xvfb-run` on Ubuntu 22.04 hangs forever in `wait` waiting
# for an Xvfb-sent SIGUSR1 that the modern Xvfb doesn't emit reliably,
# leaving the container stuck before the binary ever runs (and Cloud Run
# then fails the deploy with "container failed to start and listen on the
# port"). The manual `Xvfb :99 & sleep && exec …` pattern is what every
# headless-Flutter-on-Cloud-Run example actually uses in production.
#
# Packages:
# - xvfb - virtual framebuffer X server
# - libgtk-3-0 - GTK runtime the Flutter shell links against
# - libegl1 / libgles2 - OpenGL drivers Flutter shell needs even headless
# - libblkid1 / liblzma5 - misc transitive runtime deps
RUN apt-get update && apt-get install -y \\
xvfb \\
libgtk-3-0 \\
libegl1 \\
libgles2 \\
libblkid1 \\
liblzma5 \\
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy the Flutter desktop bundle from the build stage. We keep the
# `bundle/` subdirectory layout (rather than flattening into /app) so that
# Flutter's relative asset / library lookups (./data, ./lib/*.so) resolve
# the way they do during local `flutter run`.
COPY --from=build /app/build/linux/x64/release/bundle/ ./bundle/
# Copy service account key (lives at the root of the build context).
COPY *.json ./
# Expose port
EXPOSE 8080
# Launch the binary with a manually-managed Xvfb backing DISPLAY=:99.
# `exec` replaces the shell so the binary becomes PID 1 and receives
# SIGTERM cleanly when Cloud Run scales the instance down.
CMD ["sh", "-c", "Xvfb :99 -screen 0 800x600x16 & export DISPLAY=:99 && sleep 1 && exec ./bundle/\$SERVER_NAME"]
'''
.replaceAll('\$SERVER_NAME', config.serverPackageName);
final File file = File(p.join(serverPath, 'Dockerfile'));
await file.writeAsString(content);
success('Generated: ${config.serverPackageName}/Dockerfile');
}