Nix Module
OpenPost can also be deployed through a NixOS module. This is the production setup behind https://op.rgo.pt.
Source
- Live module source: rodrgds/nix-config/modules/services/openpost/default.nix
- Raw file used during docs builds: raw.githubusercontent.com/.../default.nix
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
The snippet below is refreshed at docs build time from the source repository above. If the fetch fails, the docs fall back to the last generated copy committed in this repo.
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_DB_PATH = "/data/db/openpost.db";
OPENPOST_MEDIA_PATH = "/data/media";
OPENPOST_MEDIA_URL = "https://${cfg.domain}/media";
OPENPOST_FRONTEND_URL = "https://${cfg.domain}";
OPENPOST_CORS_EXTRA_ORIGINS = "https://${cfg.domain}";
OPENPOST_DISABLE_LINKEDIN_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 = ''
JWT_SECRET=${config.sops.placeholder.openpost_jwt_secret}
ENCRYPTION_KEY=${config.sops.placeholder.openpost_encryption_key}
TWITTER_CLIENT_ID=${config.sops.placeholder.openpost_twitter_client_id}
TWITTER_CLIENT_SECRET=${config.sops.placeholder.openpost_twitter_client_secret}
TWITTER_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;
};
}