Skip to main content
IHP provides multiple deployment options for production environments. This guide covers deploying with NixOS, Docker, and bare metal servers.

Deploying with deploy-to-nixos

IHP comes with a standard command called deploy-to-nixos. This tool is a little wrapper around nixos-rebuild and allows you to deploy IHP apps to a NixOS server. With deploy-to-nixos you can manage your servers in a fully declarative way and keep the full configuration in your git repository. AWS EC2 is a good choice for deploying IHP in a professional setup.

AWS Infrastructure Preparation

The EC2 instance, RDS database, VPS, subnets, security groups, etc, can be setup automatically using Terraform.
1

Install Terraform

Install terraform and setup AWS credentials in .aws/config and .aws/credentials
2

Copy IaC files

Copy the files from the IaC/aws folder from the IaC-aws branch in ihp-boilerplate to your IHP project repo. Run the init command:
cd IaC/aws
terraform init
3

Create terraform.tfvars

Create the file terraform.tfvars with the following content:
prefix = "Project prefix for the resource names"
region = "AWS Region to deploy to"
az_1 = "Availability Zone 1"
az_2 = "Availability Zone 2"
key_name = "The key name of the SSH key-pair"
db_password = "The password for the RDS database"
The two AZs are needed to setup the RDS database. The SSH key-pair should be created in the AWS web interface.
4

Apply Terraform configuration

terraform apply
Important data like the RDS endpoint and the EC2 instance URL is written to the file db_info.txt

Connecting to the EC2 Instance

After you’ve created the instance, configure your local SSH settings to point to the instance. In your ~/.ssh/config add:
Host production
    HostName ec2-.....compute.amazonaws.com
    User root
    IdentityFile ~/.ssh/ihp-app.pem
The SSH host name must match the NixOS configuration name used in your flake.nix. Projects generated by ihp-new use production as the default.
Now you can connect using ssh production.

Configuring the Instance

Projects generated by ihp-new already include deployment configuration files under Config/nix/hosts/production/:
Config/nix/hosts/production/
├── host.nix                  # Entry point, sets system architecture
├── configuration.nix         # Main config: domain, ACME, nginx, services.ihp
└── hardware-configuration.nix # Hardware-specific config (e.g. AWS EC2 AMI)

Editing configuration.nix

Open Config/nix/hosts/production/configuration.nix and update:
1

ACME email

security.acme.defaults.email = "[email protected]";
2

Domain name

services.nginx = {
    virtualHosts."yourdomain.com" = {};
};

services.ihp = {
    domain = "yourdomain.com";
    # ...
};
3

Session secret

Generate one with new-session-secret:
services.ihp = {
    sessionSecret = "your-generated-secret";
    # ...
};
4

SMTP and AWS credentials

Update the CHANGE-ME values in additionalEnvVars if you use email or S3.

Editing hardware-configuration.nix

For AWS EC2 deployments, uncomment the Amazon AMI import:
imports = [
    "${inputs.nixpkgs}/nixos/modules/virtualisation/amazon-image.nix"
];

Deploying the App

Now you can deploy the app using deploy-to-nixos:
deploy-to-nixos production
This will connect to the server via SSH and apply the NixOS configuration. The argument must match both your SSH host name and the nixosConfigurations key in your flake.nix.

Troubleshooting / Operations

If a deployment goes wrong, login to the EC2 instance:
ssh production
Useful debugging commands:
# Check resource usage
df -h
free -m
top

# Start/restart the app
systemctl start app.service
systemctl restart app.service

# Check logs
journalctl --unit=app.service -n 100 --no-pager
journalctl -u worker -r

# Delete old logs if disk is full
journalctl --vacuum-time=2d

# Check hardware issues
dmesg

# Check firewall rules
iptables -L
Changes should always be done declaratively via the nix files. Manual changes will be lost at the next deployment.

Deploying with Docker

Deploying IHP with docker is a good choice for a professional production setup. IHP has a first party CLI tool called ihp-app-to-docker-image to create Docker images out of your app. This tool is available with IHP Pro and IHP Business.

Creating a Docker Image

To create a Docker image, first install Podman, then run:
nix build .#unoptimized-docker-image --option sandbox false --extra-experimental-features nix-command --extra-experimental-features flakes

cat result | podman load
Running podman images shows the image:
$ docker images

REPOSITORY     TAG                                IMAGE ID       CREATED         SIZE
app            g13rks9fb4ik8hnqip2s3ngqq4nq14zw   ffc01de1ec7e   54 years ago    86.6MB
The CREATED timestamp shows over 50 years ago because the image is built with nix. For reproducible builds, the timestamp is set to Jan 1970, 00:00 UTC.

Starting the App Container

You can start your app container like this:
$ docker run -p 8000:8000 app:g13rks9fb4ik8hnqip2s3ngqq4nq14zw

Connecting the DB

You need to connect a postgres database. It’s recommended to use a managed database service like AWS RDS. For a quick setup you can use docker:
$ docker run --name app-db -e POSTGRES_PASSWORD=mysecretpassword -d postgres

# Import the Schema.sql
$ docker exec -i app-db psql -U postgres -d postgres < Application/Schema.sql

# Import the Fixtures.sql
$ docker exec -i app-db psql -U postgres -d postgres < Application/Fixtures.sql
Configure the database connection using the DATABASE_URL env variable:
$ docker run \
    -p 8000:8000 \
    -e 'DATABASE_URL=postgresql://postgres:mysecretpassword@the-hostname/postgres' \
    app:g13rks9fb4ik8hnqip2s3ngqq4nq14zw
In production setups you want to configure the IHP_SESSION_SECRET env variable. It’s a private key used to encrypt your session state.Generate a new secret:
$ docker run -it app:g13rks9fb4ik8hnqip2s3ngqq4nq14zw
IHP_SESSION_SECRET=1J8jtRW331a0IbHBCHmsFNoesQUNFnuHqY8cB5927KsoV5sYmiq3DMmvsYk5S7EDma9YhqZLZWeTFu2pGOxMT2F/5PnifW/5ffwJjZvZcJh9MKPh3Ez9fmPEyxZBDxVp
Use it:
$ docker run \
    -e 'IHP_SESSION_SECRET=1J8jtRW331a0IbHBCHmsFNoesQUNFnuHqY8cB5927KsoV5sYmiq3DMmvsYk5S7EDma9YhqZLZWeTFu2pGOxMT2F/5PnifW/5ffwJjZvZcJh9MKPh3Ez9fmPEyxZBDxVp' \
    app:g13rks9fb4ik8hnqip2s3ngqq4nq14zw

TLS Certificates in Docker Images

If your container makes HTTPS requests and you see certificate errors, your image likely doesn’t contain a root CA bundle. Fix by overriding the IHP Docker image to include CA certificates:
# inside your flake outputs
packages = {
  unoptimized-docker-image = lib.mkForce (pkgs.dockerTools.buildImage {
    name = "ihp-app";
    copyToRoot = with pkgs.dockerTools; [
      usrBinEnv
      binSh
      caCertificates  # /etc/ssl/certs/ca-certificates.crt
      fakeNss
    ];
    config = {
      Cmd = [ "${self'.packages.unoptimized-prod-server}/bin/RunProdServer" ];
      Env = [
        "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"
        "NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"
        "SSL_CERT_DIR=/etc/ssl/certs"
      ];
    };
  });
};

Deploying on Bare Metal

You can build and deploy your IHP app on your own server without external deployment tools.
Make sure that the infrastructure has enough memory. Otherwise, the build might fail because GHC is very memory hungry. You can set up a swap file to work around this.

Install Nix on your server

curl -L https://nixos.org/nix/install | sh
Use the digitally induced cachix binary cache:
cachix use digitallyinduced

Copy your project folder to your server

Copy your application source code to the build server. If you’re using git, we recommend you use SSH agent forwarding.

Configuration

IHP apps are typically configured using environment variables:
  1. Set IHP_ENV=Production to enable production mode
  2. Set IHP_BASEURL=https://{yourdomain}
  3. Set DATABASE_URL to your Postgres connection URL
  4. Set PORT to the port the app will listen on
  5. Configure any custom settings
The database needs the UUID extension: create extension if not exists "uuid-ossp";

Building

Inside your project directory, build your app:
# For optimized production builds (recommended)
nix build .#optimized-prod-server

# For faster unoptimized builds (useful for testing)
nix build .#unoptimized-prod-server
After the build finishes, find the production binary at result/bin/RunProdServer.

Starting the app

Start your app by running:
result/bin/RunProdServer

systemd Integration

The deploy-to-nixos tool includes systemd integration to improve reliability:

Key Features

  1. Systemd Watchdog: The app sends a heartbeat every 30 seconds. If unresponsive, systemd restarts it after 60 seconds.
  2. Socket Activation: Systemd queues incoming HTTP requests during app startup or restarts, eliminating downtime.
  3. Automatic Configuration: The IHP_SYSTEMD environment variable is set to "1" automatically when deploying with deploy-to-nixos.

Build docs developers (and LLMs) love