---
title: "Deploy Open Journal Systems | (Just Updated) Academic Journal Publishing With REST API, Admin Password Generated"
description: "Open Journal Systems (OJS) academic journal publishing, REST API"
category: "Other"
url: https://railway.com/deploy/open-journal-systems-or-just-updated-aca
---

# Deploy Open Journal Systems | (Just Updated) Academic Journal Publishing With REST API, Admin Password Generated

Open Journal Systems (OJS) academic journal publishing, REST API

**[Deploy Open Journal Systems | (Just Updated) Academic Journal Publishing With REST API, Admin Password Generated on Railway](https://railway.com/template/open-journal-systems-or-just-updated-aca)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/open-journal-systems-or-just-updated-aca/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** Other

## Template content

### ojs

- **Image:** ghcr.io/bon5co/ojs-railway:3.5.0-6@sha256:8449c67dd692e7b688a4f697dfefa5a73f6fc61bcd3068afcc0ec28b98593d56
- **Public domain:** Yes

### mariadb

- **Image:** mariadb:11.8@sha256:6422478cb8e159f080fb1d8ccf65101e26fe51385787fde7d16c3b165a331f15
- **Start command:** `/bin/sh -c 'set -e; M=$(cat /sys/fs/cgroup/memory.max 2>/dev/null || echo max); if [ "$M" != max ]; then B=$((M / 2 / 1048576)); [ "$B" -lt 128 ] && B=128; else B=512; fi; export MARIADB_DATABASE=ojs MARIADB_USER=ojs; echo "[railway] uid=$(id -u) innodbBufferPoolMB=$B owner=$(stat -c %u:%g /var/lib/mysql)"; exec docker-entrypoint.sh mariadbd --datadir=/var/lib/mysql/data --innodb-buffer-pool-size=${B}M --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci'`

## Documentation

# Deploy and Host Open Journal Systems on Railway

**Built for an LLM agent to deploy and use.** The deploy form has no fields to fill: the administrator password and the security salt are generated, the database is wired in, the OJS installer runs by itself on first boot and the config, uploads and database sit on volumes. Open Journal Systems (OJS) is the open-source journal management and publishing system from the Public Knowledge Project: submissions, double-blind peer review, copyediting, production and online publication for academic journals, with a REST API.

## About Hosting Open Journal Systems

This template runs two services: **ojs** (the application on one public domain, with a volume at `/data` holding `config.inc.php`, the private files directory and the public directory for journal logos and covers) and **mariadb** (MariaDB 11.8 with a volume at `/var/lib/mysql`). Journals, submissions, users and uploaded files survive redeploys.

**No default password.** Upstream's installer ships with the administrator `admin` / `admin`. This template installs OJS on first boot from the generated `OJS_ADMIN_PASSWORD`, so the account exists only with a 32-character password and the instance never answers to the default one. The wrapper refuses to start without a password of at least 12 characters. The first boot takes about two minutes while the installer creates the schema, so the service has no HTTP healthcheck; wait for the login page to answer 200.

**What the wrapper fixes for Railway.** Apache is moved onto Railway's injected `PORT` (upstream listens on 80) and told the request was https when the edge says so, so links and redirects stay on https. `config.inc.php` is moved onto the volume and linked back in, because upstream keeps it inside the image where a redeploy would lose the installed state. The Host-header allowlist (`allowed_hosts`) is set to your domain, Railway's healthcheck host and the private name, since OJS answers any other host with `400 Server host not allowed`. Sessions are no longer bound to the client IP (`session_check_ip` off), because Railway's edge address changes per request and would sign every user out on the next click. Railway ignores the upstream image's removal of the extra Apache MPM modules, so the entrypoint resets them to prefork before Apache starts. Upstream is OJS 3.5.0-6, pinned by digest.

**Using it.** Sign in at `https://YOUR_DOMAIN/index.php/index/login` as `admin` with `OJS_ADMIN_PASSWORD` (the username is fixed, it is not a template variable). No journal exists yet: create the first one from the site administration page or with the API call below, then open `https://YOUR_DOMAIN/index.php/JOURNAL_PATH/`.

### Agent contract

- Public URL: `https://RAILWAY_PUBLIC_DOMAIN` (the generated domain of the `ojs` service; journals live under `/index.php/JOURNAL_PATH/`, the site-wide REST API under `/index.php/index/api/v1`)
- Private URL: `http://ojs.railway.internal:8080`
- Credentials: the variable `OJS_ADMIN_PASSWORD` on the `ojs` service, for the fixed username `admin` (the username is not a template variable)
- Auth: a form login. `GET /index.php/index/login` returns a `csrfToken`; `POST /index.php/index/login/signIn` with `username`, `password`, `csrfToken` and a cookie jar answers `302` and starts a session. Writes to the REST API need that session cookie plus an `X-Csrf-Token` header, taken from the `csrfToken` in the page `GET /index.php/index/admin/contexts`. A write with no session answers `403`, and so does a write with a wrong token. A login with a wrong password does not redirect and answers `200`.
- Health: `GET /index.php/index/login` returns 200 (about two minutes after the first deploy starts, while the installer runs)

```bash
#!/bin/sh
# needs: RAILWAY_PUBLIC_DOMAIN, OJS_ADMIN_PASSWORD (variables of the ojs service); also curl. Creates one throwaway journal.
B="https://$RAILWAY_PUBLIC_DOMAIN"
J=$(mktemp)
# first boot runs the OJS installer (about two minutes); wait for the login page
n=0; until [ "$(curl -s -o /dev/null -w '%{http_code}' "$B/index.php/index/login")" = 200 ]; do n=$((n+1)); [ "$n" -le 90 ] || break; sleep 5; done
curl -s -o /dev/null -w "health: %{http_code}\n" "$B/index.php/index/login"
T=$(curl -s -c "$J" -b "$J" "$B/index.php/index/login" | sed -n 's/.*name="csrfToken" value="\([^"]*\)".*/\1/p' | head -1)
curl -s -o /dev/null -w "login: %{http_code}\n" -c "$J" -b "$J" -X POST "$B/index.php/index/login/signIn" --data-urlencode "username=admin" --data-urlencode "password=$OJS_ADMIN_PASSWORD" --data-urlencode "csrfToken=$T" --data-urlencode "source="
C=$(curl -s -b "$J" "$B/index.php/index/admin/contexts" | sed -n 's/.*"csrfToken":"\([A-Za-z0-9]*\)".*/\1/p' | head -1)
P="journal$(date +%s)"
D="{\"urlPath\":\"$P\",\"name\":{\"en\":\"Agent Journal\"},\"primaryLocale\":\"en\",\"supportedLocales\":[\"en\"],\"supportedFormLocales\":[\"en\"],\"supportedSubmissionLocales\":[\"en\"],\"contactName\":\"Admin\",\"contactEmail\":\"admin@example.com\"}"
curl -s -o /dev/null -w "write with session: %{http_code}\n" -b "$J" -X POST "$B/index.php/index/api/v1/contexts" -H "X-Csrf-Token: $C" -H "Content-Type: application/json" -d "$D"
curl -s -o /dev/null -w "journal page: %{http_code}\n" "$B/index.php/$P/"
curl -s -o /dev/null -w "write without key: %{http_code}\n" -X POST "$B/index.php/index/api/v1/contexts" -H "Content-Type: application/json" -d "{\"urlPath\":\"x$P\"}"
curl -s -o /dev/null -w "write with wrong key: %{http_code}\n" -X POST "$B/index.php/index/api/v1/contexts" -H "X-Csrf-Token: wrong" -H "Content-Type: application/json" -d "{\"urlPath\":\"y$P\"}"
curl -s -o /dev/null -w "login with wrong password: %{http_code}\n" -X POST "$B/index.php/index/login/signIn" --data-urlencode "username=admin" --data-urlencode "password=${OJS_ADMIN_PASSWORD}x" --data-urlencode "csrfToken=$T" --data-urlencode "source="
rm -f "$J"
```

A fresh deploy prints `health: 200`, `login: 302`, `write with session: 200`, `journal page: 200`, `write without key: 403`, `write with wrong key: 403` and `login with wrong password: 200`.

## Why Deploy Open Journal Systems on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Open Journal Systems on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

- **Agent-ready** - no deployer fields, a contract block and one script that proves the installer finished, the login, the creation of a journal over the REST API and the refusal of a write with no session or a wrong token.
- **No default administrator** - upstream's `admin` / `admin` is replaced by a generated password at install time.
- **Data survives redeploys** - the database, `config.inc.php`, uploaded files and journal images all sit on volumes.
- **Pinned** - the upstream image is pinned by digest and the wrapper by tag and digest.
- **Your journal on your domain** - submissions, reviews and published articles stay on infrastructure you control.

## Common Use Cases

- **Launch an academic journal** - run submission, peer review, copyediting and publication for a new open-access journal.
- **Host several journals for a university or society** - one OJS site, a journal per path, shared users and reviewers.
- **Conference proceedings and working papers** - publish a series with DOIs, issues and article metadata.
- **Agent-driven editorial tooling** - give an agent the REST API to create journals, read submissions and report on editorial status.

## Dependencies for Open Journal Systems Hosting

- The ojs service and the mariadb service with their volumes, created by the template. A custom domain is optional.

### Deployment Dependencies

- [OJS repository](https://github.com/pkp/ojs)
- [PKP documentation](https://docs.pkp.sfu.ca/)
- [Wrapper image source](https://github.com/bon5co/ojs-railway)


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/open-journal-systems-or-just-updated-aca
