There’s a couple of options I’ve come across when I need a path to a directory with Actix, usually for static files like Javascript or CSS.

Option 1: CARGO_MANIFEST_DIR

The first is to use the CARGO_MANIFEST_DIR environment variable which is set when compiling a crate; it points to the directory containing the package manifest (Cargo.toml). You can get this value by using the env! macro from std::env:

use std::env;

fn main() { 
    println!(env!("CARGO_MANIFEST_DIR"));
}

You can use this value with Actix to point to the directory containing your static files:

use actix_files::Files;
use actix_web::{App, HttpServer};

use std::env;
use std::path::Path;

#[actix_web::main]
async fn main() -> std::io::Result<()> {

 HttpServer::new(|| {
        App::new()
            .service(actix_files::Files::new(
                "/static",
                Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static")),
            ))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

Downsides

There are a number of problems with this approach though:

  1. Variable is set by Cargo, so complilation has to use cargo build rather than rustc
  2. The variable is set at compile-time, so will be hardcoded to the location of the manifest file when you run cargo build. This means it can work quite nicely in development, but if you use a continuous integration setup like Github Actions or CircleCI, then CARGO_MANIFEST_DIR will be set to the path of the CI runner which won’t be correct for your deployed binary. You could, of course, compile your crate on your production server in the place you want to run it, but that feels ever-so-slightly hacky.

Option 2: Environment variable

Using a very similar approach, you can set your own environment variable to customise the path. This is especially useful since you can add a fallback option, and change the value between environments.

use std::env;

fn main() {
    let static_path = env::var("STATIC_PATH").unwrap_or_else(|_| "./".to_string());
    println!("path: {}", static_path);
}

Outputs

cargo run -> path: ./
STATIC_PATH=/some/dir cargo run -> path: /some/dir

This is used with Actix like this:

use actix_files::Files;
use actix_web::{App, HttpServer};

use std::env;
use std::path::Path;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let static_path = env::var("STATIC_PATH").unwrap_or_else(|_| "./".to_string());

    // change port based on env too
    let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());

    // note move so static_path is available in closure
    HttpServer::new(move || {
        App::new()
            .service(actix_files::Files::new(
                "/static",
                Path::new(&format!("{}/static", static_path)),
            ))
    })
    .bind(format!("0.0.0.0:{}", port))? // update port
    .run()
    .await
}

Option 3: Nginx

If you’re running a web service like Actix, another alternative is to use a dedicated server like Nginx in front of your static content and skip Actix entirely. I usually keep the Actix file serving for local development, then add Nginx in front to intercept requests to static files.

Actix service:

use actix_files::Files;
use actix_web::{App, HttpServer};

use std::env;
use std::path::Path;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let static_path = env::var("STATIC_PATH").unwrap_or_else(|_| "./".to_string());
    let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());

    // note move so static_path is available in closure
    HttpServer::new(move || {
        App::new()
            .service(actix_files::Files::new(
                "/static",
                Path::new(&format!("{}/static", static_path)),
            ))
    })
    .bind(format!("0.0.0.0:{}", port))?
    .run()
    .await
}

Then the Nginx configuration would look something like

# The Actix service
upstream printenvvars {
    server localhost:8080;
}

server {
    # usual setup stuff
    server_name envvars.xyz;

    # any requests to envvars.xyz/static/ will be intercepted
    # and served by Nginx instead of Actix
    location /static/ {
        alias /path/to/your/static/files/;
    }

    # reverse proxy everything else to Actix
    location / {
        proxy_pass http://printenvvars;
    }
}

This means Nginx can intercept any requests to the /static route and serve the files directly, so the requests for static files never get to Actix in production, while all other requests get passed to the Actix using the proxy_pass directive. The advantages of this is that Nginx is incredibly good at handling requests and serving static files; it’s quite a common pattern to have Nginx handle all web requests and then reverse proxy the requests to different application servers.

Nginx as a reverse proxy really works well if you have a completely static frontend and an API, for instance a React single-page app calling your Actix server. If you were using server-side rendering with something like Tera, then using an environment variable would be better instead.