Docker: Custom App Container
In this guide, you'll learn how to package your custom app with a Fluvio client into a Docker image, and how to run a container with docker
CLI.
This tutorial assumes you have a fluvio cluster running. If you don't, follow the Quickstart to get set up.
Example custom Fluvio client
To help illustrate the basic development workflow for all services using a Fluvio client, we'll create this sample Rust service.
Run these commands to initialize the project
$ cargo new fluvio-rust-client
$ cd fluvio-rust-client
$ cargo add async-std --features attributes
$ cargo add chrono fluvio
Copy this code into src/main.rs
use async_std::stream::StreamExt;
use chrono::Local;
use fluvio::metadata::topic::TopicSpec;
use fluvio::{Fluvio, RecordKey};
const TOPIC_NAME: &str = "hello-rust";
const PARTITION_NUM: u32 = 0;
const PARTITIONS: u32 = 1;
const REPLICAS: u32 = 1;
/// This is an example of a basic Fluvio workflow in Rust
///
/// 1. Establish a connection to the Fluvio cluster
/// 2. Create a topic to store data in
/// 3. Create a producer and send some bytes
/// 4. Create a consumer, and stream the data back
#[async_std::main]
async fn main() {
// Connect to Fluvio cluster
let fluvio = Fluvio::connect().await.unwrap();
// Create a topic
let admin = fluvio.admin().await;
let topic_spec = TopicSpec::new_computed(PARTITIONS, REPLICAS, None);
let _topic_create = admin
.create(TOPIC_NAME.to_string(), false, topic_spec)
.await;
// Create a record
let record = format!("Hello World! - Time is {}", Local::now().to_rfc2822());
// Produce to a topic
let producer = fluvio::producer(TOPIC_NAME).await.unwrap();
producer.send(RecordKey::NULL, record).await.unwrap();
// Fluvio batches outgoing records by default, so flush producer to ensure all records are sent
producer.flush().await.unwrap();
// Consume last record from topic
let consumer = fluvio::consumer(TOPIC_NAME, PARTITION_NUM).await.unwrap();
let mut stream = consumer.stream(fluvio::Offset::from_end(1)).await.unwrap();
if let Some(Ok(record)) = stream.next().await {
let string = String::from_utf8_lossy(record.value());
println!("{}", string);
}
}
Example Dockerfile
Save the following Dockerfile
. This Dockerfile adds our workflow an starts it with cargo run
, which will always build the code before it starts.
FROM rust:1.73.0
# Run as the `fluvio` user instead of root
ENV USER=fluvio
RUN useradd --create-home "$USER"
USER $USER
WORKDIR /home/fluvio
# Copy your Rust project and run it
COPY --chown=$USER:$USER Cargo.toml .
COPY --chown=$USER:$USER src ./src
CMD /usr/local/cargo/bin/cargo run
Running docker image
The fluvio clients search for connection information from a config file located at $HOME/.fluvio/config
.
Run with docker run
In this example, we build our docker image with docker build
then start a container from the image with docker run
. We volume mount our host's config file to the container when we start.
$ docker build -t fluvio-client-example .
$ docker run -v $HOME/.fluvio/config:/home/fluvio/.fluvio/config --init --rm fluvio-client-example
After running you'll see the following output:
Running `target/debug/fluvio-rust-client`
Hello World! - Time is Tue, 10 Oct 2023 21:09:05 +0000
The same event has been published to the topic:
$ fluvio consume -Bd hello-rust
Consuming records from 'hello-rust' starting from the beginning of log
Hello World! - Time is Tue, 10 Oct 2023 21:09:05 +0000
Run with docker compose
(alternative)
Copy this yaml file and save it as docker-compose.yaml
# docker-compose.yaml
version: "3"
services:
example:
build: .
volumes:
- $HOME/.fluvio/config:/home/fluvio/.fluvio/config:ro
In this example, we build our docker image with docker compose build
then start a container from the image with docker compose up
. We volume mount our host's config file to the container when we start.
$ docker compose build
$ docker compose up