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
|
||||
|
||||
ARG WORKDIR_ROOT=/usr/src
|
||||
|
||||
ARG PROJECT_NAME=learn_axum
|
||||
|
||||
ARG BUILD_TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
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" ]
|
||||
FROM docker.io/rust:1.75.0
|
||||
WORKDIR /app
|
||||
RUN apt update && apt install lld clang -y
|
||||
COPY . .
|
||||
ENV SQLX_OFFLINE true
|
||||
RUN cargo build --release
|
||||
ENV APP_ENVIRONMENT production
|
||||
ENTRYPOINT ["./target/release/learn_axum"]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
application_port: 8000
|
||||
application:
|
||||
port: 8000
|
||||
database:
|
||||
host: "127.0.0.1"
|
||||
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};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
/// The application's settings
|
||||
/// The setting collection.
|
||||
///
|
||||
/// * `database`: database settings
|
||||
/// * `application_port`: the port the app is running on
|
||||
/// * `application`: application settings
|
||||
pub struct Settings {
|
||||
pub database: DatabaseSettings,
|
||||
pub application_port: u16,
|
||||
pub application: ApplicationSettings,
|
||||
}
|
||||
|
||||
#[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
|
||||
/// * `password`: the DB pasword
|
||||
|
|
@ -28,14 +38,58 @@ pub struct DatabaseSettings {
|
|||
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> {
|
||||
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()
|
||||
.add_source(config::File::new(
|
||||
"configuration.yaml",
|
||||
config::FileFormat::Yaml,
|
||||
.add_source(config::File::from(
|
||||
configuration_directory.join("base.yaml"),
|
||||
))
|
||||
.add_source(config::File::from(
|
||||
configuration_directory.join(environment_filename),
|
||||
))
|
||||
.build()?;
|
||||
|
||||
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::telemetry::{get_subscriber, init_subscriber};
|
||||
use secrecy::ExposeSecret;
|
||||
use sqlx::PgPool;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -13,11 +13,14 @@ async fn main() {
|
|||
init_subscriber(subscriber);
|
||||
|
||||
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 connection_pool =
|
||||
PgPool::connect(configuration.database.connection_string().expose_secret())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres.");
|
||||
let connection_pool = PgPoolOptions::new()
|
||||
.acquire_timeout(std::time::Duration::from_secs(2))
|
||||
.connect_lazy(configuration.database.connection_string().expose_secret())
|
||||
.expect("Failed to connect to Postgres.");
|
||||
startup::run(listener, connection_pool).await.unwrap();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue