Nix Module
OpenPost can also be deployed through a NixOS module. This is the production setup behind https://openpost.rgo.pt (my instance).
What this example shows
- Running OpenPost as an OCI container
- Persisting SQLite and media storage under
/var/lib/openpost - Supplying secrets through
sops - Wiring public callback and media URLs to the deployed domain
- Exposing the service through your existing reverse proxy layer
Current module
Source: rodrgds/nix-config/modules/services/openpost/default.nix
nix
# OpenPost - Multi-platform social media posting
# https://github.com/rodrgds/openpost
{
config,
lib,
pkgs,
...
}:
let
cfg = config.vps.openpost;
# Host port (external) - must be unique per service
openpostHostPort = 8090;
# Container port (internal) - OpenPost listens on 8080 inside container
openpostContainerPort = 8080;
in
{
options.vps.openpost = {
enable = lib.mkEnableOption "OpenPost social media scheduler";
domain = lib.mkOption {
type = lib.types.str;
default = "openpost.rgo.pt";
description = "Domain for OpenPost";
};
timezone = lib.mkOption {
type = lib.types.str;
default = "Europe/Lisbon";
description = "Timezone for OpenPost";
};
};
config = lib.mkIf cfg.enable {
# Create persistent directories
# Note: Container runs as user 'openpost' (UID 1000)
systemd.tmpfiles.rules = [
"d /var/lib/openpost 0755 root root -"
"d /var/lib/openpost/data 0755 1000 1000 -"
"d /var/lib/openpost/data/db 0755 1000 1000 -"
"d /var/lib/openpost/data/media 0755 1000 1000 -"
];
# OpenPost application
virtualisation.oci-containers.containers.openpost = {
image = "ghcr.io/rodrgds/openpost:latest";
environment = {
OPENPOST_PORT = toString openpostContainerPort;
OPENPOST_DATABASE_PATH = "/data/db/openpost.db";
OPENPOST_MEDIA_PATH = "/data/media";
OPENPOST_MEDIA_URL = "https://${cfg.domain}/media";
OPENPOST_APP_URL = "https://${cfg.domain}";
OPENPOST_EXTRA_CORS_ORIGINS = "https://${cfg.domain}";
OPENPOST_DISABLE_REGISTRATIONS = "true";
LINKEDIN_DISABLE_THREAD_REPLIES = "true";
TZ = cfg.timezone;
};
environmentFiles = [
config.sops.templates.openpost-env.path
];
volumes = [
"/var/lib/openpost/data:/data"
];
ports = [
"127.0.0.1:${toString openpostHostPort}:${toString openpostContainerPort}"
];
extraOptions = [
"--network=podman"
"--health-cmd=wget --spider http://localhost:${toString openpostContainerPort}/api/v1/health"
"--health-interval=30s"
"--health-timeout=3s"
"--health-retries=3"
];
};
# Secrets
sops.templates = {
"openpost-env" = {
content = ''
OPENPOST_JWT_SECRET=${config.sops.placeholder.openpost_jwt_secret}
OPENPOST_ENCRYPTION_KEY=${config.sops.placeholder.openpost_encryption_key}
X_CLIENT_ID=${config.sops.placeholder.openpost_twitter_client_id}
X_CLIENT_SECRET=${config.sops.placeholder.openpost_twitter_client_secret}
X_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/x/callback
LINKEDIN_CLIENT_ID=${config.sops.placeholder.openpost_linkedin_client_id}
LINKEDIN_CLIENT_SECRET=${config.sops.placeholder.openpost_linkedin_client_secret}
LINKEDIN_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/linkedin/callback
THREADS_CLIENT_ID=${config.sops.placeholder.openpost_threads_client_id}
THREADS_CLIENT_SECRET=${config.sops.placeholder.openpost_threads_client_secret}
THREADS_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/threads/callback
MASTODON_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/mastodon/callback
MASTODON_SERVERS=${config.sops.placeholder.openpost_mastodon_servers}
'';
mode = "0444";
};
};
# Caddy reverse proxy
vps.caddy.internalPorts.openpost = openpostHostPort;
};
}