build: make project deployable via docker/podman
This commit is contained in:
parent
2ae860f176
commit
9dde52c1cd
7 changed files with 102 additions and 109 deletions
17
.sqlx/query-4bd6bbade521cd577279e91d8a8b978748046beff031d153699b351089c3bf9b.json
generated
Normal file
17
.sqlx/query-4bd6bbade521cd577279e91d8a8b978748046beff031d153699b351089c3bf9b.json
generated
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO subscriptions (id, email, name, subscribed_at)\n VALUES ($1, $2, $3, $4)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Uuid",
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Timestamptz"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "4bd6bbade521cd577279e91d8a8b978748046beff031d153699b351089c3bf9b"
|
||||||
|
}
|
||||||
102
Dockerfile
102
Dockerfile
|
|
@ -1,94 +1,8 @@
|
||||||
# Global ARGs
|
FROM docker.io/rust:1.75.0
|
||||||
|
WORKDIR /app
|
||||||
ARG WORKDIR_ROOT=/usr/src
|
RUN apt update && apt install lld clang -y
|
||||||
|
COPY . .
|
||||||
ARG PROJECT_NAME=learn_axum
|
ENV SQLX_OFFLINE true
|
||||||
|
RUN cargo build --release
|
||||||
ARG BUILD_TARGET=x86_64-unknown-linux-musl
|
ENV APP_ENVIRONMENT production
|
||||||
|
ENTRYPOINT ["./target/release/learn_axum"]
|
||||||
ARG BUILD_MODE=release
|
|
||||||
|
|
||||||
ARG BUILD_BIN=${PROJECT_NAME}
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
FROM clux/muslrust:stable AS build
|
|
||||||
|
|
||||||
# Import global ARGs
|
|
||||||
ARG WORKDIR_ROOT
|
|
||||||
ARG PROJECT_NAME
|
|
||||||
ARG BUILD_TARGET
|
|
||||||
ARG BUILD_MODE
|
|
||||||
ARG BUILD_BIN
|
|
||||||
|
|
||||||
WORKDIR ${WORKDIR_ROOT}
|
|
||||||
|
|
||||||
# Docker build cache: Create and build an empty dummy project with all
|
|
||||||
# external dependencies to avoid redownloading them on subsequent builds
|
|
||||||
# if unchanged.
|
|
||||||
RUN USER=root \
|
|
||||||
cargo new --bin ${PROJECT_NAME}
|
|
||||||
WORKDIR ${WORKDIR_ROOT}/${PROJECT_NAME}
|
|
||||||
|
|
||||||
COPY [ \
|
|
||||||
"Cargo.toml", \
|
|
||||||
"Cargo.lock", \
|
|
||||||
"./" ]
|
|
||||||
|
|
||||||
# Build the dummy project(s), then delete all build artefacts that must(!) not be cached
|
|
||||||
# RUN cargo build --${BUILD_MODE} --target ${BUILD_TARGET}
|
|
||||||
#\
|
|
||||||
# && \
|
|
||||||
# rm -f ./target/${BUILD_TARGET}/${BUILD_MODE}/${PROJECT_NAME}* \
|
|
||||||
# && \
|
|
||||||
# rm -f ./target/${BUILD_TARGET}/${BUILD_MODE}/deps/${PROJECT_NAME}-* \
|
|
||||||
# && \
|
|
||||||
# rm -rf ./target/${BUILD_TARGET}/${BUILD_MODE}/.fingerprint/${PROJECT_NAME}-*
|
|
||||||
|
|
||||||
# Copy all project (re-)sources that are required for building (ordered alphabetically)
|
|
||||||
COPY [ "migrations", "./migrations/" ]
|
|
||||||
COPY [ "src", "./src/" ]
|
|
||||||
|
|
||||||
# Test and build the actual project
|
|
||||||
RUN cargo test --${BUILD_MODE} --target ${BUILD_TARGET} --workspace \
|
|
||||||
&& \
|
|
||||||
cargo build --${BUILD_MODE} --target ${BUILD_TARGET} --bin ${BUILD_BIN} \
|
|
||||||
&& \
|
|
||||||
strip ./target/${BUILD_TARGET}/${BUILD_MODE}/${BUILD_BIN}
|
|
||||||
|
|
||||||
# Switch back to the root directory
|
|
||||||
#
|
|
||||||
# NOTE(2019-08-30, uklotzde): Otherwise copying from the build image fails
|
|
||||||
# during all subsequent builds of the 2nd stage with an unchanged 1st stage
|
|
||||||
# image. Tested with podman 1.5.x on Fedora 30.
|
|
||||||
WORKDIR /
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# 2nd Build Stage
|
|
||||||
FROM scratch
|
|
||||||
|
|
||||||
# Import global ARGs
|
|
||||||
ARG WORKDIR_ROOT
|
|
||||||
ARG PROJECT_NAME
|
|
||||||
ARG BUILD_TARGET
|
|
||||||
ARG BUILD_MODE
|
|
||||||
ARG BUILD_BIN
|
|
||||||
|
|
||||||
ARG DATA_VOLUME="/volume"
|
|
||||||
|
|
||||||
ARG EXPOSE_PORT=8080
|
|
||||||
|
|
||||||
# Copy the statically-linked executable into the minimal scratch image
|
|
||||||
COPY --from=build [ \
|
|
||||||
"${WORKDIR_ROOT}/${PROJECT_NAME}/target/${BUILD_TARGET}/${BUILD_MODE}/${BUILD_BIN}", \
|
|
||||||
"./entrypoint" ]
|
|
||||||
|
|
||||||
EXPOSE ${EXPOSE_PORT}
|
|
||||||
|
|
||||||
VOLUME [ ${DATA_VOLUME} ]
|
|
||||||
|
|
||||||
# Bind the exposed port to Rocket that is used as the web framework
|
|
||||||
ENV SERVER_PORT ${EXPOSE_PORT}
|
|
||||||
|
|
||||||
ENTRYPOINT [ "./entrypoint" ]
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
application_port: 8000
|
application:
|
||||||
|
port: 8000
|
||||||
database:
|
database:
|
||||||
host: "127.0.0.1"
|
host: "127.0.0.1"
|
||||||
port: 5432
|
port: 5432
|
||||||
2
configuration/local.yaml
Normal file
2
configuration/local.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
application:
|
||||||
|
host: 127.0.0.1
|
||||||
2
configuration/production.yaml
Normal file
2
configuration/production.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
application:
|
||||||
|
host: 0.0.0.0
|
||||||
|
|
@ -1,17 +1,27 @@
|
||||||
use secrecy::{ExposeSecret, Secret};
|
use secrecy::{ExposeSecret, Secret};
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
/// The application's settings
|
/// The setting collection.
|
||||||
///
|
///
|
||||||
/// * `database`: database settings
|
/// * `database`: database settings
|
||||||
/// * `application_port`: the port the app is running on
|
/// * `application`: application settings
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub database: DatabaseSettings,
|
pub database: DatabaseSettings,
|
||||||
pub application_port: u16,
|
pub application: ApplicationSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
/// The database settings
|
/// The application settings.
|
||||||
|
///
|
||||||
|
/// * `port`: The port to listen on
|
||||||
|
/// * `host`: The host address to listen on
|
||||||
|
pub struct ApplicationSettings {
|
||||||
|
pub port: u16,
|
||||||
|
pub host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
/// The database settings.
|
||||||
///
|
///
|
||||||
/// * `username`: the DB username
|
/// * `username`: the DB username
|
||||||
/// * `password`: the DB pasword
|
/// * `password`: the DB pasword
|
||||||
|
|
@ -28,14 +38,58 @@ pub struct DatabaseSettings {
|
||||||
pub require_ssl: bool,
|
pub require_ssl: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides the application settings
|
/// The possible runtime environment for our application.
|
||||||
|
pub enum Environment {
|
||||||
|
Local,
|
||||||
|
Production,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Environment::Local => "local",
|
||||||
|
Environment::Production => "production",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Environment {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"local" => Ok(Self::Local),
|
||||||
|
"production" => Ok(Self::Production),
|
||||||
|
other => Err(format!(
|
||||||
|
"{} is not a supported environment. \
|
||||||
|
Use either `local` or `production`.",
|
||||||
|
other
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides the application settings.
|
||||||
pub fn get_configuration() -> Result<Settings, config::ConfigError> {
|
pub fn get_configuration() -> Result<Settings, config::ConfigError> {
|
||||||
|
let base_path = std::env::current_dir().expect("Failed to determine the current directory");
|
||||||
|
let configuration_directory = base_path.join("configuration");
|
||||||
|
|
||||||
|
// Detect the running environment.
|
||||||
|
// Default to `local` if unspecified.
|
||||||
|
let environment: Environment = std::env::var("APP_ENVIRONMENT")
|
||||||
|
.unwrap_or_else(|_| "local".into())
|
||||||
|
.try_into()
|
||||||
|
.expect("Failed to parse APP_ENVIRONMENT.");
|
||||||
|
let environment_filename = format!("{}.yaml", environment.as_str());
|
||||||
let settings = config::Config::builder()
|
let settings = config::Config::builder()
|
||||||
.add_source(config::File::new(
|
.add_source(config::File::from(
|
||||||
"configuration.yaml",
|
configuration_directory.join("base.yaml"),
|
||||||
config::FileFormat::Yaml,
|
))
|
||||||
|
.add_source(config::File::from(
|
||||||
|
configuration_directory.join(environment_filename),
|
||||||
))
|
))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
settings.try_deserialize::<Settings>()
|
settings.try_deserialize::<Settings>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
src/main.rs
15
src/main.rs
|
|
@ -2,7 +2,7 @@ use learn_axum::configuration::get_configuration;
|
||||||
use learn_axum::startup;
|
use learn_axum::startup;
|
||||||
use learn_axum::telemetry::{get_subscriber, init_subscriber};
|
use learn_axum::telemetry::{get_subscriber, init_subscriber};
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
use sqlx::PgPool;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -13,11 +13,14 @@ async fn main() {
|
||||||
init_subscriber(subscriber);
|
init_subscriber(subscriber);
|
||||||
|
|
||||||
let configuration = get_configuration().expect("Failed to read configuration.");
|
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||||
let addr = format!("127.0.0.1:{}", configuration.application_port);
|
let addr = format!(
|
||||||
|
"{}:{}",
|
||||||
|
configuration.application.host, configuration.application.port
|
||||||
|
);
|
||||||
let listener = TcpListener::bind(addr).await.unwrap(); //.expect("Unable to bind to port");
|
let listener = TcpListener::bind(addr).await.unwrap(); //.expect("Unable to bind to port");
|
||||||
let connection_pool =
|
let connection_pool = PgPoolOptions::new()
|
||||||
PgPool::connect(configuration.database.connection_string().expose_secret())
|
.acquire_timeout(std::time::Duration::from_secs(2))
|
||||||
.await
|
.connect_lazy(configuration.database.connection_string().expose_secret())
|
||||||
.expect("Failed to connect to Postgres.");
|
.expect("Failed to connect to Postgres.");
|
||||||
startup::run(listener, connection_pool).await.unwrap();
|
startup::run(listener, connection_pool).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue